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