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