]> git.lyx.org Git - lyx.git/blob - src/BiblioInfo.cpp
Merge branch 'master' of git.lyx.org:lyx
[lyx.git] / src / BiblioInfo.cpp
1 /**
2  * \file BiblioInfo.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Angus Leeming
7  * \author Herbert Voß
8  * \author Richard Heck
9  * \author Julien Rioux
10  *
11  * Full author contact details are available in file CREDITS.
12  */
13
14 #include <config.h>
15
16 #include "BiblioInfo.h"
17 #include "Buffer.h"
18 #include "BufferParams.h"
19 #include "buffer_funcs.h"
20 #include "Encoding.h"
21 #include "InsetIterator.h"
22 #include "Language.h"
23 #include "output_xhtml.h"
24 #include "Paragraph.h"
25 #include "TextClass.h"
26 #include "TocBackend.h"
27
28 #include "support/convert.h"
29 #include "support/debug.h"
30 #include "support/docstream.h"
31 #include "support/gettext.h"
32 #include "support/lassert.h"
33 #include "support/lstrings.h"
34 #include "support/regex.h"
35 #include "support/textutils.h"
36
37 #include <set>
38
39 using namespace std;
40 using namespace lyx::support;
41
42
43 namespace lyx {
44
45 namespace {
46
47 // gets the "family name" from an author-type string
48 docstring familyName(docstring const & name)
49 {
50         if (name.empty())
51                 return docstring();
52
53         // first we look for a comma, and take the last name to be everything
54         // preceding the right-most one, so that we also get the "jr" part.
55         docstring::size_type idx = name.rfind(',');
56         if (idx != docstring::npos)
57                 return ltrim(name.substr(0, idx));
58
59         // OK, so now we want to look for the last name. We're going to
60         // include the "von" part. This isn't perfect.
61         // Split on spaces, to get various tokens.
62         vector<docstring> pieces = getVectorFromString(name, from_ascii(" "));
63         // If we only get two, assume the last one is the last name
64         if (pieces.size() <= 2)
65                 return pieces.back();
66
67         // Now we look for the first token that begins with a lower case letter.
68         vector<docstring>::const_iterator it = pieces.begin();
69         vector<docstring>::const_iterator en = pieces.end();
70         for (; it != en; ++it) {
71                 if ((*it).empty())
72                         continue;
73                 char_type const c = (*it)[0];
74                 if (isLower(c))
75                         break;
76         }
77
78         if (it == en) // we never found a "von"
79                 return pieces.back();
80
81         // reconstruct what we need to return
82         docstring retval;
83         bool first = true;
84         for (; it != en; ++it) {
85                 if (!first)
86                         retval += " ";
87                 else
88                         first = false;
89                 retval += *it;
90         }
91         return retval;
92 }
93
94
95 // converts a string containing LaTeX commands into unicode
96 // for display.
97 docstring convertLaTeXCommands(docstring const & str)
98 {
99         docstring val = str;
100         docstring ret;
101
102         bool scanning_cmd = false;
103         bool scanning_math = false;
104         bool escaped = false; // used to catch \$, etc.
105         while (!val.empty()) {
106                 char_type const ch = val[0];
107
108                 // if we're scanning math, we output everything until we
109                 // find an unescaped $, at which point we break out.
110                 if (scanning_math) {
111                         if (escaped)
112                                 escaped = false;
113                         else if (ch == '\\')
114                                 escaped = true;
115                         else if (ch == '$')
116                                 scanning_math = false;
117                         ret += ch;
118                         val = val.substr(1);
119                         continue;
120                 }
121
122                 // if we're scanning a command name, then we just
123                 // discard characters until we hit something that
124                 // isn't alpha.
125                 if (scanning_cmd) {
126                         if (isAlphaASCII(ch)) {
127                                 val = val.substr(1);
128                                 escaped = false;
129                                 continue;
130                         }
131                         // so we're done with this command.
132                         // now we fall through and check this character.
133                         scanning_cmd = false;
134                 }
135
136                 // was the last character a \? If so, then this is something like:
137                 // \\ or \$, so we'll just output it. That's probably not always right...
138                 if (escaped) {
139                         // exception: output \, as THIN SPACE
140                         if (ch == ',')
141                                 ret.push_back(0x2009);
142                         else
143                                 ret += ch;
144                         val = val.substr(1);
145                         escaped = false;
146                         continue;
147                 }
148
149                 if (ch == '$') {
150                         ret += ch;
151                         val = val.substr(1);
152                         scanning_math = true;
153                         continue;
154                 }
155
156                 // we just ignore braces
157                 if (ch == '{' || ch == '}') {
158                         val = val.substr(1);
159                         continue;
160                 }
161
162                 // we're going to check things that look like commands, so if
163                 // this doesn't, just output it.
164                 if (ch != '\\') {
165                         ret += ch;
166                         val = val.substr(1);
167                         continue;
168                 }
169
170                 // ok, could be a command of some sort
171                 // let's see if it corresponds to some unicode
172                 // unicodesymbols has things in the form: \"{u},
173                 // whereas we may see things like: \"u. So we'll
174                 // look for that and change it, if necessary.
175                 // FIXME: This is a sort of mini-tex2lyx.
176                 //        Use the real tex2lyx instead!
177                 static lyx::regex const reg("^\\\\\\W\\w");
178                 if (lyx::regex_search(to_utf8(val), reg)) {
179                         val.insert(3, from_ascii("}"));
180                         val.insert(2, from_ascii("{"));
181                 }
182                 bool termination;
183                 docstring rem;
184                 docstring const cnvtd = Encodings::fromLaTeXCommand(val,
185                                 Encodings::TEXT_CMD, termination, rem);
186                 if (!cnvtd.empty()) {
187                         // it did, so we'll take that bit and proceed with what's left
188                         ret += cnvtd;
189                         val = rem;
190                         continue;
191                 }
192                 // it's a command of some sort
193                 scanning_cmd = true;
194                 escaped = true;
195                 val = val.substr(1);
196         }
197         return ret;
198 }
199
200
201 // Escape '<' and '>' and remove richtext markers (e.g. {!this is richtext!}) from a string.
202 docstring processRichtext(docstring const & str, bool richtext)
203 {
204         docstring val = str;
205         docstring ret;
206
207         bool scanning_rich = false;
208         while (!val.empty()) {
209                 char_type const ch = val[0];
210                 if (ch == '{' && val.size() > 1 && val[1] == '!') {
211                         // beginning of rich text
212                         scanning_rich = true;
213                         val = val.substr(2);
214                         continue;
215                 }
216                 if (scanning_rich && ch == '!' && val.size() > 1 && val[1] == '}') {
217                         // end of rich text
218                         scanning_rich = false;
219                         val = val.substr(2);
220                         continue;
221                 }
222                 if (richtext) {
223                         if (scanning_rich)
224                                 ret += ch;
225                         else {
226                                 // we need to escape '<' and '>'
227                                 if (ch == '<')
228                                         ret += "&lt;";
229                                 else if (ch == '>')
230                                         ret += "&gt;";
231                                 else
232                                         ret += ch;
233                         }
234                 } else if (!scanning_rich /* && !richtext */)
235                         ret += ch;
236                 // else the character is discarded, which will happen only if
237                 // richtext == false and we are scanning rich text
238                 val = val.substr(1);
239         }
240         return ret;
241 }
242
243 } // anon namespace
244
245
246 //////////////////////////////////////////////////////////////////////
247 //
248 // BibTeXInfo
249 //
250 //////////////////////////////////////////////////////////////////////
251
252 BibTeXInfo::BibTeXInfo(docstring const & key, docstring const & type)
253         : is_bibtex_(true), bib_key_(key), entry_type_(type), info_(),
254           modifier_(0)
255 {}
256
257
258 docstring const BibTeXInfo::getAbbreviatedAuthor(bool jurabib_style) const
259 {
260         if (!is_bibtex_) {
261                 docstring const opt = label();
262                 if (opt.empty())
263                         return docstring();
264
265                 docstring authors;
266                 docstring const remainder = trim(split(opt, authors, '('));
267                 if (remainder.empty())
268                         // in this case, we didn't find a "(",
269                         // so we don't have author (year)
270                         return docstring();
271                 return authors;
272         }
273
274         docstring author = operator[]("author");
275         if (author.empty()) {
276                 author = operator[]("editor");
277                 if (author.empty())
278                         return author;
279         }
280
281         // FIXME Move this to a separate routine that can
282         // be called from elsewhere.
283         //
284         // OK, we've got some names. Let's format them.
285         // Try to split the author list on " and "
286         vector<docstring> const authors =
287                 getVectorFromString(author, from_ascii(" and "));
288
289         if (jurabib_style && (authors.size() == 2 || authors.size() == 3)) {
290                 docstring shortauthor = familyName(authors[0])
291                         + "/" + familyName(authors[1]);
292                 if (authors.size() == 3)
293                         shortauthor += "/" + familyName(authors[2]);
294                 return convertLaTeXCommands(shortauthor);
295         }
296
297         docstring retval = familyName(authors[0]);
298
299         if (authors.size() == 2 && authors[1] != "others")
300                 retval = bformat(from_ascii("%1$s and %2$s"),
301                         familyName(authors[0]), familyName(authors[1]));
302
303         if (authors.size() >= 2)
304                 retval = bformat(from_ascii("%1$s et al."),
305                         familyName(authors[0]));
306
307         return convertLaTeXCommands(retval);
308 }
309
310
311 docstring const BibTeXInfo::getAbbreviatedAuthor(Buffer const & buf, bool jurabib_style) const
312 {
313         docstring const author = getAbbreviatedAuthor(jurabib_style);
314         if (!is_bibtex_)
315                 return author;
316         vector<docstring> const authors = getVectorFromString(author, from_ascii(" and "));
317         if (authors.size() == 2)
318                 return bformat(buf.B_("%1$s and %2$s"), authors[0], authors[1]);
319         docstring::size_type const idx = author.rfind(from_ascii(" et al."));
320         if (idx != docstring::npos)
321                 return bformat(buf.B_("%1$s et al."), author.substr(0, idx));
322         return author;
323 }
324
325
326 docstring const BibTeXInfo::getYear() const
327 {
328         if (is_bibtex_)
329                 return operator[]("year");
330
331         docstring const opt = label();
332         if (opt.empty())
333                 return docstring();
334
335         docstring authors;
336         docstring tmp = split(opt, authors, '(');
337         if (tmp.empty())
338                 // we don't have author (year)
339                 return docstring();
340         docstring year;
341         tmp = split(tmp, year, ')');
342         return year;
343 }
344
345
346 docstring const BibTeXInfo::getXRef() const
347 {
348         if (!is_bibtex_)
349                 return docstring();
350         return operator[]("crossref");
351 }
352
353
354 namespace {
355
356 docstring parseOptions(docstring const & format, string & optkey,
357                     docstring & ifpart, docstring & elsepart);
358
359 // Calls parseOptions to deal with an embedded option, such as:
360 //   {%number%[[, no.~%number%]]}
361 // which must appear at the start of format. ifelsepart gets the
362 // whole of the option, and we return what's left after the option.
363 // we return format if there is an error.
364 docstring parseEmbeddedOption(docstring const & format, docstring & ifelsepart)
365 {
366         LASSERT(format[0] == '{' && format[1] == '%', return format);
367         string optkey;
368         docstring ifpart;
369         docstring elsepart;
370         docstring const rest = parseOptions(format, optkey, ifpart, elsepart);
371         if (format == rest) { // parse error
372                 LYXERR0("ERROR! Couldn't parse `" << format <<"'.");
373                 return format;
374         }
375         LASSERT(rest.size() <= format.size(),
376                 { ifelsepart = docstring(); return format; });
377         ifelsepart = format.substr(0, format.size() - rest.size());
378         return rest;
379 }
380
381
382 // Gets a "clause" from a format string, where the clause is
383 // delimited by '[[' and ']]'. Returns what is left after the
384 // clause is removed, and returns format if there is an error.
385 docstring getClause(docstring const & format, docstring & clause)
386 {
387         docstring fmt = format;
388         // remove '[['
389         fmt = fmt.substr(2);
390         // we'll remove characters from the front of fmt as we
391         // deal with them
392         while (!fmt.empty()) {
393                 if (fmt[0] == ']' && fmt.size() > 1 && fmt[1] == ']') {
394                         // that's the end
395                         fmt = fmt.substr(2);
396                         break;
397                 }
398                 // check for an embedded option
399                 if (fmt[0] == '{' && fmt.size() > 1 && fmt[1] == '%') {
400                         docstring part;
401                         docstring const rest = parseEmbeddedOption(fmt, part);
402                         if (fmt == rest) {
403                                 LYXERR0("ERROR! Couldn't parse embedded option in `" << format <<"'.");
404                                 return format;
405                         }
406                         clause += part;
407                         fmt = rest;
408                 } else { // it's just a normal character
409                                 clause += fmt[0];
410                                 fmt = fmt.substr(1);
411                 }
412         }
413         return fmt;
414 }
415
416
417 // parse an options string, which must appear at the start of the
418 // format parameter. puts the parsed bits in optkey, ifpart, and
419 // elsepart and returns what's left after the option is removed.
420 // if there's an error, it returns format itself.
421 docstring parseOptions(docstring const & format, string & optkey,
422                     docstring & ifpart, docstring & elsepart)
423 {
424         LASSERT(format[0] == '{' && format[1] == '%', return format);
425         // strip '{%'
426         docstring fmt = format.substr(2);
427         size_t pos = fmt.find('%'); // end of key
428         if (pos == string::npos) {
429                 LYXERR0("Error parsing  `" << format <<"'. Can't find end of key.");
430                 return format;
431         }
432         optkey = to_utf8(fmt.substr(0, pos));
433         fmt = fmt.substr(pos + 1);
434         // [[format]] should be next
435         if (fmt[0] != '[' || fmt[1] != '[') {
436                 LYXERR0("Error parsing  `" << format <<"'. Can't find '[[' after key.");
437                 return format;
438         }
439
440         docstring curfmt = fmt;
441         fmt = getClause(curfmt, ifpart);
442         if (fmt == curfmt) {
443                 LYXERR0("Error parsing  `" << format <<"'. Couldn't get if clause.");
444                 return format;
445         }
446
447         if (fmt[0] == '}') // we're done, no else clause
448                 return fmt.substr(1);
449
450         // else part should follow
451         if (fmt[0] != '[' || fmt[1] != '[') {
452                 LYXERR0("Error parsing  `" << format <<"'. Can't find else clause.");
453                 return format;
454         }
455
456         curfmt = fmt;
457         fmt = getClause(curfmt, elsepart);
458         // we should be done
459         if (fmt == curfmt || fmt[0] != '}') {
460                 LYXERR0("Error parsing  `" << format <<"'. Can't find end of option.");
461                 return format;
462         }
463         return fmt.substr(1);
464 }
465
466
467 } // anon namespace
468
469 /* FIXME
470 Bug #9131 revealed an oddity in how we are generating citation information
471 when more than one key is given. We end up building a longer and longer format 
472 string as we go, which we then have to re-parse, over and over and over again,
473 rather than generating the information for the individual keys and then putting
474 all of that together. We do that to deal with the way separators work, from what
475 I can tell, but it still feels like a hack. Fixing this would require quite a
476 bit of work, however.
477 */
478 docstring BibTeXInfo::expandFormat(docstring const & format,
479                 BibTeXInfo const * const xref, int & counter, Buffer const & buf,
480                 docstring before, docstring after, docstring dialog, bool next) const
481 {
482         // incorrect use of macros could put us in an infinite loop
483         static int const max_passes = 5000;
484         // the use of overly large keys can lead to performance problems, due
485         // to eventual attempts to convert LaTeX macros to unicode. See bug
486         // #8944. This is perhaps not the best solution, but it will have to
487         // do for now.
488         static size_t const max_keysize = 128;
489         odocstringstream ret; // return value
490         string key;
491         bool scanning_key = false;
492         bool scanning_rich = false;
493
494         CiteEngineType const engine_type = buf.params().citeEngineType();
495         docstring fmt = format;
496         // we'll remove characters from the front of fmt as we
497         // deal with them
498         while (!fmt.empty()) {
499                 if (counter > max_passes) {
500                         LYXERR0("Recursion limit reached while parsing `"
501                                 << format << "'.");
502                         return _("ERROR!");
503                 }
504
505                 char_type thischar = fmt[0];
506                 if (thischar == '%') {
507                         // beginning or end of key
508                         if (scanning_key) {
509                                 // end of key
510                                 scanning_key = false;
511                                 // so we replace the key with its value, which may be empty
512                                 if (key[0] == '!') {
513                                         // macro
514                                         string const val =
515                                                 buf.params().documentClass().getCiteMacro(engine_type, key);
516                                         fmt = from_utf8(val) + fmt.substr(1);
517                                         counter += 1;
518                                         continue;
519                                 } else if (key[0] == '_') {
520                                         // a translatable bit
521                                         string const val =
522                                                 buf.params().documentClass().getCiteMacro(engine_type, key);
523                                         docstring const trans =
524                                                 translateIfPossible(from_utf8(val), buf.params().language->code());
525                                         ret << trans;
526                                 } else {
527                                         docstring const val =
528                                                 getValueForKey(key, buf, before, after, dialog, xref, max_keysize);
529                                         if (!scanning_rich)
530                                                 ret << from_ascii("{!<span class=\"bib-" + key + "\">!}");
531                                         ret << val;
532                                         if (!scanning_rich)
533                                                 ret << from_ascii("{!</span>!}");
534                                 }
535                         } else {
536                                 // beginning of key
537                                 key.clear();
538                                 scanning_key = true;
539                         }
540                 }
541                 else if (thischar == '{') {
542                         // beginning of option?
543                         if (scanning_key) {
544                                 LYXERR0("ERROR: Found `{' when scanning key in `" << format << "'.");
545                                 return _("ERROR!");
546                         }
547                         if (fmt.size() > 1) {
548                                 if (fmt[1] == '%') {
549                                         // it is the beginning of an optional format
550                                         string optkey;
551                                         docstring ifpart;
552                                         docstring elsepart;
553                                         docstring const newfmt =
554                                                 parseOptions(fmt, optkey, ifpart, elsepart);
555                                         if (newfmt == fmt) // parse error
556                                                 return _("ERROR!");
557                                         fmt = newfmt;
558                                         docstring const val =
559                                                 getValueForKey(optkey, buf, before, after, dialog, xref);
560                                         if (optkey == "next" && next)
561                                                 ret << ifpart; // without expansion
562                                         else if (!val.empty()) {
563                                                 int newcounter = 0;
564                                                 ret << expandFormat(ifpart, xref, newcounter, buf,
565                                                         before, after, dialog, next);
566                                         } else if (!elsepart.empty()) {
567                                                 int newcounter = 0;
568                                                 ret << expandFormat(elsepart, xref, newcounter, buf,
569                                                         before, after, dialog, next);
570                                         }
571                                         // fmt will have been shortened for us already
572                                         continue;
573                                 }
574                                 if (fmt[1] == '!') {
575                                         // beginning of rich text
576                                         scanning_rich = true;
577                                         fmt = fmt.substr(2);
578                                         ret << from_ascii("{!");
579                                         continue;
580                                 }
581                         }
582                         // we are here if '{' was not followed by % or !.
583                         // So it's just a character.
584                         ret << thischar;
585                 }
586                 else if (scanning_rich && thischar == '!'
587                          && fmt.size() > 1 && fmt[1] == '}') {
588                         // end of rich text
589                         scanning_rich = false;
590                         fmt = fmt.substr(2);
591                         ret << from_ascii("!}");
592                         continue;
593                 }
594                 else if (scanning_key)
595                         key += char(thischar);
596                 else {
597                         try {
598                                 ret.put(thischar);
599                         } catch (EncodingException & /* e */) {
600                                 LYXERR0("Uncodable character '" << docstring(1, thischar) << " in citation label!");
601                         }
602                 }
603                 fmt = fmt.substr(1);
604         } // for loop
605         if (scanning_key) {
606                 LYXERR0("Never found end of key in `" << format << "'!");
607                 return _("ERROR!");
608         }
609         if (scanning_rich) {
610                 LYXERR0("Never found end of rich text in `" << format << "'!");
611                 return _("ERROR!");
612         }
613         return ret.str();
614 }
615
616
617 docstring const & BibTeXInfo::getInfo(BibTeXInfo const * const xref,
618         Buffer const & buf, bool richtext) const
619 {
620         if (!richtext && !info_.empty())
621                 return info_;
622         if (richtext && !info_richtext_.empty())
623                 return info_richtext_;
624
625         if (!is_bibtex_) {
626                 BibTeXInfo::const_iterator it = find(from_ascii("ref"));
627                 info_ = it->second;
628                 return info_;
629         }
630
631         CiteEngineType const engine_type = buf.params().citeEngineType();
632         DocumentClass const & dc = buf.params().documentClass();
633         docstring const & format =
634                 from_utf8(dc.getCiteFormat(engine_type, to_utf8(entry_type_)));
635         int counter = 0;
636         info_ = expandFormat(format, xref, counter, buf,
637                 docstring(), docstring(), docstring(), false);
638
639         if (info_.empty()) {
640                 // this probably shouldn't happen
641                 return info_;
642         }
643
644         if (richtext) {
645                 info_richtext_ = convertLaTeXCommands(processRichtext(info_, true));
646                 return info_richtext_;
647         }
648
649         info_ = convertLaTeXCommands(processRichtext(info_, false));
650         return info_;
651 }
652
653
654 docstring const BibTeXInfo::getLabel(BibTeXInfo const * const xref,
655         Buffer const & buf, docstring const & format, bool richtext,
656         docstring const & before, docstring const & after, 
657         docstring const & dialog, bool next) const
658 {
659         docstring loclabel;
660
661         int counter = 0;
662         loclabel = expandFormat(format, xref, counter, buf,
663                 before, after, dialog, next);
664
665         if (!loclabel.empty() && !next) {
666                 loclabel = processRichtext(loclabel, richtext);
667                 loclabel = convertLaTeXCommands(loclabel);
668         }
669
670         return loclabel;
671 }
672
673
674 docstring const & BibTeXInfo::operator[](docstring const & field) const
675 {
676         BibTeXInfo::const_iterator it = find(field);
677         if (it != end())
678                 return it->second;
679         static docstring const empty_value = docstring();
680         return empty_value;
681 }
682
683
684 docstring const & BibTeXInfo::operator[](string const & field) const
685 {
686         return operator[](from_ascii(field));
687 }
688
689
690 docstring BibTeXInfo::getValueForKey(string const & oldkey, Buffer const & buf,
691         docstring const & before, docstring const & after, docstring const & dialog,
692         BibTeXInfo const * const xref, size_t maxsize) const
693 {
694         // anything less is pointless
695         LASSERT(maxsize >= 16, maxsize = 16);
696         string key = oldkey;
697         bool cleanit = false;
698         if (prefixIs(oldkey, "clean:")) {
699                 key = oldkey.substr(6);
700                 cleanit = true;
701         }
702
703         docstring ret = operator[](key);
704         if (ret.empty() && xref)
705                 ret = (*xref)[key];
706         if (ret.empty()) {
707                 // some special keys
708                 // FIXME: dialog, textbefore and textafter have nothing to do with this
709                 if (key == "dialog")
710                         ret = dialog;
711                 else if (key == "entrytype")
712                         ret = entry_type_;
713                 else if (key == "key")
714                         ret = bib_key_;
715                 else if (key == "label")
716                         ret = label_;
717                 else if (key == "modifier" && modifier_ != 0)
718                         ret = modifier_;
719                 else if (key == "numericallabel")
720                         ret = cite_number_;
721                 else if (key == "abbrvauthor")
722                         // Special key to provide abbreviated author names.
723                         ret = getAbbreviatedAuthor(buf, false);
724                 else if (key == "shortauthor")
725                         // When shortauthor is not defined, jurabib automatically
726                         // provides jurabib-style abbreviated author names. We do
727                         // this as well.
728                         ret = getAbbreviatedAuthor(buf, true);
729                 else if (key == "shorttitle") {
730                         // When shorttitle is not defined, jurabib uses for `article'
731                         // and `periodical' entries the form `journal volume [year]'
732                         // and for other types of entries it uses the `title' field.
733                         if (entry_type_ == "article" || entry_type_ == "periodical")
734                                 ret = operator[]("journal") + " " + operator[]("volume")
735                                         + " [" + operator[]("year") + "]";
736                         else
737                                 ret = operator[]("title");
738                 } else if (key == "bibentry") {
739                         // Special key to provide the full bibliography entry: see getInfo()
740                         CiteEngineType const engine_type = buf.params().citeEngineType();
741                         DocumentClass const & dc = buf.params().documentClass();
742                         docstring const & format =
743                                 from_utf8(dc.getCiteFormat(engine_type, to_utf8(entry_type_)));
744                         int counter = 0;
745                         ret = expandFormat(format, xref, counter, buf,
746                                 docstring(), docstring(), docstring(), false);
747                 } else if (key == "textbefore")
748                         ret = before;
749                 else if (key == "textafter")
750                         ret = after;
751                 else if (key == "year")
752                         ret = getYear();
753         }
754
755         if (cleanit)
756                 ret = html::cleanAttr(ret);
757
758         // make sure it is not too big
759         support::truncateWithEllipsis(ret, maxsize);
760         return ret;
761 }
762
763
764 //////////////////////////////////////////////////////////////////////
765 //
766 // BiblioInfo
767 //
768 //////////////////////////////////////////////////////////////////////
769
770 namespace {
771
772 // A functor for use with sort, leading to case insensitive sorting
773 class compareNoCase: public binary_function<docstring, docstring, bool>
774 {
775 public:
776         bool operator()(docstring const & s1, docstring const & s2) const {
777                 return compare_no_case(s1, s2) < 0;
778         }
779 };
780
781 } // namespace anon
782
783
784 vector<docstring> const BiblioInfo::getKeys() const
785 {
786         vector<docstring> bibkeys;
787         BiblioInfo::const_iterator it  = begin();
788         for (; it != end(); ++it)
789                 bibkeys.push_back(it->first);
790         sort(bibkeys.begin(), bibkeys.end(), compareNoCase());
791         return bibkeys;
792 }
793
794
795 vector<docstring> const BiblioInfo::getFields() const
796 {
797         vector<docstring> bibfields;
798         set<docstring>::const_iterator it = field_names_.begin();
799         set<docstring>::const_iterator end = field_names_.end();
800         for (; it != end; ++it)
801                 bibfields.push_back(*it);
802         sort(bibfields.begin(), bibfields.end());
803         return bibfields;
804 }
805
806
807 vector<docstring> const BiblioInfo::getEntries() const
808 {
809         vector<docstring> bibentries;
810         set<docstring>::const_iterator it = entry_types_.begin();
811         set<docstring>::const_iterator end = entry_types_.end();
812         for (; it != end; ++it)
813                 bibentries.push_back(*it);
814         sort(bibentries.begin(), bibentries.end());
815         return bibentries;
816 }
817
818
819 docstring const BiblioInfo::getAbbreviatedAuthor(docstring const & key, Buffer const & buf) const
820 {
821         BiblioInfo::const_iterator it = find(key);
822         if (it == end())
823                 return docstring();
824         BibTeXInfo const & data = it->second;
825         return data.getAbbreviatedAuthor(buf, false);
826 }
827
828
829 docstring const BiblioInfo::getCiteNumber(docstring const & key) const
830 {
831         BiblioInfo::const_iterator it = find(key);
832         if (it == end())
833                 return docstring();
834         BibTeXInfo const & data = it->second;
835         return data.citeNumber();
836 }
837
838
839 docstring const BiblioInfo::getYear(docstring const & key, bool use_modifier) const
840 {
841         BiblioInfo::const_iterator it = find(key);
842         if (it == end())
843                 return docstring();
844         BibTeXInfo const & data = it->second;
845         docstring year = data.getYear();
846         if (year.empty()) {
847                 // let's try the crossref
848                 docstring const xref = data.getXRef();
849                 if (xref.empty())
850                         // no luck
851                         return docstring();
852                 BiblioInfo::const_iterator const xrefit = find(xref);
853                 if (xrefit == end())
854                         // no luck again
855                         return docstring();
856                 BibTeXInfo const & xref_data = xrefit->second;
857                 year = xref_data.getYear();
858         }
859         if (use_modifier && data.modifier() != 0)
860                 year += data.modifier();
861         return year;
862 }
863
864
865 docstring const BiblioInfo::getYear(docstring const & key, Buffer const & buf, bool use_modifier) const
866 {
867         docstring const year = getYear(key, use_modifier);
868         if (year.empty())
869                 return buf.B_("No year");
870         return year;
871 }
872
873
874 docstring const BiblioInfo::getInfo(docstring const & key,
875         Buffer const & buf, bool richtext) const
876 {
877         BiblioInfo::const_iterator it = find(key);
878         if (it == end())
879                 return docstring(_("Bibliography entry not found!"));
880         BibTeXInfo const & data = it->second;
881         BibTeXInfo const * xrefptr = 0;
882         docstring const xref = data.getXRef();
883         if (!xref.empty()) {
884                 BiblioInfo::const_iterator const xrefit = find(xref);
885                 if (xrefit != end())
886                         xrefptr = &(xrefit->second);
887         }
888         return data.getInfo(xrefptr, buf, richtext);
889 }
890
891
892 docstring const BiblioInfo::getLabel(vector<docstring> keys,
893         Buffer const & buf, string const & style, bool for_xhtml,
894         size_t max_size, docstring const & before, docstring const & after,
895         docstring const & dialog) const
896 {
897         // shorter makes no sense
898         LASSERT(max_size >= 16, max_size = 16);
899
900         // we can't display more than 10 of these, anyway
901         bool const too_many_keys = keys.size() > 10;
902         if (too_many_keys)
903                 keys.resize(10);
904
905         CiteEngineType const engine_type = buf.params().citeEngineType();
906         DocumentClass const & dc = buf.params().documentClass();
907         docstring const & format = from_utf8(dc.getCiteFormat(engine_type, style, "cite"));
908         docstring ret = format;
909         vector<docstring>::const_iterator key = keys.begin();
910         vector<docstring>::const_iterator ken = keys.end();
911         for (; key != ken; ++key) {
912                 BiblioInfo::const_iterator it = find(*key);
913                 BibTeXInfo empty_data;
914                 empty_data.key(*key);
915                 BibTeXInfo & data = empty_data;
916                 BibTeXInfo const * xrefptr = 0;
917                 if (it != end()) {
918                         data = it->second;
919                         docstring const xref = data.getXRef();
920                         if (!xref.empty()) {
921                                 BiblioInfo::const_iterator const xrefit = find(xref);
922                                 if (xrefit != end())
923                                         xrefptr = &(xrefit->second);
924                         }
925                 }
926                 ret = data.getLabel(xrefptr, buf, ret, for_xhtml,
927                         before, after, dialog, key + 1 != ken);
928         }
929
930         if (too_many_keys)
931                 ret.push_back(0x2026);//HORIZONTAL ELLIPSIS
932         support::truncateWithEllipsis(ret, max_size);
933         return ret;
934 }
935
936
937 bool BiblioInfo::isBibtex(docstring const & key) const
938 {
939         docstring key1;
940         split(key, key1, ',');
941         BiblioInfo::const_iterator it = find(key1);
942         if (it == end())
943                 return false;
944         return it->second.isBibTeX();
945 }
946
947
948 vector<docstring> const BiblioInfo::getCiteStrings(
949         vector<docstring> const & keys, vector<CitationStyle> const & styles,
950         Buffer const & buf, docstring const & before,
951         docstring const & after, docstring const & dialog, size_t max_size) const
952 {
953         if (empty())
954                 return vector<docstring>();
955
956         string style;
957         vector<docstring> vec(styles.size());
958         for (size_t i = 0; i != vec.size(); ++i) {
959                 style = styles[i].cmd;
960                 vec[i] = getLabel(keys, buf, style, false, max_size, before, after, dialog);
961         }
962
963         return vec;
964 }
965
966
967 void BiblioInfo::mergeBiblioInfo(BiblioInfo const & info)
968 {
969         bimap_.insert(info.begin(), info.end());
970         field_names_.insert(info.field_names_.begin(), info.field_names_.end());
971         entry_types_.insert(info.entry_types_.begin(), info.entry_types_.end());
972 }
973
974
975 namespace {
976
977 // used in xhtml to sort a list of BibTeXInfo objects
978 bool lSorter(BibTeXInfo const * lhs, BibTeXInfo const * rhs)
979 {
980         docstring const lauth = lhs->getAbbreviatedAuthor();
981         docstring const rauth = rhs->getAbbreviatedAuthor();
982         docstring const lyear = lhs->getYear();
983         docstring const ryear = rhs->getYear();
984         docstring const ltitl = lhs->operator[]("title");
985         docstring const rtitl = rhs->operator[]("title");
986         return  (lauth < rauth)
987                 || (lauth == rauth && lyear < ryear)
988                 || (lauth == rauth && lyear == ryear && ltitl < rtitl);
989 }
990
991 }
992
993
994 void BiblioInfo::collectCitedEntries(Buffer const & buf)
995 {
996         cited_entries_.clear();
997         // We are going to collect all the citation keys used in the document,
998         // getting them from the TOC.
999         // FIXME We may want to collect these differently, in the first case,
1000         // so that we might have them in order of appearance.
1001         set<docstring> citekeys;
1002         shared_ptr<Toc const> toc = buf.tocBackend().toc("citation");
1003         Toc::const_iterator it = toc->begin();
1004         Toc::const_iterator const en = toc->end();
1005         for (; it != en; ++it) {
1006                 if (it->str().empty())
1007                         continue;
1008                 vector<docstring> const keys = getVectorFromString(it->str());
1009                 citekeys.insert(keys.begin(), keys.end());
1010         }
1011         if (citekeys.empty())
1012                 return;
1013
1014         // We have a set of the keys used in this document.
1015         // We will now convert it to a list of the BibTeXInfo objects used in
1016         // this document...
1017         vector<BibTeXInfo const *> bi;
1018         set<docstring>::const_iterator cit = citekeys.begin();
1019         set<docstring>::const_iterator const cen = citekeys.end();
1020         for (; cit != cen; ++cit) {
1021                 BiblioInfo::const_iterator const bt = find(*cit);
1022                 if (bt == end() || !bt->second.isBibTeX())
1023                         continue;
1024                 bi.push_back(&(bt->second));
1025         }
1026         // ...and sort it.
1027         sort(bi.begin(), bi.end(), lSorter);
1028
1029         // Now we can write the sorted keys
1030         vector<BibTeXInfo const *>::const_iterator bit = bi.begin();
1031         vector<BibTeXInfo const *>::const_iterator ben = bi.end();
1032         for (; bit != ben; ++bit)
1033                 cited_entries_.push_back((*bit)->key());
1034 }
1035
1036
1037 void BiblioInfo::makeCitationLabels(Buffer const & buf)
1038 {
1039         collectCitedEntries(buf);
1040         CiteEngineType const engine_type = buf.params().citeEngineType();
1041         bool const numbers = (engine_type & ENGINE_TYPE_NUMERICAL);
1042
1043         int keynumber = 0;
1044         char modifier = 0;
1045         // used to remember the last one we saw
1046         // we'll be comparing entries to see if we need to add
1047         // modifiers, like "1984a"
1048         map<docstring, BibTeXInfo>::iterator last;
1049
1050         vector<docstring>::const_iterator it = cited_entries_.begin();
1051         vector<docstring>::const_iterator const en = cited_entries_.end();
1052         for (; it != en; ++it) {
1053                 map<docstring, BibTeXInfo>::iterator const biit = bimap_.find(*it);
1054                 // this shouldn't happen, but...
1055                 if (biit == bimap_.end())
1056                         // ...fail gracefully, anyway.
1057                         continue;
1058                 BibTeXInfo & entry = biit->second;
1059                 if (numbers) {
1060                         docstring const num = convert<docstring>(++keynumber);
1061                         entry.setCiteNumber(num);
1062                 } else {
1063                         // coverity complains about our derefercing the iterator last,
1064                         // which was not initialized above. but it does get initialized
1065                         // after the first time through the loop, which is the point of
1066                         // the first test.
1067                         // coverity[FORWARD_NULL]
1068                         if (it != cited_entries_.begin()
1069                             && entry.getAbbreviatedAuthor() == last->second.getAbbreviatedAuthor()
1070                             // we access the year via getYear() so as to get it from the xref,
1071                             // if we need to do so
1072                             && getYear(entry.key()) == getYear(last->second.key())) {
1073                                 if (modifier == 0) {
1074                                         // so the last one should have been 'a'
1075                                         last->second.setModifier('a');
1076                                         modifier = 'b';
1077                                 } else if (modifier == 'z')
1078                                         modifier = 'A';
1079                                 else
1080                                         modifier++;
1081                         } else {
1082                                 modifier = 0;
1083                         }
1084                         entry.setModifier(modifier);
1085                         // remember the last one
1086                         last = biit;
1087                 }
1088         }
1089         // Set the labels
1090         it = cited_entries_.begin();
1091         for (; it != en; ++it) {
1092                 map<docstring, BibTeXInfo>::iterator const biit = bimap_.find(*it);
1093                 // this shouldn't happen, but...
1094                 if (biit == bimap_.end())
1095                         // ...fail gracefully, anyway.
1096                         continue;
1097                 BibTeXInfo & entry = biit->second;
1098                 if (numbers) {
1099                         entry.label(entry.citeNumber());
1100                 } else {
1101                         docstring const auth = entry.getAbbreviatedAuthor(buf, false);
1102                         // we do it this way so as to access the xref, if necessary
1103                         // note that this also gives us the modifier
1104                         docstring const year = getYear(*it, buf, true);
1105                         if (!auth.empty() && !year.empty())
1106                                 entry.label(auth + ' ' + year);
1107                         else
1108                                 entry.label(entry.key());
1109                 }
1110         }
1111 }
1112
1113
1114 //////////////////////////////////////////////////////////////////////
1115 //
1116 // CitationStyle
1117 //
1118 //////////////////////////////////////////////////////////////////////
1119
1120
1121 CitationStyle citationStyleFromString(string const & command)
1122 {
1123         CitationStyle cs;
1124         if (command.empty())
1125                 return cs;
1126
1127         string cmd = command;
1128         if (cmd[0] == 'C') {
1129                 cs.forceUpperCase = true;
1130                 cmd[0] = 'c';
1131         }
1132
1133         size_t const n = cmd.size() - 1;
1134         if (cmd[n] == '*') {
1135                 cs.fullAuthorList = true;
1136                 cmd = cmd.substr(0, n);
1137         }
1138
1139         cs.cmd = cmd;
1140         return cs;
1141 }
1142
1143
1144 string citationStyleToString(const CitationStyle & cs)
1145 {
1146         string cmd = cs.cmd;
1147         if (cs.forceUpperCase)
1148                 cmd[0] = 'C';
1149         if (cs.fullAuthorList)
1150                 cmd += '*';
1151         return cmd;
1152 }
1153
1154 } // namespace lyx