]> git.lyx.org Git - lyx.git/blob - src/frontends/controllers/biblio.C
hopefully fix tex2lyx linking.
[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                 // FIXME UNICODE
223                 return lyx::to_utf8(bformat(_("%1$s and %2$s"),
224                         lyx::from_utf8(familyName(authors[0])), lyx::from_utf8(familyName(authors[1]))));
225
226         if (authors.size() > 2)
227                 // FIXME UNICODE
228                 return lyx::to_utf8(bformat(_("%1$s et al."), lyx::from_utf8(familyName(authors[0]))));
229
230         return familyName(authors[0]);
231 }
232
233
234 string const getYear(InfoMap const & map, string const & key)
235 {
236         BOOST_ASSERT(!map.empty());
237
238         InfoMap::const_iterator it = map.find(key);
239         if (it == map.end())
240                 return string();
241         string const & data = it->second;
242
243         // Is the entry a BibTeX one or one from lyx-layout "bibliography"?
244         string::size_type const pos = data.find("TheBibliographyRef");
245         if (pos != string::npos) {
246                 if (pos <= 2) {
247                         return string();
248                 }
249
250                 string const opt =
251                         trim(data.substr(0, pos - 1));
252                 if (opt.empty())
253                         return string();
254
255                 string authors;
256                 string const tmp = split(opt, authors, '(');
257                 string year;
258                 split(tmp, year, ')');
259                 return year;
260
261         }
262
263         string year = parseBibTeX(data, "year");
264         if (year.empty())
265                 year = lyx::to_utf8(_("No year"));
266
267         return year;
268 }
269
270
271 namespace {
272
273 // A functor for use with std::sort, leading to case insensitive sorting
274 class compareNoCase: public std::binary_function<string, string, bool>
275 {
276 public:
277         bool operator()(string const & s1, string const & s2) const {
278                 return compare_ascii_no_case(s1, s2) < 0;
279         }
280 };
281
282 } // namespace anon
283
284
285 vector<string> const getKeys(InfoMap const & map)
286 {
287         vector<string> bibkeys;
288         InfoMap::const_iterator it  = map.begin();
289         InfoMap::const_iterator end = map.end();
290         for (; it != end; ++it) {
291                 bibkeys.push_back(it->first);
292         }
293
294         std::sort(bibkeys.begin(), bibkeys.end(), compareNoCase());
295         return bibkeys;
296 }
297
298
299 string const getInfo(InfoMap const & map, string const & key)
300 {
301         BOOST_ASSERT(!map.empty());
302
303         InfoMap::const_iterator it = map.find(key);
304         if (it == map.end())
305                 return string();
306         string const & data = it->second;
307
308         // is the entry a BibTeX one or one from lyx-layout "bibliography"?
309         string const separator("TheBibliographyRef");
310         string::size_type const pos = data.find(separator);
311         if (pos != string::npos) {
312                 string::size_type const pos2 = pos + separator.size();
313                 string const info = trim(data.substr(pos2));
314                 return info;
315         }
316
317         // Search for all possible "required" keys
318         string author = parseBibTeX(data, "author");
319         if (author.empty())
320                 author = parseBibTeX(data, "editor");
321
322         string year       = parseBibTeX(data, "year");
323         string title      = parseBibTeX(data, "title");
324         string booktitle  = parseBibTeX(data, "booktitle");
325         string chapter    = parseBibTeX(data, "chapter");
326         string number     = parseBibTeX(data, "number");
327         string volume     = parseBibTeX(data, "volume");
328         string pages      = parseBibTeX(data, "pages");
329         string annote     = parseBibTeX(data, "annote");
330
331         string media      = parseBibTeX(data, "journal");
332         if (media.empty())
333                 media = parseBibTeX(data, "publisher");
334         if (media.empty())
335                 media = parseBibTeX(data, "school");
336         if (media.empty())
337                 media = parseBibTeX(data, "institution");
338
339         ostringstream result;
340         if (!author.empty())
341                 result << author << ", ";
342         if (!title.empty())
343                 result << title;
344         if (!booktitle.empty())
345                 result << ", in " << booktitle;
346         if (!chapter.empty())
347                 result << ", Ch. " << chapter;
348         if (!media.empty())
349                 result << ", " << media;
350         if (!volume.empty())
351                 result << ", vol. " << volume;
352         if (!number.empty())
353                 result << ", no. " << number;
354         if (!pages.empty())
355                 result << ", pp. " << pages;
356         if (!year.empty())
357                 result << ", " << year;
358         if (!annote.empty())
359                 result << "\n\n" << annote;
360
361         string const result_str = rtrim(result.str());
362         if (!result_str.empty())
363                 return result_str;
364
365         // This should never happen (or at least be very unusual!)
366         return data;
367 }
368
369
370 namespace {
371
372 // Escape special chars.
373 // All characters are literals except: '.|*?+(){}[]^$\'
374 // These characters are literals when preceded by a "\", which is done here
375 // @todo: This function should be moved to support, and then the test in tests
376 //        should be moved there as well.
377 string const escape_special_chars(string const & expr)
378 {
379         // Search for all chars '.|*?+(){}[^$]\'
380         // Note that '[' and '\' must be escaped.
381         // This is a limitation of boost::regex, but all other chars in BREs
382         // are assumed literal.
383         boost::regex reg("[].|*?+(){}^$\\[\\\\]");
384
385         // $& is a perl-like expression that expands to all
386         // of the current match
387         // The '$' must be prefixed with the escape character '\' for
388         // boost to treat it as a literal.
389         // Thus, to prefix a matched expression with '\', we use:
390         return boost::regex_replace(expr, reg, "\\\\$&");
391 }
392
393
394 // A functor for use with std::find_if, used to ascertain whether a
395 // data entry matches the required regex_
396 // @throws: boost::regex_error if the supplied regex pattern is not valid
397 // @todo: This function should be moved to support.
398 class RegexMatch : public std::unary_function<string, bool>
399 {
400 public:
401         // re and icase are used to construct an instance of boost::RegEx.
402         // if icase is true, then matching is insensitive to case
403         RegexMatch(InfoMap const & m, string const & re, bool icase)
404                 : map_(m), regex_(re, icase) {}
405
406         bool operator()(string const & key) const {
407                 // the data searched is the key + its associated BibTeX/biblio
408                 // fields
409                 string data = key;
410                 InfoMap::const_iterator info = map_.find(key);
411                 if (info != map_.end())
412                         data += ' ' + info->second;
413
414                 // Attempts to find a match for the current RE
415                 // somewhere in data.
416                 return boost::regex_search(data, regex_);
417         }
418 private:
419         InfoMap const map_;
420         mutable boost::regex regex_;
421 };
422
423 } // namespace anon
424
425
426 vector<string>::const_iterator
427 searchKeys(InfoMap const & theMap,
428            vector<string> const & keys,
429            string const & search_expr,
430            vector<string>::const_iterator start,
431            Search type,
432            Direction dir,
433            bool caseSensitive)
434 {
435         // Preliminary checks
436         if (start < keys.begin() || start >= keys.end())
437                 return keys.end();
438
439         string expr = trim(search_expr);
440         if (expr.empty())
441                 return keys.end();
442
443         if (type == SIMPLE)
444                 // We must escape special chars in the search_expr so that
445                 // it is treated as a simple string by boost::regex.
446                 expr = escape_special_chars(expr);
447
448         try {
449                 // Build the functor that will be passed to find_if.
450                 RegexMatch const match(theMap, expr, !caseSensitive);
451
452                 // Search the vector of 'keys' from 'start' for one
453                 // that matches the predicate 'match'. Searching can
454                 // be forward or backward from start.
455                 if (dir == FORWARD)
456                         return std::find_if(start, keys.end(), match);
457
458                 vector<string>::const_reverse_iterator rit(start);
459                 vector<string>::const_reverse_iterator rend = keys.rend();
460                 rit = std::find_if(rit, rend, match);
461
462                 if (rit == rend)
463                         return keys.end();
464                 // This is correct and always safe.
465                 // (See Meyer's Effective STL, Item 28.)
466                 return (++rit).base();
467         }
468         catch (boost::regex_error &) {
469                 return keys.end();
470         }
471 }
472
473
474 string const parseBibTeX(string data, string const & findkey)
475 {
476         string keyvalue;
477         // at first we delete all characters right of '%' and
478         // replace tabs through a space and remove leading spaces
479         // we read the data line by line so that the \n are
480         // ignored, too.
481         string data_;
482         int Entries = 0;
483         string dummy = token(data,'\n', Entries);
484         while (!dummy.empty()) {
485                 dummy = subst(dummy, '\t', ' ');        // no tabs
486                 dummy = ltrim(dummy);           // no leading spaces
487                 // ignore lines with a beginning '%' or ignore all right of %
488                 string::size_type const idx =
489                         dummy.empty() ? string::npos : dummy.find('%');
490                 if (idx != string::npos)
491                         // Check if this is really a comment or just "\%"
492                         if (idx == 0 || dummy[idx - 1] != '\\')
493                                 dummy.erase(idx, string::npos);
494                         else  //  This is "\%", so just erase the '\'
495                                 dummy.erase(idx - 1, 1);
496                 // do we have a new token or a new line of
497                 // the same one? In the first case we ignore
498                 // the \n and in the second we replace it
499                 // with a space
500                 if (!dummy.empty()) {
501                         if (!contains(dummy, '='))
502                                 data_ += ' ' + dummy;
503                         else
504                                 data_ += dummy;
505                 }
506                 dummy = token(data, '\n', ++Entries);
507         }
508
509         // replace double commas with "" for easy scanning
510         data = subst(data_, ",,", "\"\"");
511
512         // unlikely!
513         if (data.empty())
514                 return string();
515
516         // now get only the important line of the bibtex entry.
517         // all entries are devided by ',' except the last one.
518         data += ',';  // now we have same behaviour for all entries
519                       // because the last one is "blah ... }"
520         Entries = 0;
521         bool found = false;
522         // parsing of title and booktitle is different from the
523         // others, because booktitle contains title
524         do {
525                 dummy = token(data, ',', Entries++);
526                 if (!dummy.empty()) {
527                         found = contains(ascii_lowercase(dummy), findkey);
528                         if (findkey == "title" &&
529                                 contains(ascii_lowercase(dummy), "booktitle"))
530                                 found = false;
531                 }
532         } while (!found && !dummy.empty());
533         if (dummy.empty())
534                 // no such keyword
535                 return string();
536
537         // we are not sure, if we get all, because "key= "blah, blah" is
538         // allowed.
539         // Therefore we read all until the next "=" character, which follows a
540         // new keyword
541         keyvalue = dummy;
542         dummy = token(data, ',', Entries++);
543         while (!contains(dummy, '=') && !dummy.empty()) {
544                 keyvalue += ',' + dummy;
545                 dummy = token(data, ',', Entries++);
546         }
547
548         // replace double "" with originals ,, (two commas)
549         // leaving us with the all-important line
550         data = subst(keyvalue, "\"\"", ",,");
551
552         // Clean-up.
553         // 1. Spaces
554         data = rtrim(data);
555         // 2. if there is no opening '{' then a closing '{' is probably cruft.
556         if (!contains(data, '{'))
557                 data = rtrim(data, "}");
558         // happens, when last keyword
559         string::size_type const idx =
560                 !data.empty() ? data.find('=') : string::npos;
561
562         if (idx == string::npos)
563                 return string();
564
565         data = trim(data.substr(idx));
566
567         if (data.length() < 2 || data[0] != '=') {      // a valid entry?
568                 return string();
569         } else {
570                 // delete '=' and the following spaces
571                 data = ltrim(data, " =");
572                 if (data.length() < 2) {
573                         return data;    // not long enough to find delimiters
574                 } else {
575                         string::size_type keypos = 1;
576                         char enclosing;
577                         if (data[0] == '{') {
578                                 enclosing = '}';
579                         } else if (data[0] == '"') {
580                                 enclosing = '"';
581                         } else {
582                                 // no {} and no "", pure data but with a
583                                 // possible ',' at the end
584                                 return rtrim(data, ",");
585                         }
586                         string tmp = data.substr(keypos);
587                         while (tmp.find('{') != string::npos &&
588                                tmp.find('}') != string::npos &&
589                                tmp.find('{') < tmp.find('}') &&
590                                tmp.find('{') < tmp.find(enclosing)) {
591
592                                 keypos += tmp.find('{') + 1;
593                                 tmp = data.substr(keypos);
594                                 keypos += tmp.find('}') + 1;
595                                 tmp = data.substr(keypos);
596                         }
597                         if (tmp.find(enclosing) == string::npos)
598                                 return data;
599                         else {
600                                 keypos += tmp.find(enclosing);
601                                 return data.substr(1, keypos - 1);
602                         }
603                 }
604         }
605 }
606
607
608 namespace {
609
610
611 char const * const citeCommands[] = {
612         "cite", "citet", "citep", "citealt", "citealp", "citeauthor",
613         "citeyear", "citeyearpar" };
614
615 unsigned int const nCiteCommands =
616         sizeof(citeCommands) / sizeof(char *);
617
618 CiteStyle const citeStyles[] = {
619         CITE, CITET, CITEP, CITEALT, CITEALP,
620         CITEAUTHOR, CITEYEAR, CITEYEARPAR };
621
622 unsigned int const nCiteStyles =
623         sizeof(citeStyles) / sizeof(CiteStyle);
624
625 CiteStyle const citeStylesFull[] = {
626         CITET, CITEP, CITEALT, CITEALP, CITEAUTHOR };
627
628 unsigned int const nCiteStylesFull =
629         sizeof(citeStylesFull) / sizeof(CiteStyle);
630
631 CiteStyle const citeStylesUCase[] = {
632         CITET, CITEP, CITEALT, CITEALP, CITEAUTHOR };
633
634 unsigned int const nCiteStylesUCase =
635         sizeof(citeStylesUCase) / sizeof(CiteStyle);
636
637 } // namespace anon
638
639
640 CitationStyle::CitationStyle(string const & command)
641         : style(CITE), full(false), forceUCase(false)
642 {
643         if (command.empty())
644                 return;
645
646         string cmd = command;
647         if (cmd[0] == 'C') {
648                 forceUCase = true;
649                 cmd[0] = 'c';
650         }
651
652         string::size_type const n = cmd.size() - 1;
653         if (cmd != "cite" && cmd[n] == '*') {
654                 full = true;
655                 cmd = cmd.substr(0,n);
656         }
657
658         char const * const * const last = citeCommands + nCiteCommands;
659         char const * const * const ptr = std::find(citeCommands, last, cmd);
660
661         if (ptr != last) {
662                 size_t idx = ptr - citeCommands;
663                 style = citeStyles[idx];
664         }
665 }
666
667
668 string const CitationStyle::asLatexStr() const
669 {
670         string cite = citeCommands[style];
671         if (full) {
672                 CiteStyle const * last = citeStylesFull + nCiteStylesFull;
673                 if (std::find(citeStylesFull, last, style) != last)
674                         cite += '*';
675         }
676
677         if (forceUCase) {
678                 CiteStyle const * last = citeStylesUCase + nCiteStylesUCase;
679                 if (std::find(citeStylesUCase, last, style) != last)
680                         cite[0] = 'C';
681         }
682
683         return cite;
684 }
685
686
687 CiteEngine_enum getEngine(Buffer const & buffer)
688 {
689         return buffer.params().cite_engine;
690 }
691
692
693 vector<CiteStyle> const getCiteStyles(CiteEngine_enum const & engine)
694 {
695         unsigned int nStyles = 0;
696         unsigned int start = 0;
697
698         switch (engine) {
699         case ENGINE_BASIC:
700                 nStyles = 1;
701                 start = 0;
702                 break;
703         case ENGINE_NATBIB_AUTHORYEAR:
704         case ENGINE_NATBIB_NUMERICAL:
705                 nStyles = nCiteStyles - 1;
706                 start = 1;
707                 break;
708         case ENGINE_JURABIB:
709                 nStyles = nCiteStyles;
710                 start = 0;
711                 break;
712         }
713
714         typedef vector<CiteStyle> cite_vec;
715
716         cite_vec styles(nStyles);
717         cite_vec::size_type i = 0;
718         int j = start;
719         for (; i != styles.size(); ++i, ++j)
720                 styles[i] = citeStyles[j];
721
722         return styles;
723 }
724
725
726 vector<string> const
727 getNumericalStrings(string const & key,
728                     InfoMap const & map, vector<CiteStyle> const & styles)
729 {
730         if (map.empty()) {
731                 return vector<string>();
732         }
733
734         string const author = getAbbreviatedAuthor(map, key);
735         string const year   = getYear(map, key);
736         if (author.empty() || year.empty())
737                 return vector<string>();
738
739         vector<string> vec(styles.size());
740         for (vector<string>::size_type i = 0; i != vec.size(); ++i) {
741                 string str;
742
743                 switch (styles[i]) {
744                 case CITE:
745                 case CITEP:
746                         str = "[#ID]";
747                         break;
748
749                 case CITET:
750                         str = author + " [#ID]";
751                         break;
752
753                 case CITEALT:
754                         str = author + " #ID";
755                         break;
756
757                 case CITEALP:
758                         str = "#ID";
759                         break;
760
761                 case CITEAUTHOR:
762                         str = author;
763                         break;
764
765                 case CITEYEAR:
766                         str = year;
767                         break;
768
769                 case CITEYEARPAR:
770                         str = '(' + year + ')';
771                         break;
772                 }
773
774                 vec[i] = str;
775         }
776
777         return vec;
778 }
779
780
781 vector<string> const
782 getAuthorYearStrings(string const & key,
783                     InfoMap const & map, vector<CiteStyle> const & styles)
784 {
785         if (map.empty()) {
786                 return vector<string>();
787         }
788
789         string const author = getAbbreviatedAuthor(map, key);
790         string const year   = getYear(map, key);
791         if (author.empty() || year.empty())
792                 return vector<string>();
793
794         vector<string> vec(styles.size());
795         for (vector<string>::size_type i = 0; i != vec.size(); ++i) {
796                 string str;
797
798                 switch (styles[i]) {
799                 case CITE:
800                         // jurabib only: Author/Annotator
801                         // (i.e. the "before" field, 2nd opt arg)
802                         str = author + "/<" + lyx::to_utf8(_("before")) + '>';
803                         break;
804
805                 case CITET:
806                         str = author + " (" + year + ')';
807                         break;
808
809                 case CITEP:
810                         str = '(' + author + ", " + year + ')';
811                         break;
812
813                 case CITEALT:
814                         str = author + ' ' + year ;
815                         break;
816
817                 case CITEALP:
818                         str = author + ", " + year ;
819                         break;
820
821                 case CITEAUTHOR:
822                         str = author;
823                         break;
824
825                 case CITEYEAR:
826                         str = year;
827                         break;
828
829                 case CITEYEARPAR:
830                         str = '(' + year + ')';
831                         break;
832                 }
833
834                 vec[i] = str;
835         }
836
837         return vec;
838 }
839
840 } // namespace biblio
841 } // namespace lyx