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