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