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