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