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