3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Angus Leeming
11 * Full author contact details are available in file CREDITS.
16 #include "BiblioInfo.h"
18 #include "BufferParams.h"
19 #include "buffer_funcs.h"
21 #include "InsetIterator.h"
23 #include "output_xhtml.h"
24 #include "Paragraph.h"
25 #include "TextClass.h"
26 #include "TocBackend.h"
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"
40 using namespace lyx::support;
47 // gets the "family name" from an author-type string
48 docstring familyName(docstring const & name)
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));
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)
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) {
73 char_type const c = (*it)[0];
78 if (it == en) // we never found a "von"
81 // reconstruct what we need to return
84 for (; it != en; ++it) {
95 // converts a string containing LaTeX commands into unicode
97 docstring convertLaTeXCommands(docstring const & str)
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];
108 // if we're scanning math, we output everything until we
109 // find an unescaped $, at which point we break out.
116 scanning_math = false;
122 // if we're scanning a command name, then we just
123 // discard characters until we hit something that
126 if (isAlphaASCII(ch)) {
131 // so we're done with this command.
132 // now we fall through and check this character.
133 scanning_cmd = false;
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...
139 // exception: output \, as THIN SPACE
141 ret.push_back(0x2009);
152 scanning_math = true;
156 // we just ignore braces
157 if (ch == '{' || ch == '}') {
162 // we're going to check things that look like commands, so if
163 // this doesn't, just output it.
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("{"));
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
192 // it's a command of some sort
201 // Escape '<' and '>' and remove richtext markers (e.g. {!this is richtext!}) from a string.
202 docstring processRichtext(docstring const & str, bool richtext)
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;
216 if (scanning_rich && ch == '!' && val.size() > 1 && val[1] == '}') {
218 scanning_rich = false;
226 // we need to escape '<' and '>'
234 } else if (!scanning_rich /* && !richtext */)
236 // else the character is discarded, which will happen only if
237 // richtext == false and we are scanning rich text
246 //////////////////////////////////////////////////////////////////////
250 //////////////////////////////////////////////////////////////////////
252 BibTeXInfo::BibTeXInfo(docstring const & key, docstring const & type)
253 : is_bibtex_(true), bib_key_(key), entry_type_(type), info_(),
258 docstring const BibTeXInfo::getAbbreviatedAuthor(
259 Buffer const * buf, bool jurabib_style) const
262 docstring const opt = label();
267 docstring const remainder = trim(split(opt, authors, '('));
268 if (remainder.empty())
269 // in this case, we didn't find a "(",
270 // so we don't have author (year)
275 docstring author = operator[]("author");
276 if (author.empty()) {
277 author = operator[]("editor");
282 // FIXME Move this to a separate routine that can
283 // be called from elsewhere.
285 // OK, we've got some names. Let's format them.
286 // Try to split the author list on " and "
287 vector<docstring> const authors =
288 getVectorFromString(author, from_ascii(" and "));
290 if (jurabib_style && (authors.size() == 2 || authors.size() == 3)) {
291 docstring shortauthor = familyName(authors[0])
292 + "/" + familyName(authors[1]);
293 if (authors.size() == 3)
294 shortauthor += "/" + familyName(authors[2]);
295 return convertLaTeXCommands(shortauthor);
298 docstring retval = familyName(authors[0]);
300 if (authors.size() == 2 && authors[1] != "others") {
301 docstring const dformat = buf ?
302 buf->B_("%1$s and %2$s") : from_ascii("%1$s and %2$s");
303 retval = bformat(dformat, familyName(authors[0]), familyName(authors[1]));
304 } else if (authors.size() >= 2) {
305 // we get here either if the author list is longer than two names
306 // or if the second 'name' is "others". we do the same thing either
308 docstring const dformat = buf ?
309 buf->B_("%1$s et al.") : from_ascii("%1$s et al.");
310 retval = bformat(dformat, familyName(authors[0]));
313 return convertLaTeXCommands(retval);
317 docstring const BibTeXInfo::getYear() const
320 // first try legacy year field
321 docstring year = operator[]("year");
324 // now try biblatex's date field
325 year = operator[]("date");
326 // Format is [-]YYYY-MM-DD*/[-]YYYY-MM-DD*
327 // We only want the years.
328 static regex const yreg("[-]?([\\d]{4}).*");
329 static regex const ereg(".*/([\\d]{4}).*");
331 string const date = to_utf8(year);
332 regex_match(date, sm, yreg);
333 year = from_ascii(sm[1]);
334 // check for an endyear
335 if (regex_match(date, sm, ereg))
336 year += char_type(0x2013) + from_ascii(sm[1]);
340 docstring const opt = label();
345 docstring tmp = split(opt, authors, '(');
347 // we don't have author (year)
350 tmp = split(tmp, year, ')');
357 docstring parseOptions(docstring const & format, string & optkey,
358 docstring & ifpart, docstring & elsepart);
360 // Calls parseOptions to deal with an embedded option, such as:
361 // {%number%[[, no.~%number%]]}
362 // which must appear at the start of format. ifelsepart gets the
363 // whole of the option, and we return what's left after the option.
364 // we return format if there is an error.
365 docstring parseEmbeddedOption(docstring const & format, docstring & ifelsepart)
367 LASSERT(format[0] == '{' && format[1] == '%', return format);
371 docstring const rest = parseOptions(format, optkey, ifpart, elsepart);
372 if (format == rest) { // parse error
373 LYXERR0("ERROR! Couldn't parse `" << format <<"'.");
376 LASSERT(rest.size() <= format.size(),
377 { ifelsepart = docstring(); return format; });
378 ifelsepart = format.substr(0, format.size() - rest.size());
383 // Gets a "clause" from a format string, where the clause is
384 // delimited by '[[' and ']]'. Returns what is left after the
385 // clause is removed, and returns format if there is an error.
386 docstring getClause(docstring const & format, docstring & clause)
388 docstring fmt = format;
391 // we'll remove characters from the front of fmt as we
393 while (!fmt.empty()) {
394 if (fmt[0] == ']' && fmt.size() > 1 && fmt[1] == ']') {
399 // check for an embedded option
400 if (fmt[0] == '{' && fmt.size() > 1 && fmt[1] == '%') {
402 docstring const rest = parseEmbeddedOption(fmt, part);
404 LYXERR0("ERROR! Couldn't parse embedded option in `" << format <<"'.");
409 } else { // it's just a normal character
418 // parse an options string, which must appear at the start of the
419 // format parameter. puts the parsed bits in optkey, ifpart, and
420 // elsepart and returns what's left after the option is removed.
421 // if there's an error, it returns format itself.
422 docstring parseOptions(docstring const & format, string & optkey,
423 docstring & ifpart, docstring & elsepart)
425 LASSERT(format[0] == '{' && format[1] == '%', return format);
427 docstring fmt = format.substr(2);
428 size_t pos = fmt.find('%'); // end of key
429 if (pos == string::npos) {
430 LYXERR0("Error parsing `" << format <<"'. Can't find end of key.");
433 optkey = to_utf8(fmt.substr(0, pos));
434 fmt = fmt.substr(pos + 1);
435 // [[format]] should be next
436 if (fmt[0] != '[' || fmt[1] != '[') {
437 LYXERR0("Error parsing `" << format <<"'. Can't find '[[' after key.");
441 docstring curfmt = fmt;
442 fmt = getClause(curfmt, ifpart);
444 LYXERR0("Error parsing `" << format <<"'. Couldn't get if clause.");
448 if (fmt[0] == '}') // we're done, no else clause
449 return fmt.substr(1);
451 // else part should follow
452 if (fmt[0] != '[' || fmt[1] != '[') {
453 LYXERR0("Error parsing `" << format <<"'. Can't find else clause.");
458 fmt = getClause(curfmt, elsepart);
460 if (fmt == curfmt || fmt[0] != '}') {
461 LYXERR0("Error parsing `" << format <<"'. Can't find end of option.");
464 return fmt.substr(1);
471 Bug #9131 revealed an oddity in how we are generating citation information
472 when more than one key is given. We end up building a longer and longer format
473 string as we go, which we then have to re-parse, over and over and over again,
474 rather than generating the information for the individual keys and then putting
475 all of that together. We do that to deal with the way separators work, from what
476 I can tell, but it still feels like a hack. Fixing this would require quite a
477 bit of work, however.
479 docstring BibTeXInfo::expandFormat(docstring const & format,
480 BibTeXInfoList const xrefs, int & counter, Buffer const & buf,
481 docstring before, docstring after, docstring dialog, bool next) const
483 // incorrect use of macros could put us in an infinite loop
484 static int const max_passes = 5000;
485 // the use of overly large keys can lead to performance problems, due
486 // to eventual attempts to convert LaTeX macros to unicode. See bug
487 // #8944. This is perhaps not the best solution, but it will have to
489 static size_t const max_keysize = 128;
490 odocstringstream ret; // return value
492 bool scanning_key = false;
493 bool scanning_rich = false;
495 CiteEngineType const engine_type = buf.params().citeEngineType();
496 docstring fmt = format;
497 // we'll remove characters from the front of fmt as we
499 while (!fmt.empty()) {
500 if (counter > max_passes) {
501 LYXERR0("Recursion limit reached while parsing `"
506 char_type thischar = fmt[0];
507 if (thischar == '%') {
508 // beginning or end of key
511 scanning_key = false;
512 // so we replace the key with its value, which may be empty
516 buf.params().documentClass().getCiteMacro(engine_type, key);
517 fmt = from_utf8(val) + fmt.substr(1);
520 } else if (key[0] == '_') {
521 // a translatable bit
523 buf.params().documentClass().getCiteMacro(engine_type, key);
524 docstring const trans =
525 translateIfPossible(from_utf8(val), buf.params().language->code());
528 docstring const val =
529 getValueForKey(key, buf, before, after, dialog, xrefs, max_keysize);
531 ret << from_ascii("{!<span class=\"bib-" + key + "\">!}");
534 ret << from_ascii("{!</span>!}");
542 else if (thischar == '{') {
543 // beginning of option?
545 LYXERR0("ERROR: Found `{' when scanning key in `" << format << "'.");
548 if (fmt.size() > 1) {
550 // it is the beginning of an optional format
554 docstring const newfmt =
555 parseOptions(fmt, optkey, ifpart, elsepart);
556 if (newfmt == fmt) // parse error
559 docstring const val =
560 getValueForKey(optkey, buf, before, after, dialog, xrefs);
561 if (optkey == "next" && next)
562 ret << ifpart; // without expansion
563 else if (!val.empty()) {
565 ret << expandFormat(ifpart, xrefs, newcounter, buf,
566 before, after, dialog, next);
567 } else if (!elsepart.empty()) {
569 ret << expandFormat(elsepart, xrefs, newcounter, buf,
570 before, after, dialog, next);
572 // fmt will have been shortened for us already
576 // beginning of rich text
577 scanning_rich = true;
579 ret << from_ascii("{!");
583 // we are here if '{' was not followed by % or !.
584 // So it's just a character.
587 else if (scanning_rich && thischar == '!'
588 && fmt.size() > 1 && fmt[1] == '}') {
590 scanning_rich = false;
592 ret << from_ascii("!}");
595 else if (scanning_key)
596 key += char(thischar);
600 } catch (EncodingException & /* e */) {
601 LYXERR0("Uncodable character '" << docstring(1, thischar) << " in citation label!");
607 LYXERR0("Never found end of key in `" << format << "'!");
611 LYXERR0("Never found end of rich text in `" << format << "'!");
618 docstring const & BibTeXInfo::getInfo(BibTeXInfoList const xrefs,
619 Buffer const & buf, bool richtext) const
621 if (!richtext && !info_.empty())
623 if (richtext && !info_richtext_.empty())
624 return info_richtext_;
627 BibTeXInfo::const_iterator it = find(from_ascii("ref"));
632 CiteEngineType const engine_type = buf.params().citeEngineType();
633 DocumentClass const & dc = buf.params().documentClass();
634 docstring const & format =
635 from_utf8(dc.getCiteFormat(engine_type, to_utf8(entry_type_)));
637 info_ = expandFormat(format, xrefs, counter, buf,
638 docstring(), docstring(), docstring(), false);
641 // this probably shouldn't happen
646 info_richtext_ = convertLaTeXCommands(processRichtext(info_, true));
647 return info_richtext_;
650 info_ = convertLaTeXCommands(processRichtext(info_, false));
655 docstring const BibTeXInfo::getLabel(BibTeXInfoList const xrefs,
656 Buffer const & buf, docstring const & format, bool richtext,
657 docstring const & before, docstring const & after,
658 docstring const & dialog, bool next) const
663 loclabel = expandFormat(format, xrefs, counter, buf,
664 before, after, dialog, next);
666 if (!loclabel.empty() && !next) {
667 loclabel = processRichtext(loclabel, richtext);
668 loclabel = convertLaTeXCommands(loclabel);
675 docstring const & BibTeXInfo::operator[](docstring const & field) const
677 BibTeXInfo::const_iterator it = find(field);
680 static docstring const empty_value = docstring();
685 docstring const & BibTeXInfo::operator[](string const & field) const
687 return operator[](from_ascii(field));
691 docstring BibTeXInfo::getValueForKey(string const & oldkey, Buffer const & buf,
692 docstring const & before, docstring const & after, docstring const & dialog,
693 BibTeXInfoList const xrefs, size_t maxsize) const
695 // anything less is pointless
696 LASSERT(maxsize >= 16, maxsize = 16);
698 bool cleanit = false;
699 if (prefixIs(oldkey, "clean:")) {
700 key = oldkey.substr(6);
704 docstring ret = operator[](key);
705 if (ret.empty() && !xrefs.empty()) {
706 vector<BibTeXInfo const *>::const_iterator it = xrefs.begin();
707 vector<BibTeXInfo const *>::const_iterator en = xrefs.end();
708 for (; it != en; ++it) {
709 if (*it && !(**it)[key].empty()) {
717 // FIXME: dialog, textbefore and textafter have nothing to do with this
720 else if (key == "entrytype")
722 else if (key == "key")
724 else if (key == "label")
726 else if (key == "modifier" && modifier_ != 0)
728 else if (key == "numericallabel")
730 else if (key == "abbrvauthor")
731 // Special key to provide abbreviated author names.
732 ret = getAbbreviatedAuthor(&buf, false);
733 else if (key == "shortauthor")
734 // When shortauthor is not defined, jurabib automatically
735 // provides jurabib-style abbreviated author names. We do
737 ret = getAbbreviatedAuthor(&buf, true);
738 else if (key == "shorttitle") {
739 // When shorttitle is not defined, jurabib uses for `article'
740 // and `periodical' entries the form `journal volume [year]'
741 // and for other types of entries it uses the `title' field.
742 if (entry_type_ == "article" || entry_type_ == "periodical")
743 ret = operator[]("journal") + " " + operator[]("volume")
744 + " [" + operator[]("year") + "]";
746 ret = operator[]("title");
747 } else if (key == "bibentry") {
748 // Special key to provide the full bibliography entry: see getInfo()
749 CiteEngineType const engine_type = buf.params().citeEngineType();
750 DocumentClass const & dc = buf.params().documentClass();
751 docstring const & format =
752 from_utf8(dc.getCiteFormat(engine_type, to_utf8(entry_type_)));
754 ret = expandFormat(format, xrefs, counter, buf,
755 docstring(), docstring(), docstring(), false);
756 } else if (key == "textbefore")
758 else if (key == "textafter")
760 else if (key == "year")
765 ret = html::cleanAttr(ret);
767 // make sure it is not too big
768 support::truncateWithEllipsis(ret, maxsize);
773 //////////////////////////////////////////////////////////////////////
777 //////////////////////////////////////////////////////////////////////
781 // A functor for use with sort, leading to case insensitive sorting
782 class compareNoCase: public binary_function<docstring, docstring, bool>
785 bool operator()(docstring const & s1, docstring const & s2) const {
786 return compare_no_case(s1, s2) < 0;
793 vector<docstring> const BiblioInfo::getXRefs(BibTeXInfo const & data, bool const nested) const
795 vector<docstring> result;
796 if (!data.isBibTeX())
798 // Legacy crossref field. This is not nestable.
799 if (!nested && !data["crossref"].empty())
800 result.push_back(data["crossref"]);
801 // Biblatex's xdata field. Infinitely nestable.
802 docstring const xdatakey = data["xdata"];
803 if (!xdatakey.empty()) {
804 result.push_back(xdatakey);
805 BiblioInfo::const_iterator it = find(xdatakey);
807 BibTeXInfo const & xdata = it->second;
808 vector<docstring> const nxdata = getXRefs(xdata, true);
810 result.insert(result.end(), nxdata.begin(), nxdata.end());
817 vector<docstring> const BiblioInfo::getKeys() const
819 vector<docstring> bibkeys;
820 BiblioInfo::const_iterator it = begin();
821 for (; it != end(); ++it)
822 bibkeys.push_back(it->first);
823 sort(bibkeys.begin(), bibkeys.end(), compareNoCase());
828 vector<docstring> const BiblioInfo::getFields() const
830 vector<docstring> bibfields;
831 set<docstring>::const_iterator it = field_names_.begin();
832 set<docstring>::const_iterator end = field_names_.end();
833 for (; it != end; ++it)
834 bibfields.push_back(*it);
835 sort(bibfields.begin(), bibfields.end());
840 vector<docstring> const BiblioInfo::getEntries() const
842 vector<docstring> bibentries;
843 set<docstring>::const_iterator it = entry_types_.begin();
844 set<docstring>::const_iterator end = entry_types_.end();
845 for (; it != end; ++it)
846 bibentries.push_back(*it);
847 sort(bibentries.begin(), bibentries.end());
852 docstring const BiblioInfo::getAbbreviatedAuthor(docstring const & key, Buffer const & buf) const
854 BiblioInfo::const_iterator it = find(key);
857 BibTeXInfo const & data = it->second;
858 return data.getAbbreviatedAuthor(&buf, false);
862 docstring const BiblioInfo::getCiteNumber(docstring const & key) const
864 BiblioInfo::const_iterator it = find(key);
867 BibTeXInfo const & data = it->second;
868 return data.citeNumber();
872 docstring const BiblioInfo::getYear(docstring const & key, bool use_modifier) const
874 BiblioInfo::const_iterator it = find(key);
877 BibTeXInfo const & data = it->second;
878 docstring year = data.getYear();
880 // let's try the crossrefs
881 vector<docstring> const xrefs = getXRefs(data);
885 vector<docstring>::const_iterator it = xrefs.begin();
886 vector<docstring>::const_iterator en = xrefs.end();
887 for (; it != en; ++it) {
888 BiblioInfo::const_iterator const xrefit = find(*it);
891 BibTeXInfo const & xref_data = xrefit->second;
892 year = xref_data.getYear();
898 if (use_modifier && data.modifier() != 0)
899 year += data.modifier();
904 docstring const BiblioInfo::getYear(docstring const & key, Buffer const & buf, bool use_modifier) const
906 docstring const year = getYear(key, use_modifier);
908 return buf.B_("No year");
913 docstring const BiblioInfo::getInfo(docstring const & key,
914 Buffer const & buf, bool richtext) const
916 BiblioInfo::const_iterator it = find(key);
918 return docstring(_("Bibliography entry not found!"));
919 BibTeXInfo const & data = it->second;
920 BibTeXInfoList xrefptrs;
921 vector<docstring> const xrefs = getXRefs(data);
922 if (!xrefs.empty()) {
923 vector<docstring>::const_iterator it = xrefs.begin();
924 vector<docstring>::const_iterator en = xrefs.end();
925 for (; it != en; ++it) {
926 BiblioInfo::const_iterator const xrefit = find(*it);
928 xrefptrs.push_back(&(xrefit->second));
931 return data.getInfo(xrefptrs, buf, richtext);
935 docstring const BiblioInfo::getLabel(vector<docstring> keys,
936 Buffer const & buf, string const & style, bool for_xhtml,
937 size_t max_size, docstring const & before, docstring const & after,
938 docstring const & dialog) const
940 // shorter makes no sense
941 LASSERT(max_size >= 16, max_size = 16);
943 // we can't display more than 10 of these, anyway
944 bool const too_many_keys = keys.size() > 10;
948 CiteEngineType const engine_type = buf.params().citeEngineType();
949 DocumentClass const & dc = buf.params().documentClass();
950 docstring const & format = from_utf8(dc.getCiteFormat(engine_type, style, "cite"));
951 docstring ret = format;
952 vector<docstring>::const_iterator key = keys.begin();
953 vector<docstring>::const_iterator ken = keys.end();
954 for (; key != ken; ++key) {
955 BiblioInfo::const_iterator it = find(*key);
956 BibTeXInfo empty_data;
957 empty_data.key(*key);
958 BibTeXInfo & data = empty_data;
959 vector<BibTeXInfo const *> xrefptrs;
962 vector<docstring> const xrefs = getXRefs(data);
963 if (!xrefs.empty()) {
964 vector<docstring>::const_iterator it = xrefs.begin();
965 vector<docstring>::const_iterator en = xrefs.end();
966 for (; it != en; ++it) {
967 BiblioInfo::const_iterator const xrefit = find(*it);
969 xrefptrs.push_back(&(xrefit->second));
973 ret = data.getLabel(xrefptrs, buf, ret, for_xhtml,
974 before, after, dialog, key + 1 != ken);
978 ret.push_back(0x2026);//HORIZONTAL ELLIPSIS
979 support::truncateWithEllipsis(ret, max_size);
984 bool BiblioInfo::isBibtex(docstring const & key) const
987 split(key, key1, ',');
988 BiblioInfo::const_iterator it = find(key1);
991 return it->second.isBibTeX();
995 vector<docstring> const BiblioInfo::getCiteStrings(
996 vector<docstring> const & keys, vector<CitationStyle> const & styles,
997 Buffer const & buf, docstring const & before,
998 docstring const & after, docstring const & dialog, size_t max_size) const
1001 return vector<docstring>();
1004 vector<docstring> vec(styles.size());
1005 for (size_t i = 0; i != vec.size(); ++i) {
1006 style = styles[i].cmd;
1007 vec[i] = getLabel(keys, buf, style, false, max_size, before, after, dialog);
1014 void BiblioInfo::mergeBiblioInfo(BiblioInfo const & info)
1016 bimap_.insert(info.begin(), info.end());
1017 field_names_.insert(info.field_names_.begin(), info.field_names_.end());
1018 entry_types_.insert(info.entry_types_.begin(), info.entry_types_.end());
1024 // used in xhtml to sort a list of BibTeXInfo objects
1025 bool lSorter(BibTeXInfo const * lhs, BibTeXInfo const * rhs)
1027 docstring const lauth = lhs->getAbbreviatedAuthor();
1028 docstring const rauth = rhs->getAbbreviatedAuthor();
1029 docstring const lyear = lhs->getYear();
1030 docstring const ryear = rhs->getYear();
1031 docstring const ltitl = lhs->operator[]("title");
1032 docstring const rtitl = rhs->operator[]("title");
1033 return (lauth < rauth)
1034 || (lauth == rauth && lyear < ryear)
1035 || (lauth == rauth && lyear == ryear && ltitl < rtitl);
1041 void BiblioInfo::collectCitedEntries(Buffer const & buf)
1043 cited_entries_.clear();
1044 // We are going to collect all the citation keys used in the document,
1045 // getting them from the TOC.
1046 // FIXME We may want to collect these differently, in the first case,
1047 // so that we might have them in order of appearance.
1048 set<docstring> citekeys;
1049 shared_ptr<Toc const> toc = buf.tocBackend().toc("citation");
1050 Toc::const_iterator it = toc->begin();
1051 Toc::const_iterator const en = toc->end();
1052 for (; it != en; ++it) {
1053 if (it->str().empty())
1055 vector<docstring> const keys = getVectorFromString(it->str());
1056 citekeys.insert(keys.begin(), keys.end());
1058 if (citekeys.empty())
1061 // We have a set of the keys used in this document.
1062 // We will now convert it to a list of the BibTeXInfo objects used in
1064 vector<BibTeXInfo const *> bi;
1065 set<docstring>::const_iterator cit = citekeys.begin();
1066 set<docstring>::const_iterator const cen = citekeys.end();
1067 for (; cit != cen; ++cit) {
1068 BiblioInfo::const_iterator const bt = find(*cit);
1069 if (bt == end() || !bt->second.isBibTeX())
1071 bi.push_back(&(bt->second));
1074 sort(bi.begin(), bi.end(), lSorter);
1076 // Now we can write the sorted keys
1077 vector<BibTeXInfo const *>::const_iterator bit = bi.begin();
1078 vector<BibTeXInfo const *>::const_iterator ben = bi.end();
1079 for (; bit != ben; ++bit)
1080 cited_entries_.push_back((*bit)->key());
1084 void BiblioInfo::makeCitationLabels(Buffer const & buf)
1086 collectCitedEntries(buf);
1087 CiteEngineType const engine_type = buf.params().citeEngineType();
1088 bool const numbers = (engine_type & ENGINE_TYPE_NUMERICAL);
1092 // used to remember the last one we saw
1093 // we'll be comparing entries to see if we need to add
1094 // modifiers, like "1984a"
1095 map<docstring, BibTeXInfo>::iterator last;
1097 vector<docstring>::const_iterator it = cited_entries_.begin();
1098 vector<docstring>::const_iterator const en = cited_entries_.end();
1099 for (; it != en; ++it) {
1100 map<docstring, BibTeXInfo>::iterator const biit = bimap_.find(*it);
1101 // this shouldn't happen, but...
1102 if (biit == bimap_.end())
1103 // ...fail gracefully, anyway.
1105 BibTeXInfo & entry = biit->second;
1107 docstring const num = convert<docstring>(++keynumber);
1108 entry.setCiteNumber(num);
1110 // coverity complains about our derefercing the iterator last,
1111 // which was not initialized above. but it does get initialized
1112 // after the first time through the loop, which is the point of
1114 // coverity[FORWARD_NULL]
1115 if (it != cited_entries_.begin()
1116 && entry.getAbbreviatedAuthor() == last->second.getAbbreviatedAuthor()
1117 // we access the year via getYear() so as to get it from the xref,
1118 // if we need to do so
1119 && getYear(entry.key()) == getYear(last->second.key())) {
1120 if (modifier == 0) {
1121 // so the last one should have been 'a'
1122 last->second.setModifier('a');
1124 } else if (modifier == 'z')
1131 entry.setModifier(modifier);
1132 // remember the last one
1137 it = cited_entries_.begin();
1138 for (; it != en; ++it) {
1139 map<docstring, BibTeXInfo>::iterator const biit = bimap_.find(*it);
1140 // this shouldn't happen, but...
1141 if (biit == bimap_.end())
1142 // ...fail gracefully, anyway.
1144 BibTeXInfo & entry = biit->second;
1146 entry.label(entry.citeNumber());
1148 docstring const auth = entry.getAbbreviatedAuthor(&buf, false);
1149 // we do it this way so as to access the xref, if necessary
1150 // note that this also gives us the modifier
1151 docstring const year = getYear(*it, buf, true);
1152 if (!auth.empty() && !year.empty())
1153 entry.label(auth + ' ' + year);
1155 entry.label(entry.key());
1161 //////////////////////////////////////////////////////////////////////
1165 //////////////////////////////////////////////////////////////////////
1168 CitationStyle citationStyleFromString(string const & command)
1171 if (command.empty())
1174 string cmd = command;
1175 if (cmd[0] == 'C') {
1176 cs.forceUpperCase = true;
1180 size_t const n = cmd.size() - 1;
1181 if (cmd[n] == '*') {
1182 cs.fullAuthorList = true;
1183 cmd = cmd.substr(0, n);
1191 string citationStyleToString(const CitationStyle & cs)
1193 string cmd = cs.cmd;
1194 if (cs.forceUpperCase)
1195 cmd[0] = uppercase(cmd[0]);
1196 if (cs.fullAuthorList)