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