]> git.lyx.org Git - lyx.git/blobdiff - src/BiblioInfo.cpp
Account for old versions of Pygments
[lyx.git] / src / BiblioInfo.cpp
index 44e517a18fc01b345f880aabec1def157319a98b..a9f48e44577ed28eea1a66389b0699ad0b7c63d2 100644 (file)
@@ -7,6 +7,7 @@
  * \author Herbert Voß
  * \author Richard Heck
  * \author Julien Rioux
+ * \author Jürgen Spitzmüller
  *
  * Full author contact details are available in file CREDITS.
  */
@@ -35,6 +36,7 @@
 #include "support/regex.h"
 #include "support/textutils.h"
 
+#include <map>
 #include <set>
 
 using namespace std;
@@ -45,51 +47,273 @@ namespace lyx {
 
 namespace {
 
-// gets the "family name" from an author-type string
-docstring familyName(docstring const & name)
+// Remove placeholders from names
+docstring renormalize(docstring const & input)
 {
-       if (name.empty())
-               return docstring();
+       docstring res = subst(input, from_ascii("$$space!"), from_ascii(" "));
+       return subst(res, from_ascii("$$comma!"), from_ascii(","));
+}
+
+
+// Split the surname into prefix ("von-part") and family name
+pair<docstring, docstring> parseSurname(docstring const & sname)
+{
+       // Split the surname into its tokens
+       vector<docstring> pieces = getVectorFromString(sname, from_ascii(" "));
+       if (pieces.size() < 2)
+               return make_pair(docstring(), sname);
+
+       // Now we look for pieces that begin with a lower case letter.
+       // All except for the very last token constitute the "von-part".
+       docstring prefix;
+       vector<docstring>::const_iterator it = pieces.begin();
+       vector<docstring>::const_iterator const en = pieces.end();
+       bool first = true;
+       for (; it != en; ++it) {
+               if ((*it).empty())
+                       continue;
+               // If this is the last piece, then what we now have is
+               // the family name, notwithstanding the casing.
+               if (it + 1 == en)
+                       break;
+               char_type const c = (*it)[0];
+               // If the piece starts with a upper case char, we assume
+               // this is part of the surname.
+               if (!isLower(c))
+                       break;
+               // Nothing of the former, so add this piece to the prename
+               if (!first)
+                       prefix += " ";
+               else
+                       first = false;
+               prefix += *it;
+       }
+
+       // Reconstruct the family name.
+       // Note that if we left the loop with because it + 1 == en,
+       // then this will still do the right thing, i.e., make surname
+       // just be the last piece.
+       docstring surname;
+       first = true;
+       for (; it != en; ++it) {
+               if (!first)
+                       surname += " ";
+               else
+                       first = false;
+               surname += *it;
+       }
+       return make_pair(prefix, surname);
+}
+
+
+struct name_parts {
+       docstring surname;
+       docstring prename;
+       docstring suffix;
+       docstring prefix;
+};
 
-       // first we look for a comma, and take the last name to be everything
-       // preceding the right-most one, so that we also get the "jr" part.
-       docstring::size_type idx = name.rfind(',');
-       if (idx != docstring::npos)
-               return ltrim(name.substr(0, idx));
 
-       // OK, so now we want to look for the last name. We're going to
-       // include the "von" part. This isn't perfect.
+// gets the name parts (prename, surname, prefix, suffix) from an author-type string
+name_parts nameParts(docstring const & iname)
+{
+       name_parts res;
+       if (iname.empty())
+               return res;
+
+       // First we check for goupings (via {...}) and replace blanks and
+       // commas inside groups with temporary placeholders
+       docstring name;
+       int gl = 0;
+       docstring::const_iterator p = iname.begin();
+       while (p != iname.end()) {
+               // count grouping level
+               if (*p == '{')
+                       ++gl;
+               else if (*p == '}')
+                       --gl;
+               // generate string with probable placeholders
+               if (*p == ' ' && gl > 0)
+                       name += from_ascii("$$space!");
+               else if (*p == ',' && gl > 0)
+                       name += from_ascii("$$comma!");
+               else
+                       name += *p;
+               ++p;
+       }
+
+       // Now we look for a comma, and take the last name to be everything
+       // preceding the right-most one, so that we also get the name suffix
+       // (aka "jr" part).
+       vector<docstring> pieces = getVectorFromString(name);
+       if (pieces.size() > 1) {
+               // Whether we have a name suffix or not, the prename is
+               // always last item
+               res.prename = renormalize(pieces.back());
+               // The family name, conversely, is always the first item.
+               // However, it might contain a prefix (aka "von" part)
+               docstring const sname = pieces.front();
+               res.prefix = renormalize(parseSurname(sname).first);
+               res.surname = renormalize(parseSurname(sname).second);
+               // If we have three pieces (the maximum allowed by BibTeX),
+               // the second one is the name suffix.
+               if (pieces.size() > 2)
+                       res.suffix = renormalize(pieces.at(1));
+               return res;
+       }
+
+       // OK, so now we want to look for the last name.
        // Split on spaces, to get various tokens.
-       vector<docstring> pieces = getVectorFromString(name, from_ascii(" "));
-       // If we only get two, assume the last one is the last name
-       if (pieces.size() <= 2)
-               return pieces.back();
+       pieces = getVectorFromString(name, from_ascii(" "));
+       // No space: Only a family name given
+       if (pieces.size() < 2) {
+               res.surname = renormalize(pieces.back());
+               return res;
+       }
+       // If we get two pieces, assume "prename surname"
+       if (pieces.size() == 2) {
+               res.prename = renormalize(pieces.front());
+               res.surname = renormalize(pieces.back());
+               return res;
+       }
 
-       // Now we look for the first token that begins with a lower case letter.
+       // More than 3 pieces: A name prefix (aka "von" part) might be included.
+       // We look for the first piece that begins with a lower case letter
+       // (which is the name prefix, if it is not the last token) or the last token.
+       docstring prename;
        vector<docstring>::const_iterator it = pieces.begin();
-       vector<docstring>::const_iterator en = pieces.end();
+       vector<docstring>::const_iterator const en = pieces.end();
+       bool first = true;
        for (; it != en; ++it) {
                if ((*it).empty())
                        continue;
                char_type const c = (*it)[0];
+               // If the piece starts with a lower case char, we assume
+               // this is the name prefix and thus prename is complete.
                if (isLower(c))
                        break;
+               // Same if this is the last piece, which is always the surname.
+               if (it + 1 == en)
+                       break;
+               // Nothing of the former, so add this piece to the prename
+               if (!first)
+                       prename += " ";
+               else
+                       first = false;
+               prename += *it;
        }
 
-       if (it == en) // we never found a "von"
-               return pieces.back();
-
-       // reconstruct what we need to return
-       docstring retval;
-       bool first = true;
+       // Now reconstruct the family name and strip the prefix.
+       // Note that if we left the loop because it + 1 == en,
+       // then this will still do the right thing, i.e., make surname
+       // just be the last piece.
+       docstring surname;
+       first = true;
        for (; it != en; ++it) {
                if (!first)
-                       retval += " ";
+                       surname += " ";
                else
                        first = false;
-               retval += *it;
+               surname += *it;
+       }
+       res.prename = renormalize(prename);
+       res.prefix = renormalize(parseSurname(surname).first);
+       res.surname = renormalize(parseSurname(surname).second);
+       return res;
+}
+
+
+docstring constructName(docstring const & name, string const scheme)
+{
+       // re-constructs a name from name parts according
+       // to a given scheme
+       docstring const prename = nameParts(name).prename;
+       docstring const surname = nameParts(name).surname;
+       docstring const prefix = nameParts(name).prefix;
+       docstring const suffix = nameParts(name).suffix;
+       string res = scheme;
+       static regex const reg1("(.*)(\\{%prename%\\[\\[)([^\\]]+)(\\]\\]\\})(.*)");
+       static regex const reg2("(.*)(\\{%suffix%\\[\\[)([^\\]]+)(\\]\\]\\})(.*)");
+       static regex const reg3("(.*)(\\{%prefix%\\[\\[)([^\\]]+)(\\]\\]\\})(.*)");
+       smatch sub;
+       if (regex_match(scheme, sub, reg1)) {
+               res = sub.str(1);
+               if (!prename.empty())
+                       res += sub.str(3);
+               res += sub.str(5);
+       }
+       if (regex_match(res, sub, reg2)) {
+               res = sub.str(1);
+               if (!suffix.empty())
+                       res += sub.str(3);
+               res += sub.str(5);
+       }
+       if (regex_match(res, sub, reg3)) {
+               res = sub.str(1);
+               if (!prefix.empty())
+                       res += sub.str(3);
+               res += sub.str(5);
+       }
+       docstring result = from_ascii(res);
+       result = subst(result, from_ascii("%prename%"), prename);
+       result = subst(result, from_ascii("%surname%"), surname);
+       result = subst(result, from_ascii("%prefix%"), prefix);
+       result = subst(result, from_ascii("%suffix%"), suffix);
+       return result;
+}
+
+
+vector<docstring> const getAuthors(docstring const & author)
+{
+       // We check for goupings (via {...}) and only consider " and "
+       // outside groups as author separator. This is to account
+       // for cases such as {{Barnes and Noble, Inc.}}, which
+       // need to be treated as one single family name.
+       // We use temporary placeholders in order to differentiate the
+       // diverse " and " cases.
+
+       // First, we temporarily replace all ampersands. It is rather unusual
+       // in author names, but can happen (consider cases such as "C \& A Corp.").
+       docstring iname = subst(author, from_ascii("&"), from_ascii("$$amp!"));
+       // Then, we temporarily make all " and " strings to ampersands in order
+       // to handle them later on a per-char level.
+       iname = subst(iname, from_ascii(" and "), from_ascii(" & "));
+       // Now we traverse through the string and replace the "&" by the proper
+       // output in- and outside groups
+       docstring name;
+       int gl = 0;
+       docstring::const_iterator p = iname.begin();
+       while (p != iname.end()) {
+               // count grouping level
+               if (*p == '{')
+                       ++gl;
+               else if (*p == '}')
+                       --gl;
+               // generate string with probable placeholders
+               if (*p == '&') {
+                       if (gl > 0)
+                               // Inside groups, we output "and"
+                               name += from_ascii("and");
+                       else
+                               // Outside groups, we output a separator
+                               name += from_ascii("$$namesep!");
+               }
+               else
+                       name += *p;
+               ++p;
        }
-       return retval;
+
+       // re-insert the literal ampersands
+       name = subst(name, from_ascii("$$amp!"), from_ascii("&"));
+
+       // Now construct the actual vector
+       return getVectorFromString(name, from_ascii(" $$namesep! "));
+}
+
+
+bool multipleAuthors(docstring const author)
+{
+       return getAuthors(author).size() > 1;
 }
 
 
@@ -154,7 +378,18 @@ docstring convertLaTeXCommands(docstring const & str)
                        continue;
                }
 
-               // we just ignore braces
+               // Change text mode accents in the form
+               // {\v a} to \v{a} (see #9340).
+               // FIXME: This is a sort of mini-tex2lyx.
+               //        Use the real tex2lyx instead!
+               static lyx::regex const tma_reg("^\\{\\\\[bcCdfGhHkrtuUv]\\s\\w\\}");
+               if (lyx::regex_search(to_utf8(val), tma_reg)) {
+                       val = val.substr(1);
+                       val.replace(2, 1, from_ascii("{"));
+                       continue;
+               }
+
+               // Apart from the above, we just ignore braces
                if (ch == '{' || ch == '}') {
                        val = val.substr(1);
                        continue;
@@ -256,8 +491,21 @@ BibTeXInfo::BibTeXInfo(docstring const & key, docstring const & type)
 {}
 
 
-docstring const BibTeXInfo::getAuthorList(
-    Buffer const * buf, bool full, bool forceshort) const
+
+docstring const BibTeXInfo::getAuthorOrEditorList(Buffer const * buf,
+                                         bool full, bool forceshort) const
+{
+       docstring author = operator[]("author");
+       if (author.empty())
+               author = operator[]("editor");
+
+       return getAuthorList(buf, author, full, forceshort);
+}
+
+
+docstring const BibTeXInfo::getAuthorList(Buffer const * buf, 
+               docstring const & author, bool const full, bool const forceshort, 
+               bool const allnames, bool const beginning) const
 {
        // Maxnames treshold depend on engine
        size_t maxnames = buf ?
@@ -277,28 +525,13 @@ docstring const BibTeXInfo::getAuthorList(
                return authors;
        }
 
-       docstring author = operator[]("author");
-       if (author.empty()) {
-               author = operator[]("editor");
-               if (author.empty())
-                       return author;
-       }
+       if (author.empty())
+               return author;
 
-       // FIXME Move this to a separate routine that can
-       // be called from elsewhere.
-       //
        // OK, we've got some names. Let's format them.
-       // Try to split the author list on " and "
-       vector<docstring> const authors =
-               getVectorFromString(author, from_ascii(" and "));
-
-       if (jurabib_style && (authors.size() == 2 || authors.size() == 3)) {
-               docstring shortauthor = familyName(authors[0])
-                       + "/" + familyName(authors[1]);
-               if (authors.size() == 3)
-                       shortauthor += "/" + familyName(authors[2]);
-               return convertLaTeXCommands(shortauthor);
-       }
+       // Try to split the author list
+       vector<docstring> const authors = getAuthors(author);
+
        docstring retval;
 
        CiteEngineType const engine_type = buf ? buf->params().citeEngineType()
@@ -317,9 +550,22 @@ docstring const BibTeXInfo::getAuthorList(
        string const pairnamesep =
                buf ? buf->params().documentClass().getCiteMacro(engine_type, "_pairnamesep")
                     : " and ";
+       string firstnameform =
+                       buf ? buf->params().documentClass().getCiteMacro(engine_type, "!firstnameform")
+                            : "{%prefix%[[%prefix% ]]}%surname%{%suffix%[[, %suffix%]]}{%prename%[[, %prename%]]}";
+       if (!beginning)
+               firstnameform = buf ? buf->params().documentClass().getCiteMacro(engine_type, "!firstbynameform")
+                                            : "%prename% {%prefix%[[%prefix% ]]}%surname%{%suffix%[[, %suffix%]]}";
+       string othernameform = buf ? buf->params().documentClass().getCiteMacro(engine_type, "!othernameform")
+                            : "{%prefix%[[%prefix% ]]}%surname%{%suffix%[[, %suffix%]]}{%prename%[[, %prename%]]}";
+       if (!beginning)
+               othernameform = buf ? buf->params().documentClass().getCiteMacro(engine_type, "!otherbynameform")
+                                            : "%prename% {%prefix%[[%prefix% ]]}%surname%{%suffix%[[, %suffix%]]}";
+       string citenameform = buf ? buf->params().documentClass().getCiteMacro(engine_type, "!citenameform")
+                            : "{%prefix%[[%prefix% ]]}%surname%";
 
        // Shorten the list (with et al.) if forceshort is set
-       // and the list can actually be shorten, else if maxcitenames
+       // and the list can actually be shortened, else if maxcitenames
        // is passed and full is not set.
        bool shorten = forceshort && authors.size() > 1;
        vector<docstring>::const_iterator it = authors.begin();
@@ -340,10 +586,18 @@ docstring const BibTeXInfo::getAuthorList(
                                retval += buf ? buf->B_(lastnamesep) : from_ascii(lastnamesep);
                } else if (i > 0)
                        retval += buf ? buf->B_(namesep) : from_ascii(namesep);
-               retval += familyName(*it);
+               if (allnames)
+                       retval += (i == 0) ? constructName(*it, firstnameform)
+                               : constructName(*it, othernameform);
+               else
+                       retval += constructName(*it, citenameform);
+       }
+       if (shorten) {
+               if (allnames)
+                       retval = constructName(authors[0], firstnameform) + (buf ? buf->B_(etal) : from_ascii(etal));
+               else
+                       retval = constructName(authors[0], citenameform) + (buf ? buf->B_(etal) : from_ascii(etal));
        }
-       if (shorten)
-               retval = familyName(authors[0]) + (buf ? buf->B_(etal) : from_ascii(etal));
 
        return convertLaTeXCommands(retval);
 }
@@ -364,7 +618,9 @@ docstring const BibTeXInfo::getYear() const
                static regex const ereg(".*/[-]?([\\d]{4}).*");
                smatch sm;
                string const date = to_utf8(year);
-               regex_match(date, sm, yreg);
+               if (!regex_match(date, sm, yreg))
+                       // cannot parse year.
+                       return docstring();
                year = from_ascii(sm[1]);
                // check for an endyear
                if (regex_match(date, sm, ereg))
@@ -519,9 +775,10 @@ docstring BibTeXInfo::expandFormat(docstring const & format,
        static int const max_passes = 5000;
        // the use of overly large keys can lead to performance problems, due
        // to eventual attempts to convert LaTeX macros to unicode. See bug
-       // #8944. This is perhaps not the best solution, but it will have to
-       // do for now.
-       static size_t const max_keysize = 128;
+       // #8944. By default, the size is limited to 128 (in CiteItem), but
+       // for specific purposes (such as XHTML export), it needs to be enlarged
+       // This is perhaps not the best solution, but it will have to do for now.
+       size_t const max_keysize = ci.max_key_size;
        odocstringstream ret; // return value
        string key;
        bool scanning_key = false;
@@ -595,7 +852,11 @@ docstring BibTeXInfo::expandFormat(docstring const & format,
                                                getValueForKey(optkey, buf, ci, xrefs);
                                        if (optkey == "next" && next)
                                                ret << ifpart; // without expansion
-                                       else if (!val.empty()) {
+                                       else if (optkey == "second" && second) {
+                                               int newcounter = 0;
+                                               ret << expandFormat(ifpart, xrefs, newcounter, buf,
+                                                       ci, next);
+                                       } else if (!val.empty()) {
                                                int newcounter = 0;
                                                ret << expandFormat(ifpart, xrefs, newcounter, buf,
                                                        ci, next);
@@ -751,10 +1012,17 @@ docstring BibTeXInfo::getValueForKey(string const & oldkey, Buffer const & buf,
                // FIXME: dialog, textbefore and textafter have nothing to do with this
                if (key == "dialog" && ci.context == CiteItem::Dialog)
                        ret = from_ascii("x"); // any non-empty string will do
+               else if (key == "export" && ci.context == CiteItem::Export)
+                       ret = from_ascii("x"); // any non-empty string will do
                else if (key == "ifstar" && ci.Starred)
                        ret = from_ascii("x"); // any non-empty string will do
+               else if (key == "ifqualified" && ci.isQualified)
+                       ret = from_ascii("x"); // any non-empty string will do
                else if (key == "entrytype")
                        ret = entry_type_;
+               else if (prefixIs(key, "ifentrytype:")
+                        && from_ascii(key.substr(12)) == entry_type_)
+                       ret = from_ascii("x"); // any non-empty string will do
                else if (key == "key")
                        ret = bib_key_;
                else if (key == "label")
@@ -763,35 +1031,75 @@ docstring BibTeXInfo::getValueForKey(string const & oldkey, Buffer const & buf,
                        ret = modifier_;
                else if (key == "numericallabel")
                        ret = cite_number_;
-               else if (key == "shortauthor")
-                       // When shortauthor is not defined, jurabib automatically
-                       // provides jurabib-style abbreviated author names. We do
-                       // this as well.
-                       ret = getAbbreviatedAuthor(&buf, true);
-               else if (key == "shorttitle") {
-                       // When shorttitle is not defined, jurabib uses for `article'
-                       // and `periodical' entries the form `journal volume [year]'
-                       // and for other types of entries it uses the `title' field.
-                       if (entry_type_ == "article" || entry_type_ == "periodical")
-                               ret = operator[]("journal") + " " + operator[]("volume")
-                                       + " [" + operator[]("year") + "]";
-                       else
-                               ret = operator[]("title");
-               else if (key == "abbrvauthor") {
-                       // Special key to provide abbreviated author names,
+               else if (prefixIs(key, "ifmultiple:")) {
+                       // Return whether we have multiple authors
+                       docstring const kind = operator[](from_ascii(key.substr(11)));
+                       if (multipleAuthors(kind))
+                               ret = from_ascii("x"); // any non-empty string will do
+               }
+               else if (prefixIs(key, "abbrvnames:")) {
+                       // Special key to provide abbreviated name list,
+                       // with respect to maxcitenames. Suitable for Bibliography
+                       // beginnings.
+                       docstring const kind = operator[](from_ascii(key.substr(11)));
+                       ret = getAuthorList(&buf, kind, false, false, true);
+                       if (ci.forceUpperCase && isLowerCase(ret[0]))
+                               ret[0] = uppercase(ret[0]);
+               } else if (prefixIs(key, "fullnames:")) {
+                       // Return a full name list. Suitable for Bibliography
+                       // beginnings.
+                       docstring const kind = operator[](from_ascii(key.substr(10)));
+                       ret = getAuthorList(&buf, kind, true, false, true);
+                       if (ci.forceUpperCase && isLowerCase(ret[0]))
+                               ret[0] = uppercase(ret[0]);
+               } else if (prefixIs(key, "forceabbrvnames:")) {
+                       // Special key to provide abbreviated name lists,
+                       // irrespective of maxcitenames. Suitable for Bibliography
+                       // beginnings.
+                       docstring const kind = operator[](from_ascii(key.substr(15)));
+                       ret = getAuthorList(&buf, kind, false, true, true);
+                       if (ci.forceUpperCase && isLowerCase(ret[0]))
+                               ret[0] = uppercase(ret[0]);
+               } else if (prefixIs(key, "abbrvbynames:")) {
+                       // Special key to provide abbreviated name list,
+                       // with respect to maxcitenames. Suitable for further names inside a
+                       // bibliography item // (such as "ed. by ...")
+                       docstring const kind = operator[](from_ascii(key.substr(11)));
+                       ret = getAuthorList(&buf, kind, false, false, true, false);
+                       if (ci.forceUpperCase && isLowerCase(ret[0]))
+                               ret[0] = uppercase(ret[0]);
+               } else if (prefixIs(key, "fullbynames:")) {
+                       // Return a full name list. Suitable for further names inside a
+                       // bibliography item // (such as "ed. by ...")
+                       docstring const kind = operator[](from_ascii(key.substr(10)));
+                       ret = getAuthorList(&buf, kind, true, false, true, false);
+                       if (ci.forceUpperCase && isLowerCase(ret[0]))
+                               ret[0] = uppercase(ret[0]);
+               } else if (prefixIs(key, "forceabbrvbynames:")) {
+                       // Special key to provide abbreviated name lists,
+                       // irrespective of maxcitenames. Suitable for further names inside a
+                       // bibliography item // (such as "ed. by ...")
+                       docstring const kind = operator[](from_ascii(key.substr(15)));
+                       ret = getAuthorList(&buf, kind, false, true, true, false);
+                       if (ci.forceUpperCase && isLowerCase(ret[0]))
+                               ret[0] = uppercase(ret[0]);
+               } else if (key == "abbrvciteauthor") {
+                       // Special key to provide abbreviated author or
+                       // editor names (suitable for citation labels),
                        // with respect to maxcitenames.
-                       ret = getAuthorList(&buf, false, false);
+                       ret = getAuthorOrEditorList(&buf, false, false);
                        if (ci.forceUpperCase && isLowerCase(ret[0]))
                                ret[0] = uppercase(ret[0]);
-               } else if (key == "fullauthor") {
-                       // Return a full author list
-                       ret = getAuthorList(&buf, true, false);
+               } else if (key == "fullciteauthor") {
+                       // Return a full author or editor list (for citation labels)
+                       ret = getAuthorOrEditorList(&buf, true, false);
                        if (ci.forceUpperCase && isLowerCase(ret[0]))
                                ret[0] = uppercase(ret[0]);
-               } else if (key == "forceabbrvauthor") {
-                       // Special key to provide abbreviated author names,
+               } else if (key == "forceabbrvciteauthor") {
+                       // Special key to provide abbreviated author or
+                       // editor names (suitable for citation labels),
                        // irrespective of maxcitenames.
-                       ret = getAuthorList(&buf, false, true);
+                       ret = getAuthorOrEditorList(&buf, false, true);
                        if (ci.forceUpperCase && isLowerCase(ret[0]))
                                ret[0] = uppercase(ret[0]);
                } else if (key == "bibentry") {
@@ -799,13 +1107,17 @@ docstring BibTeXInfo::getValueForKey(string const & oldkey, Buffer const & buf,
                        CiteEngineType const engine_type = buf.params().citeEngineType();
                        DocumentClass const & dc = buf.params().documentClass();
                        docstring const & format =
-                               from_utf8(dc.getCiteFormat(engine_type, to_utf8(entry_type_)));
+                               from_utf8(dc.getCiteFormat(engine_type, to_utf8(entry_type_), false));
                        int counter = 0;
                        ret = expandFormat(format, xrefs, counter, buf, ci, false, false);
                } else if (key == "textbefore")
                        ret = ci.textBefore;
                else if (key == "textafter")
                        ret = ci.textAfter;
+               else if (key == "curpretext")
+                       ret = ci.getPretexts()[bib_key_];
+               else if (key == "curposttext")
+                       ret = ci.getPosttexts()[bib_key_];
                else if (key == "year")
                        ret = getYear();
        }
@@ -914,13 +1226,13 @@ vector<docstring> const BiblioInfo::getEntries() const
 }
 
 
-docstring const BiblioInfo::getAuthorList(docstring const & key, Buffer const & buf) const
+docstring const BiblioInfo::getAuthorOrEditorList(docstring const & key, Buffer const & buf) const
 {
        BiblioInfo::const_iterator it = find(key);
        if (it == end())
                return docstring();
        BibTeXInfo const & data = it->second;
-       return data.getAuthorList(&buf, false);
+       return data.getAuthorOrEditorList(&buf, false);
 }
 
 
@@ -1011,11 +1323,11 @@ docstring const BiblioInfo::getLabel(vector<docstring> keys,
 
        CiteEngineType const engine_type = buf.params().citeEngineType();
        DocumentClass const & dc = buf.params().documentClass();
-       docstring const & format = from_utf8(dc.getCiteFormat(engine_type, style, "cite"));
+       docstring const & format = from_utf8(dc.getCiteFormat(engine_type, style, false, "cite"));
        docstring ret = format;
        vector<docstring>::const_iterator key = keys.begin();
        vector<docstring>::const_iterator ken = keys.end();
-       for (; key != ken; ++key) {
+       for (int i = 0; key != ken; ++key, ++i) {
                BiblioInfo::const_iterator it = find(*key);
                BibTeXInfo empty_data;
                empty_data.key(*key);
@@ -1055,21 +1367,21 @@ bool BiblioInfo::isBibtex(docstring const & key) const
 }
 
 
-vector<docstring> const BiblioInfo::getCiteStrings(
+BiblioInfo::CiteStringMap const BiblioInfo::getCiteStrings(
        vector<docstring> const & keys, vector<CitationStyle> const & styles,
        Buffer const & buf, CiteItem const & ci) const
 {
        if (empty())
-               return vector<docstring>();
+               return vector<pair<docstring,docstring>>();
 
        string style;
-       vector<docstring> vec(styles.size());
-       for (size_t i = 0; i != vec.size(); ++i) {
+       CiteStringMap csm(styles.size());
+       for (size_t i = 0; i != csm.size(); ++i) {
                style = styles[i].name;
-               vec[i] = getLabel(keys, buf, style, ci);
+               csm[i] = make_pair(from_ascii(style), getLabel(keys, buf, style, ci));
        }
 
-       return vec;
+       return csm;
 }
 
 
@@ -1086,8 +1398,8 @@ namespace {
 // used in xhtml to sort a list of BibTeXInfo objects
 bool lSorter(BibTeXInfo const * lhs, BibTeXInfo const * rhs)
 {
-       docstring const lauth = lhs->getAuthorList();
-       docstring const rauth = rhs->getAuthorList();
+       docstring const lauth = lhs->getAuthorOrEditorList();
+       docstring const rauth = rhs->getAuthorOrEditorList();
        docstring const lyear = lhs->getYear();
        docstring const ryear = rhs->getYear();
        docstring const ltitl = lhs->operator[]("title");
@@ -1154,7 +1466,7 @@ void BiblioInfo::makeCitationLabels(Buffer const & buf)
        // used to remember the last one we saw
        // we'll be comparing entries to see if we need to add
        // modifiers, like "1984a"
-       map<docstring, BibTeXInfo>::iterator last;
+       map<docstring, BibTeXInfo>::iterator last = bimap_.end();
 
        vector<docstring>::const_iterator it = cited_entries_.begin();
        vector<docstring>::const_iterator const en = cited_entries_.end();
@@ -1169,13 +1481,11 @@ void BiblioInfo::makeCitationLabels(Buffer const & buf)
                        docstring const num = convert<docstring>(++keynumber);
                        entry.setCiteNumber(num);
                } else {
-                       // coverity complains about our derefercing the iterator last,
-                       // which was not initialized above. but it does get initialized
-                       // after the first time through the loop, which is the point of
-                       // the first test.
-                       // coverity[FORWARD_NULL]
-                       if (it != cited_entries_.begin()
-                           && entry.getAuthorList() == last->second.getAuthorList()
+                       // The first test here is checking whether this is the first
+                       // time through the loop. If so, then we do not have anything
+                       // with which to compare.
+                       if (last != bimap_.end()
+                           && entry.getAuthorOrEditorList() == last->second.getAuthorOrEditorList()
                            // we access the year via getYear() so as to get it from the xref,
                            // if we need to do so
                            && getYear(entry.key()) == getYear(last->second.key())) {
@@ -1207,7 +1517,7 @@ void BiblioInfo::makeCitationLabels(Buffer const & buf)
                if (numbers) {
                        entry.label(entry.citeNumber());
                } else {
-                       docstring const auth = entry.getAuthorList(&buf, false);
+                       docstring const auth = entry.getAuthorOrEditorList(&buf, false);
                        // we do it this way so as to access the xref, if necessary
                        // note that this also gives us the modifier
                        docstring const year = getYear(*it, buf, true);