]> git.lyx.org Git - features.git/blob - src/frontends/controllers/biblio.C
This file is part of LyX, the document processor.
[features.git] / src / frontends / controllers / biblio.C
1 /**
2  * \file biblio.C
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Angus Leeming
7  * \author Herbert Voss
8  *
9  * Full author contact details are available in file CREDITS
10  */
11
12 #include <config.h>
13
14 #ifdef __GNUG__
15 #pragma implementation
16 #endif
17
18 #include "LString.h"
19 #include "biblio.h"
20 #include "gettext.h" // for _()
21 #include "helper_funcs.h"
22 #include "support/lstrings.h"
23 #include "support/LAssert.h"
24
25 #include <boost/regex.hpp>
26
27 #include <algorithm>
28
29 using std::find;
30 using std::min;
31 using std::vector;
32 using std::sort;
33
34 namespace biblio
35 {
36
37 namespace {
38
39 using namespace biblio;
40
41 char const * const citeCommands[] = {
42         "cite", "citet", "citep", "citealt", "citealp", "citeauthor",
43         "citeyear", "citeyearpar" };
44
45 unsigned int const nCiteCommands =
46         sizeof(citeCommands) / sizeof(char *);
47
48 CiteStyle const citeStyles[] = {
49         CITE, CITET, CITEP, CITEALT, CITEALP,
50         CITEAUTHOR, CITEYEAR, CITEYEARPAR };
51
52 unsigned int const nCiteStyles =
53         sizeof(citeStyles) / sizeof(CiteStyle);
54
55 CiteStyle const citeStylesFull[] = {
56         CITET, CITEP, CITEALT, CITEALP, CITEAUTHOR };
57
58 unsigned int const nCiteStylesFull =
59         sizeof(citeStylesFull) / sizeof(CiteStyle);
60
61 CiteStyle const citeStylesUCase[] = {
62         CITET, CITEP, CITEALT, CITEALP, CITEAUTHOR };
63
64 unsigned int const nCiteStylesUCase =
65         sizeof(citeStylesUCase) / sizeof(CiteStyle);
66
67
68 // The functions doing the dirty work for the search.
69 vector<string>::const_iterator
70 simpleSearch(InfoMap const & theMap,
71              vector<string> const & keys,
72              string const & expr,
73              vector<string>::const_iterator start,
74              Direction dir,
75              bool caseSensitive)
76 {
77         string tmp = expr;
78         if (!caseSensitive)
79                 tmp = lowercase(tmp);
80
81         vector<string> searchwords = getVectorFromString(tmp, " ");
82
83         // Loop over all keys from start...
84         for (vector<string>::const_iterator it = start;
85              // End condition is direction-dependent.
86              (dir == FORWARD) ? (it<keys.end()) : (it>=keys.begin());
87              // increment is direction-dependent.
88              (dir == FORWARD) ? (++it) : (--it)) {
89
90                 string data = (*it);
91                 InfoMap::const_iterator info = theMap.find(*it);
92                 if (info != theMap.end())
93                         data += " " + info->second;
94                 if (!caseSensitive)
95                         data = lowercase(data);
96
97                 bool found = true;
98
99                 // Loop over all search words...
100                 for (vector<string>::const_iterator sit = searchwords.begin();
101                      sit != searchwords.end(); ++sit) {
102                         if (data.find(*sit) == string::npos) {
103                                 found = false;
104                                 break;
105                         }
106                 }
107
108                 if (found) return it;
109         }
110
111         return keys.end();
112 }
113
114
115 vector<string>::const_iterator
116 regexSearch(InfoMap const & theMap,
117             vector<string> const & keys,
118             string const & expr,
119             vector<string>::const_iterator start,
120             Direction dir)
121 {
122         boost::regex reg(expr);
123
124         for (vector<string>::const_iterator it = start;
125              // End condition is direction-dependent.
126              (dir == FORWARD) ? (it < keys.end()) : (it >= keys.begin());
127              // increment is direction-dependent.
128              (dir == FORWARD) ? (++it) : (--it)) {
129
130                 string data = (*it);
131                 InfoMap::const_iterator info = theMap.find(*it);
132                 if (info != theMap.end())
133                         data += " " + info->second;
134
135                 if (boost::regex_match(data, reg)) {
136                         return it;
137                 }
138         }
139
140         return keys.end();
141 }
142
143
144 } // namespace anon
145
146
147 string const familyName(string const & name)
148 {
149         // Very simple parser
150         string fname = name;
151
152         // possible authorname combinations are:
153         // "Surname, FirstName"
154         // "Surname, F."
155         // "FirstName Surname"
156         // "F. Surname"
157         string::size_type idx = fname.find(",");
158         if (idx != string::npos)
159                 return ltrim(fname.substr(0, idx));
160         idx = fname.rfind(".");
161         if (idx != string::npos)
162                 fname = ltrim(fname.substr(idx + 1));
163         // test if we have a LaTeX Space in front
164         if (fname[0] == '\\')
165                 return fname.substr(2);
166
167         return fname;
168 }
169
170
171 string const getAbbreviatedAuthor(InfoMap const & map, string const & key)
172 {
173         lyx::Assert(!map.empty());
174
175         InfoMap::const_iterator it = map.find(key);
176         if (it == map.end())
177                 return string();
178
179         string::size_type const pos = it->second.find("TheBibliographyRef");
180         if (pos != string::npos) {
181                 if (pos <= 2) {
182                         return string();
183                 }
184
185                 string const opt = trim(it->second.substr(0, pos - 1));
186                 if (opt.empty())
187                         return string();
188
189                 string authors;
190                 split(opt, authors, '(');
191                 return authors;
192         }
193
194         string author = parseBibTeX(it->second, "author");
195         if (author.empty())
196                 author = parseBibTeX(it->second, "editor");
197
198         if (author.empty()) {
199                 author = parseBibTeX(it->second, "key");
200                 if (author.empty())
201                         author = key;
202                 return author;
203         }
204
205         vector<string> authors = getVectorFromString(author, " and");
206
207         if (!authors.empty()) {
208                 author.erase();
209
210                 for (vector<string>::iterator it = authors.begin();
211                      it != authors.end(); ++it) {
212                         *it = familyName(rtrim(*it));
213                 }
214
215                 author = authors[0];
216                 if (authors.size() == 2)
217                         author += _(" and ") + authors[1];
218                 else if (authors.size() > 2)
219                         author += _(" et al.");
220         }
221
222         return author;
223 }
224
225
226 string const getYear(InfoMap const & map, string const & key)
227 {
228         lyx::Assert(!map.empty());
229
230         InfoMap::const_iterator it = map.find(key);
231         if (it == map.end())
232                 return string();
233
234         string::size_type const pos = it->second.find("TheBibliographyRef");
235         if (pos != string::npos) {
236                 if (pos <= 2) {
237                         return string();
238                 }
239
240                 string const opt =
241                         trim(it->second.substr(0, pos - 1));
242                 if (opt.empty())
243                         return string();
244
245                 string authors;
246                 string const tmp = split(opt, authors, '(');
247                 string year;
248                 split(tmp, year, ')');
249                 return year;
250
251         }
252
253         string year = parseBibTeX(it->second, "year");
254         if (year.empty())
255                 year = _("No year");
256
257         return year;
258 }
259
260
261 // A functor for use with std::sort, leading to case insensitive sorting
262 struct compareNoCase: public std::binary_function<string, string, bool>
263 {
264         bool operator()(string const & s1, string const & s2) const {
265                 return compare_ascii_no_case(s1, s2) < 0;
266         }
267 };
268
269 vector<string> const getKeys(InfoMap const & map)
270 {
271         vector<string> bibkeys;
272
273         for (InfoMap::const_iterator it = map.begin(); it != map.end(); ++it) {
274                 bibkeys.push_back(it->first);
275         }
276
277         sort(bibkeys.begin(), bibkeys.end(), compareNoCase());
278         return bibkeys;
279 }
280
281
282 string const getInfo(InfoMap const & map, string const & key)
283 {
284         lyx::Assert(!map.empty());
285
286         InfoMap::const_iterator it = map.find(key);
287         if (it == map.end())
288                 return string();
289
290         // is the entry a BibTeX one or one from lyx-layout "bibliography"?
291         string const separator("TheBibliographyRef");
292         string::size_type const pos = it->second.find(separator);
293         if (pos != string::npos) {
294                 string::size_type const pos2 = pos + separator.size();
295                 string const info = trim(it->second.substr(pos2));
296                 return info;
297         }
298
299         // Search for all possible "required" keys
300         string author = parseBibTeX(it->second, "author");
301         if (author.empty())
302                 author = parseBibTeX(it->second, "editor");
303
304         string year       = parseBibTeX(it->second, "year");
305         string title      = parseBibTeX(it->second, "title");
306         string booktitle  = parseBibTeX(it->second, "booktitle");
307         string chapter    = parseBibTeX(it->second, "chapter");
308         string number     = parseBibTeX(it->second, "number");
309         string volume     = parseBibTeX(it->second, "volume");
310         string pages      = parseBibTeX(it->second, "pages");
311
312         string media      = parseBibTeX(it->second, "journal");
313         if (media.empty())
314                 media = parseBibTeX(it->second, "publisher");
315         if (media.empty())
316                 media = parseBibTeX(it->second, "school");
317         if (media.empty())
318                 media = parseBibTeX(it->second, "institution");
319
320         ostringstream result;
321         if (!author.empty())
322                 result << author << ", ";
323         if (!title.empty())
324                 result << title;
325         if (!booktitle.empty())
326                 result << ", in " << booktitle;
327         if (!chapter.empty())
328                 result << ", Ch. " << chapter;
329         if (!media.empty())
330                 result << ", " << media;
331         if (!volume.empty())
332                 result << ", vol. " << volume;
333         if (!number.empty())
334                 result << ", no. " << number;
335         if (!pages.empty())
336                 result << ", pp. " << pages;
337         if (!year.empty())
338                 result << ", " << year;
339
340         string const result_str = rtrim(result.str().c_str());
341         if (!result_str.empty())
342                 return result_str;
343
344         // This should never happen (or at least be very unusual!)
345         return it->second;
346 }
347
348
349 vector<string>::const_iterator
350 searchKeys(InfoMap const & theMap,
351            vector<string> const & keys,
352            string const & expr,
353            vector<string>::const_iterator start,
354            Search type,
355            Direction dir,
356            bool caseSensitive)
357 {
358         // Preliminary checks
359         if (start < keys.begin() || start >= keys.end())
360                 return keys.end();
361
362         string search_expr = trim(expr);
363         if (search_expr.empty())
364                 return keys.end();
365
366         if (type == SIMPLE)
367                 return simpleSearch(theMap, keys, search_expr, start, dir,
368                                     caseSensitive);
369
370         return regexSearch(theMap, keys, search_expr, start, dir);
371 }
372
373
374 string const parseBibTeX(string data, string const & findkey)
375 {
376         string keyvalue;
377         // at first we delete all characters right of '%' and
378         // replace tabs through a space and remove leading spaces
379         // we read the data line by line so that the \n are
380         // ignored, too.
381         string data_;
382         int Entries = 0;
383         string dummy = token(data,'\n', Entries);
384         while (!dummy.empty()) {
385                 dummy = subst(dummy, '\t', ' ');        // no tabs
386                 dummy = ltrim(dummy);           // no leading spaces
387                 // ignore lines with a beginning '%' or ignore all right of %
388                 string::size_type const idx =
389                         dummy.empty() ? string::npos : dummy.find('%');
390                 if (idx != string::npos)
391                         dummy.erase(idx, string::npos);
392                 // do we have a new token or a new line of
393                 // the same one? In the first case we ignore
394                 // the \n and in the second we replace it
395                 // with a space
396                 if (!dummy.empty()) {
397                         if (!contains(dummy, "="))
398                                 data_ += (' ' + dummy);
399                         else
400                                 data_ += dummy;
401                 }
402                 dummy = token(data, '\n', ++Entries);
403         }
404
405         // replace double commas with "" for easy scanning
406         data = subst(data_, ",,", "\"\"");
407
408         // unlikely!
409         if (data.empty())
410                 return string();
411
412         // now get only the important line of the bibtex entry.
413         // all entries are devided by ',' except the last one.
414         data += ',';  // now we have same behaviour for all entries
415                       // because the last one is "blah ... }"
416         Entries = 0;
417         bool found = false;
418         // parsing of title and booktitle is different from the
419         // others, because booktitle contains title
420         do {
421                 dummy = token(data, ',', Entries++);
422                 if (!dummy.empty()) {
423                         found = contains(ascii_lowercase(dummy), findkey);
424                         if (findkey == "title" &&
425                                 contains(ascii_lowercase(dummy), "booktitle"))
426                                 found = false;
427                 }
428         } while (!found && !dummy.empty());
429         if (dummy.empty())
430                 // no such keyword
431                 return string();
432
433         // we are not sure, if we get all, because "key= "blah, blah" is
434         // allowed.
435         // Therefore we read all until the next "=" character, which follows a
436         // new keyword
437         keyvalue = dummy;
438         dummy = token(data, ',', Entries++);
439         while (!contains(dummy, '=') && !dummy.empty()) {
440                 keyvalue += (',' + dummy);
441                 dummy = token(data, ',', Entries++);
442         }
443
444         // replace double "" with originals ,, (two commas)
445         // leaving us with the all-important line
446         data = subst(keyvalue, "\"\"", ",,");
447
448         // Clean-up.
449         // 1. Spaces
450         data = rtrim(data);
451         // 2. if there is no opening '{' then a closing '{' is probably cruft.
452         if (!contains(data, '{'))
453                 data = rtrim(data, "}");
454         // happens, when last keyword
455         string::size_type const idx =
456                 !data.empty() ? data.find('=') : string::npos;
457
458         if (idx == string::npos)
459                 return string();
460
461         data = trim(data.substr(idx));
462
463         if (data.length() < 2 || data[0] != '=') {      // a valid entry?
464                 return string();
465         } else {
466                 // delete '=' and the following spaces
467                 data = ltrim(data, " =");
468                 if (data.length() < 2) {
469                         return data;    // not long enough to find delimiters
470                 } else {
471                         string::size_type keypos = 1;
472                         char enclosing;
473                         if (data[0] == '{') {
474                                 enclosing = '}';
475                         } else if (data[0] == '"') {
476                                 enclosing = '"';
477                         } else {
478                                 // no {} and no "", pure data but with a
479                                 // possible ',' at the end
480                                 return rtrim(data, ",");
481                         }
482                         string tmp = data.substr(keypos);
483                         while (tmp.find('{') != string::npos &&
484                                tmp.find('}') != string::npos &&
485                                tmp.find('{') < tmp.find('}') &&
486                                tmp.find('{') < tmp.find(enclosing)) {
487
488                                 keypos += tmp.find('{') + 1;
489                                 tmp = data.substr(keypos);
490                                 keypos += tmp.find('}') + 1;
491                                 tmp = data.substr(keypos);
492                         }
493                         if (tmp.find(enclosing) == string::npos)
494                                 return data;
495                         else {
496                                 keypos += tmp.find(enclosing);
497                                 return data.substr(1, keypos - 1);
498                         }
499                 }
500         }
501 }
502
503
504 CitationStyle const getCitationStyle(string const & command)
505 {
506         if (command.empty()) return CitationStyle();
507
508         CitationStyle cs;
509         string cmd = command;
510
511         if (cmd[0] == 'C') {
512                 cs.forceUCase = true;
513                 cmd[0] = 'c';
514         }
515
516         size_t n = cmd.size() - 1;
517         if (cmd[n] == '*') {
518                 cs.full = true;
519                 cmd = cmd.substr(0,n);
520         }
521
522         char const * const * const last = citeCommands + nCiteCommands;
523         char const * const * const ptr = std::find(citeCommands, last, cmd);
524
525         if (ptr != last) {
526                 size_t idx = ptr - citeCommands;
527                 cs.style = citeStyles[idx];
528         }
529
530         return cs;
531 }
532
533
534 string const getCiteCommand(CiteStyle command, bool full, bool forceUCase)
535 {
536         string cite = citeCommands[command];
537         if (full) {
538                 CiteStyle const * last = citeStylesFull + nCiteStylesFull;
539                 if (std::find(citeStylesFull, last, command) != last)
540                         cite += "*";
541         }
542
543         if (forceUCase) {
544                 CiteStyle const * last = citeStylesUCase + nCiteStylesUCase;
545                 if (std::find(citeStylesUCase, last, command) != last)
546                         cite[0] = 'C';
547         }
548
549         return cite;
550 }
551
552
553 vector<CiteStyle> const getCiteStyles(bool usingNatbib)
554 {
555         unsigned int nStyles = 1;
556         unsigned int start = 0;
557         if (usingNatbib) {
558                 nStyles = nCiteStyles - 1;
559                 start = 1;
560         }
561
562         vector<CiteStyle> styles(nStyles);
563
564         vector<CiteStyle>::size_type i = 0;
565         int j = start;
566         for (; i != styles.size(); ++i, ++j) {
567                 styles[i] = citeStyles[j];
568         }
569
570         return styles;
571 }
572
573
574 vector<string> const
575 getNumericalStrings(string const & key,
576                     InfoMap const & map, vector<CiteStyle> const & styles)
577 {
578         if (map.empty()) {
579                 return vector<string>();
580         }
581
582         string const author = getAbbreviatedAuthor(map, key);
583         string const year   = getYear(map, key);
584         if (author.empty() || year.empty())
585                 return vector<string>();
586
587         vector<string> vec(styles.size());
588         for (vector<string>::size_type i = 0; i != vec.size(); ++i) {
589                 string str;
590
591                 switch (styles[i]) {
592                 case CITE:
593                 case CITEP:
594                         str = "[#ID]";
595                         break;
596
597                 case CITET:
598                         str = author + " [#ID]";
599                         break;
600
601                 case CITEALT:
602                         str = author + " #ID";
603                         break;
604
605                 case CITEALP:
606                         str = "#ID";
607                         break;
608
609                 case CITEAUTHOR:
610                         str = author;
611                         break;
612
613                 case CITEYEAR:
614                         str = year;
615                         break;
616
617                 case CITEYEARPAR:
618                         str = "(" + year + ")";
619                         break;
620                 }
621
622                 vec[i] = str;
623         }
624
625         return vec;
626 }
627
628
629 vector<string> const
630 getAuthorYearStrings(string const & key,
631                     InfoMap const & map, vector<CiteStyle> const & styles)
632 {
633         if (map.empty()) {
634                 return vector<string>();
635         }
636
637         string const author = getAbbreviatedAuthor(map, key);
638         string const year   = getYear(map, key);
639         if (author.empty() || year.empty())
640                 return vector<string>();
641
642         vector<string> vec(styles.size());
643         for (vector<string>::size_type i = 0; i != vec.size(); ++i) {
644                 string str;
645
646                 switch (styles[i]) {
647                 case CITE:
648                 case CITET:
649                         str = author + " (" + year + ")";
650                         break;
651
652                 case CITEP:
653                         str = "(" + author + ", " + year + ")";
654                         break;
655
656                 case CITEALT:
657                         str = author + " " + year ;
658                         break;
659
660                 case CITEALP:
661                         str = author + ", " + year ;
662                         break;
663
664                 case CITEAUTHOR:
665                         str = author;
666                         break;
667
668                 case CITEYEAR:
669                         str = year;
670                         break;
671
672                 case CITEYEARPAR:
673                         str = "(" + year + ")";
674                         break;
675                 }
676
677                 vec[i] = str;
678         }
679
680         return vec;
681 }
682
683 } // namespace biblio