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