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