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