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