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