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