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