]> git.lyx.org Git - lyx.git/blob - src/frontends/controllers/biblio.C
(Herbert): fix parsing of bibtex entry bug.
[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 #include <vector>
18 #include <algorithm>
19
20 #ifdef __GNUG__
21 #pragma implementation
22 #endif
23
24 #include "LString.h"
25 #include "biblio.h"
26 #include "gettext.h" // for _()
27 #include "helper_funcs.h"
28 #include "support/lstrings.h"
29 #include "support/LAssert.h"
30 #include "support/LRegex.h"
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         LRegex 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 (reg.exec(data).size() > 0)
139                         return it;
140         }
141
142         return keys.end();
143 }
144
145
146 } // namespace anon
147
148
149 string const familyName(string const & name)
150 {
151         // Very simple parser
152         string fname = name;
153
154         // possible authorname combinations are:
155         // "Surname, FirstName"
156         // "Surname, F."
157         // "FirstName Surname"
158         // "F. Surname"
159         string::size_type idx = fname.find(",");
160         if (idx != string::npos)
161                 return frontStrip(fname.substr(0,idx));
162         idx = fname.rfind(".");
163         if (idx != string::npos)
164                 fname = frontStrip(fname.substr(idx+1));
165         // test if we have a LaTeX Space in front
166         if (fname[0] == '\\')
167                 return fname.substr(2);
168
169         return fname;
170 }
171
172
173 string const getAbbreviatedAuthor(InfoMap const & map, string const & key)
174 {
175         lyx::Assert(!map.empty());
176
177         InfoMap::const_iterator it = map.find(key);
178
179         string author;
180         if (it != map.end()) {
181                 author = parseBibTeX(it->second, "author");
182                 if (author.empty())
183                         author = parseBibTeX(it->second, "editor");
184
185                 vector<string> authors = getVectorFromString(author, "and");
186
187                 if (!authors.empty()) {
188                         author.erase();
189
190                         for (vector<string>::iterator it = authors.begin();
191                              it != authors.end(); ++it) {
192                                 *it = familyName(strip(*it));
193                         }
194
195                         author = authors[0];
196                         if (authors.size() == 2)
197                                 author += _(" and ") + authors[1];
198                         else if (authors.size() > 2)
199                                 author += _(" et al.");
200                 }
201         }
202
203         if (author.empty())
204                 author = _("Caesar et al.");
205
206         return author;
207 }
208
209
210 string const getYear(InfoMap const & map, string const & key)
211 {
212         lyx::Assert(!map.empty());
213
214         InfoMap::const_iterator it = map.find(key);
215
216         string year;
217
218         if (it != map.end())
219                 year = parseBibTeX(it->second, "year");
220
221         if (year.empty())
222                 year = "50BC";
223
224         return year;
225 }
226
227
228 // A functor for use with std::sort, leading to case insensitive sorting
229 struct compareNoCase: public std::binary_function<string, string, bool>
230 {
231         bool operator()(string const & s1, string const & s2) const {
232                 return compare_no_case(s1, s2) < 0;
233         }
234 };
235
236 vector<string> const getKeys(InfoMap const & map)
237 {
238         vector<string> bibkeys;
239
240         for (InfoMap::const_iterator it = map.begin(); it != map.end(); ++it) {
241                 bibkeys.push_back(it->first);
242         }
243
244         sort(bibkeys.begin(), bibkeys.end(), compareNoCase());
245         return bibkeys;
246 }
247
248
249 string const getInfo(InfoMap const & map, string const & key)
250 {
251         lyx::Assert(!map.empty());
252
253         InfoMap::const_iterator it = map.find(key);
254         if (it == map.end())
255                 return string();
256         // is the entry a BibTeX one or one from lyx-layout "bibliography"?
257         if (!contains(it->second,'='))
258                 return it->second.c_str();
259
260         // Search for all possible "required" keys
261         string author = parseBibTeX(it->second, "author");
262         if (author.empty())
263                 author = parseBibTeX(it->second, "editor");
264
265         string year       = parseBibTeX(it->second, "year");
266         string title      = parseBibTeX(it->second, "title");
267         string booktitle  = parseBibTeX(it->second, "booktitle");
268         string chapter    = parseBibTeX(it->second, "chapter");
269         string number     = parseBibTeX(it->second, "number");
270         string volume     = parseBibTeX(it->second, "volume");
271         string pages      = parseBibTeX(it->second, "pages");
272
273         string media      = parseBibTeX(it->second, "journal");
274         if (media.empty())
275                 media = parseBibTeX(it->second, "publisher");
276         if (media.empty())
277                 media = parseBibTeX(it->second, "school");
278         if (media.empty())
279                 media = parseBibTeX(it->second, "institution");
280
281         ostringstream result;
282         if (!author.empty())
283                 result << author << ", ";
284         if (!title.empty())
285                 result << title;
286         if (!booktitle.empty())
287                 result << ", in " << booktitle;
288         if (!chapter.empty())
289                 result << ", Ch. " << chapter;
290         if (!media.empty())
291                 result << ", " << media;
292         if (!volume.empty())
293                 result << ", vol. " << volume;
294         if (!number.empty())
295                 result << ", no. " << number;
296         if (!pages.empty())
297                 result << ", pp. " << pages;
298         if (!year.empty())
299                 result << ", " << year;
300
301         string const result_str = strip(result.str().c_str());
302         if (!result_str.empty())
303                 return result_str;
304
305         // This should never happen (or at least be very unusual!)
306         return it->second;
307 }
308
309
310 vector<string>::const_iterator
311 searchKeys(InfoMap const & theMap,
312            vector<string> const & keys,
313            string const & expr,
314            vector<string>::const_iterator start,
315            Search type,
316            Direction dir,
317            bool caseSensitive)
318 {
319         // Preliminary checks
320         if (start < keys.begin() || start >= keys.end())
321                 return keys.end();
322
323         string search_expr = frontStrip(strip(expr));
324         if (search_expr.empty())
325                 return keys.end();
326
327         if (type == SIMPLE)
328                 return simpleSearch(theMap, keys, search_expr, start, dir,
329                                     caseSensitive);
330
331         return regexSearch(theMap, keys, search_expr, start, dir);
332 }
333
334
335 string const parseBibTeX(string data, string const & findkey)
336 {
337         string keyvalue;
338         // at first we delete all characters right of '%' and
339         // replace tabs through a space and remove leading spaces
340         string data_;
341         int Entries = 0;
342         string dummy = token(data,'\n', Entries);
343         while (!dummy.empty()) {
344                 dummy = subst(dummy, '\t', ' ');        // no tabs
345                 dummy = frontStrip(dummy);      // no leading spaces
346                 string::size_type const idx =
347                         dummy.empty() ? string::npos : dummy.find('%');
348                 if (idx != string::npos) {
349                         // ignore lines with a beginning '%'
350                         if (idx > 0) {
351                                 data_ += dummy.substr(0,data.find('%'));
352                         }
353                 } else {
354                         data_ += dummy;
355                 }
356                 dummy = token(data, '\n', ++Entries);
357         }
358         // replace double commas with "" for easy scanning
359         data = subst(data_, ",,", "\"\"");
360
361         // unlikely!
362         if (data.empty())
363                 return string();
364
365         // now get only the important line of the bibtex entry.
366         // all entries are devided by ',' except the last one.
367         data += ',';  // now we have same behaviour for all entries
368                       // because the last one is "blah ... }"
369         Entries = 0;
370         dummy = token(data, ',', Entries);
371         while (!contains(lowercase(dummy), findkey) && !dummy.empty())
372                 dummy = token(data, ',', ++Entries);
373         if (dummy.empty())
374                 // no such keyword
375                 return string();
376
377         // we are not sure, if we get all, because "key= "blah, blah" is
378         // allowed.
379         // Therefore we read all until the next "=" character, which follows a
380         // new keyword
381         keyvalue = dummy;
382         dummy = token(data, ',', ++Entries);
383         while (!contains(dummy, '=') && !dummy.empty()) {
384                 keyvalue += (',' + dummy);
385                 dummy = token(data, ',', ++Entries);
386         }
387
388         // replace double "" with originals ,, (two commas)
389         // leaving us with the all-important line
390         data = subst(keyvalue, "\"\"", ",,");
391
392         // Clean-up.
393         // 1. Spaces
394         data = strip(data, ' ');
395         // 2. if there is no opening '{' then a closing '{' is probably cruft.
396         if (!contains(data, '{'))
397                 data = strip(data, '}');
398         // happens, when last keyword
399         string::size_type const idx =
400                 !data.empty() ? data.find('=') : string::npos;
401
402         if (idx == string::npos)
403                 return string();
404
405         data = data.substr(idx);
406         data = frontStrip(strip(data));
407
408         if (data.length() < 2 || data[0] != '=') {      // a valid entry?
409                 return string();
410         } else {
411                 // delete '=' and the following spaces
412                 data = frontStrip(frontStrip(data,'='));
413                 if (data.length() < 2) {
414                         return data;    // not long enough to find delimiters
415                 } else {
416                         string::size_type keypos = 1;
417                         char enclosing;
418                         if (data[0] == '{') {
419                                 enclosing = '}';
420                         } else if (data[0] == '"') {
421                                 enclosing = '"';
422                         } else {
423                                 // no {} and no "", pure data but with a
424                                 // possible ',' at the end
425                                 return strip(data,',');
426                         }
427                         string tmp = data.substr(keypos);
428                         while (tmp.find('{') != string::npos &&
429                                tmp.find('}') != string::npos &&
430                                tmp.find('{') < tmp.find('}') &&
431                                tmp.find('{') < tmp.find(enclosing)) {
432
433                                 keypos += tmp.find('{') + 1;
434                                 tmp = data.substr(keypos);
435                                 keypos += tmp.find('}') + 1;
436                                 tmp = data.substr(keypos);
437                         }
438                         if (tmp.find(enclosing) == string::npos)
439                                 return data;
440                         else {
441                                 keypos += tmp.find(enclosing);
442                                 return data.substr(1, keypos - 1);
443                         }
444                 }
445         }
446 }
447
448
449 CitationStyle const getCitationStyle(string const & command)
450 {
451         if (command.empty()) return CitationStyle();
452
453         CitationStyle cs;
454         string cmd = command;
455
456         if (cmd[0] == 'C') {
457                 cs.forceUCase = true;
458                 cmd[0] = 'c';
459         }
460
461         size_t n = cmd.size()-1;
462         if (cmd[n] == '*') {
463                 cs.full = true;
464                 cmd = cmd.substr(0,n);
465         }
466
467         char const * const * const last = citeCommands + nCiteCommands;
468         char const * const * const ptr = std::find(citeCommands, last, cmd);
469
470         if (ptr != last) {
471                 size_t idx = ptr - citeCommands;
472                 cs.style = citeStyles[idx];
473         }
474
475         return cs;
476 }
477
478
479 string const getCiteCommand(CiteStyle command, bool full, bool forceUCase)
480 {
481         string cite = citeCommands[command];
482         if (full) {
483                 CiteStyle const * last = citeStylesFull + nCiteStylesFull;
484                 if (std::find(citeStylesFull, last, command) != last)
485                         cite += "*";
486         }
487
488         if (forceUCase) {
489                 CiteStyle const * last = citeStylesUCase + nCiteStylesUCase;
490                 if (std::find(citeStylesUCase, last, command) != last)
491                         cite[0] = 'C';
492         }
493
494         return cite;
495 }
496
497
498 vector<CiteStyle> const getCiteStyles(bool usingNatbib)
499 {
500         unsigned int nStyles = 1;
501         unsigned int start = 0;
502         if (usingNatbib) {
503                 nStyles = nCiteStyles - 1;
504                 start = 1;
505         }
506
507         vector<CiteStyle> styles(nStyles);
508
509         vector<CiteStyle>::size_type i = 0;
510         int j = start;
511         for (; i != styles.size(); ++i, ++j) {
512                 styles[i] = citeStyles[j];
513         }
514
515         return styles;
516 }
517
518
519 vector<string> const
520 getNumericalStrings(string const & key,
521                     InfoMap const & map, vector<CiteStyle> const & styles)
522 {
523         if (map.empty()) {
524                 vector<string> vec(1);
525                 vec[0] = _("No database");
526                 return vec;
527         }
528
529         vector<string> vec(styles.size());
530
531         string const author = getAbbreviatedAuthor(map, key);
532         string const year   = getYear(map, key);
533
534         for (vector<string>::size_type i = 0; i != vec.size(); ++i) {
535                 string str;
536
537                 switch (styles[i]) {
538                 case CITE:
539                 case CITEP:
540                         str = "[#ID]";
541                         break;
542
543                 case CITET:
544                         str = author + " [#ID]";
545                         break;
546
547                 case CITEALT:
548                         str = author + " #ID";
549                         break;
550
551                 case CITEALP:
552                         str = "#ID";
553                         break;
554
555                 case CITEAUTHOR:
556                         str = author;
557                         break;
558
559                 case CITEYEAR:
560                         str = year;
561                         break;
562
563                 case CITEYEARPAR:
564                         str = "(" + year + ")";
565                         break;
566                 }
567
568                 vec[i] = str;
569         }
570
571         return vec;
572 }
573
574
575 vector<string> const
576 getAuthorYearStrings(string const & key,
577                     InfoMap const & map, vector<CiteStyle> const & styles)
578 {
579         if (map.empty()) {
580                 vector<string> vec(1);
581                 vec[0] = _("No database");
582                 return vec;
583         }
584
585         vector<string> vec(styles.size());
586
587         string const author = getAbbreviatedAuthor(map, key);
588         string const year   = getYear(map, key);
589
590         for (vector<string>::size_type i = 0; i != vec.size(); ++i) {
591                 string str;
592
593                 switch (styles[i]) {
594                 case CITET:
595                         str = author + " (" + year + ")";
596                         break;
597
598                 case CITE:
599                 case CITEP:
600                         str = "(" + author + ", " + year + ")";
601                         break;
602
603                 case CITEALT:
604                         str = author + " " + year ;
605                         break;
606
607                 case CITEALP:
608                         str = author + ", " + year ;
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 } // namespace biblio