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