]> git.lyx.org Git - lyx.git/blobdiff - src/BiblioInfo.cpp
Correctly set language after intitle paragraphs
[lyx.git] / src / BiblioInfo.cpp
index 4220fe00212a63444cb2eee485e694ee9fe3f978..c723b9ad398465f483b5a24d399042ffd9a0f61e 100644 (file)
@@ -47,40 +47,155 @@ namespace lyx {
 
 namespace {
 
-// gets the "prename" and "family name" from an author-type string
-pair<docstring, docstring> nameParts(docstring const & name)
+// Remove placeholders from names
+docstring renormalize(docstring const & input)
 {
-       if (name.empty())
-               return make_pair(docstring(), docstring());
+       docstring res = subst(input, from_ascii("$$space!"), from_ascii(" "));
+       return subst(res, from_ascii("$$comma!"), from_ascii(","));
+}
+
 
-       // 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.
+// 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;
+};
+
+
+// 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 jr. part or not, it's always
-               // the first and last item (reversed)
-               return make_pair(pieces.back(), pieces.front());
+       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. We're going to
-       // include the "von" part. This isn't perfect.
+       // OK, so now we want to look for the last name.
        // Split on spaces, to get various tokens.
        pieces = getVectorFromString(name, from_ascii(" "));
-       // If we only get two, assume the last one is the last name
-       if (pieces.size() <= 2)
-               return make_pair(pieces.front(), pieces.back());
+       // 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 or an opening group {.
+       // 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 (isLower(c) || c == '{')
+               // 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
@@ -88,10 +203,10 @@ pair<docstring, docstring> nameParts(docstring const & name)
                prename += *it;
        }
 
-       if (it == en) // we never found a "von" or group
-               return make_pair(prename, pieces.back());
-
-       // reconstruct the family name
+       // 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) {
@@ -101,7 +216,10 @@ pair<docstring, docstring> nameParts(docstring const & name)
                        first = false;
                surname += *it;
        }
-       return make_pair(prename, surname);
+       res.prename = renormalize(prename);
+       res.prefix = renormalize(parseSurname(surname).first);
+       res.surname = renormalize(parseSurname(surname).second);
+       return res;
 }
 
 
@@ -109,20 +227,95 @@ 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).first;
-       docstring const surname = nameParts(name).second;
-       docstring result = from_ascii(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;
+       // Changing the first parameter of regex_match() may corrupt the
+       // second one. In this case we use the temporary string tmp.
+       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)) {
+               string tmp = sub.str(1);
+               if (!suffix.empty())
+                       tmp += sub.str(3);
+               res = tmp + sub.str(5);
+       }
+       if (regex_match(res, sub, reg3)) {
+               string tmp = sub.str(1);
+               if (!prefix.empty())
+                       tmp += sub.str(3);
+               res = tmp + 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;
+       }
+
+       // 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)
 {
-       vector<docstring> const authors =
-               getVectorFromString(author, from_ascii(" and "));
-       return authors.size() > 1;
+       return getAuthors(author).size() > 1;
 }
 
 
@@ -187,7 +380,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;
@@ -274,7 +478,7 @@ docstring processRichtext(docstring const & str, bool richtext)
        return ret;
 }
 
-} // anon namespace
+} // namespace
 
 
 //////////////////////////////////////////////////////////////////////
@@ -284,11 +488,12 @@ docstring processRichtext(docstring const & str, bool richtext)
 //////////////////////////////////////////////////////////////////////
 
 BibTeXInfo::BibTeXInfo(docstring const & key, docstring const & type)
-       : is_bibtex_(true), bib_key_(key), entry_type_(type), info_(),
+       : is_bibtex_(true), bib_key_(key), num_bib_key_(0), entry_type_(type), info_(),
          modifier_(0)
 {}
 
 
+
 docstring const BibTeXInfo::getAuthorOrEditorList(Buffer const * buf,
                                          bool full, bool forceshort) const
 {
@@ -300,9 +505,9 @@ docstring const BibTeXInfo::getAuthorOrEditorList(Buffer const * buf,
 }
 
 
-docstring const BibTeXInfo::getAuthorList(Buffer const * buf, docstring author,
-                                         bool full, bool forceshort, bool allnames,
-                                         bool beginning) const
+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 ?
@@ -319,19 +524,21 @@ docstring const BibTeXInfo::getAuthorList(Buffer const * buf, docstring author,
                        // in this case, we didn't find a "(",
                        // so we don't have author (year)
                        return docstring();
+               if (full) {
+                       // Natbib syntax is "Jones et al.(1990)Jones, Baker, and Williams"
+                       docstring const fullauthors = trim(rsplit(remainder, ')'));
+                       if (!fullauthors.empty())
+                               return fullauthors;
+               }
                return authors;
        }
 
        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 "));
+       // Try to split the author list
+       vector<docstring> const authors = getAuthors(author);
 
        docstring retval;
 
@@ -340,31 +547,33 @@ docstring const BibTeXInfo::getAuthorList(Buffer const * buf, docstring author,
 
        // These are defined in the styles
        string const etal =
-               buf ? buf->params().documentClass().getCiteMacro(engine_type, "_etal")
+               buf ? buf->params().documentClass().getCiteMacro(engine_type, "B_etal")
                    : " et al.";
        string const namesep =
-               buf ? buf->params().documentClass().getCiteMacro(engine_type, "_namesep")
+               buf ? buf->params().documentClass().getCiteMacro(engine_type, "B_namesep")
                   : ", ";
        string const lastnamesep =
-               buf ? buf->params().documentClass().getCiteMacro(engine_type, "_lastnamesep")
+               buf ? buf->params().documentClass().getCiteMacro(engine_type, "B_lastnamesep")
                    : ", and ";
        string const pairnamesep =
-               buf ? buf->params().documentClass().getCiteMacro(engine_type, "_pairnamesep")
+               buf ? buf->params().documentClass().getCiteMacro(engine_type, "B_pairnamesep")
                     : " and ";
        string firstnameform =
                        buf ? buf->params().documentClass().getCiteMacro(engine_type, "!firstnameform")
-                            : "%surname%, %prename%";
+                            : "{%prefix%[[%prefix% ]]}%surname%{%suffix%[[, %suffix%]]}{%prename%[[, %prename%]]}";
        if (!beginning)
                firstnameform = buf ? buf->params().documentClass().getCiteMacro(engine_type, "!firstbynameform")
-                                            : "%prename% %surname%";
+                                            : "%prename% {%prefix%[[%prefix% ]]}%surname%{%suffix%[[, %suffix%]]}";
        string othernameform = buf ? buf->params().documentClass().getCiteMacro(engine_type, "!othernameform")
-                            : "%surname%, %prename%";
+                            : "{%prefix%[[%prefix% ]]}%surname%{%suffix%[[, %suffix%]]}{%prename%[[, %prename%]]}";
        if (!beginning)
                othernameform = buf ? buf->params().documentClass().getCiteMacro(engine_type, "!otherbynameform")
-                                            : "%prename% %surname%";
+                                            : "%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();
@@ -389,13 +598,13 @@ docstring const BibTeXInfo::getAuthorList(Buffer const * buf, docstring author,
                        retval += (i == 0) ? constructName(*it, firstnameform)
                                : constructName(*it, othernameform);
                else
-                       retval += nameParts(*it).second;
+                       retval += constructName(*it, citenameform);
        }
        if (shorten) {
                if (allnames)
                        retval = constructName(authors[0], firstnameform) + (buf ? buf->B_(etal) : from_ascii(etal));
                else
-                       retval = nameParts(authors[0]).second + (buf ? buf->B_(etal) : from_ascii(etal));
+                       retval = constructName(authors[0], citenameform) + (buf ? buf->B_(etal) : from_ascii(etal));
        }
 
        return convertLaTeXCommands(retval);
@@ -417,7 +626,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))
@@ -553,11 +764,11 @@ docstring parseOptions(docstring const & format, string & optkey,
 }
 
 
-} // anon namespace
+} // namespace
 
 /* FIXME
 Bug #9131 revealed an oddity in how we are generating citation information
-when more than one key is given. We end up building a longer and longer format 
+when more than one key is given. We end up building a longer and longer format
 string as we go, which we then have to re-parse, over and over and over again,
 rather than generating the information for the individual keys and then putting
 all of that together. We do that to deal with the way separators work, from what
@@ -606,13 +817,20 @@ docstring BibTeXInfo::expandFormat(docstring const & format,
                                        fmt = from_utf8(val) + fmt.substr(1);
                                        counter += 1;
                                        continue;
-                               } else if (key[0] == '_') {
-                                       // a translatable bit
+                               } else if (prefixIs(key, "B_")) {
+                                       // a translatable bit (to the Buffer language)
                                        string const val =
                                                buf.params().documentClass().getCiteMacro(engine_type, key);
                                        docstring const trans =
                                                translateIfPossible(from_utf8(val), buf.params().language->code());
                                        ret << trans;
+                               } else if (key[0] == '_') {
+                                       // a translatable bit (to the GUI language)
+                                       string const val =
+                                               buf.params().documentClass().getCiteMacro(engine_type, key);
+                                       docstring const trans =
+                                               translateIfPossible(from_utf8(val));
+                                       ret << trans;
                                } else {
                                        docstring const val =
                                                getValueForKey(key, buf, ci, xrefs, max_keysize);
@@ -911,11 +1129,33 @@ docstring BibTeXInfo::getValueForKey(string const & oldkey, Buffer const & buf,
                        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")
+               else if (key == "curpretext") {
+                       vector<pair<docstring, docstring>> pres = ci.getPretexts();
+                       vector<pair<docstring, docstring>>::iterator it = pres.begin();
+                       int numkey = 1;
+                       for (; it != pres.end() ; ++it) {
+                               if ((*it).first == bib_key_ && numkey == num_bib_key_) {
+                                       ret = (*it).second;
+                                       pres.erase(it);
+                                       break;
+                               }
+                               if ((*it).first == bib_key_)
+                                       ++numkey;
+                       }
+               } else if (key == "curposttext") {
+                       vector<pair<docstring, docstring>> posts = ci.getPosttexts();
+                       vector<pair<docstring, docstring>>::iterator it = posts.begin();
+                       int numkey = 1;
+                       for (; it != posts.end() ; ++it) {
+                               if ((*it).first == bib_key_ && numkey == num_bib_key_) {
+                                       ret = (*it).second;
+                                       posts.erase(it);
+                                       break;
+                               }
+                               if ((*it).first == bib_key_)
+                                       ++numkey;
+                       }
+               } else if (key == "year")
                        ret = getYear();
        }
 
@@ -945,7 +1185,7 @@ public:
        }
 };
 
-} // namespace anon
+} // namespace
 
 
 vector<docstring> const BiblioInfo::getXRefs(BibTeXInfo const & data, bool const nested) const
@@ -1056,10 +1296,8 @@ docstring const BiblioInfo::getYear(docstring const & key, bool use_modifier) co
                if (xrefs.empty())
                        // no luck
                        return docstring();
-               vector<docstring>::const_iterator it = xrefs.begin();
-               vector<docstring>::const_iterator en = xrefs.end();
-               for (; it != en; ++it) {
-                       BiblioInfo::const_iterator const xrefit = find(*it);
+               for (docstring const & xref : xrefs) {
+                       BiblioInfo::const_iterator const xrefit = find(xref);
                        if (xrefit == end())
                                continue;
                        BibTeXInfo const & xref_data = xrefit->second;
@@ -1093,14 +1331,10 @@ docstring const BiblioInfo::getInfo(docstring const & key,
        BibTeXInfo const & data = it->second;
        BibTeXInfoList xrefptrs;
        vector<docstring> const xrefs = getXRefs(data);
-       if (!xrefs.empty()) {
-               vector<docstring>::const_iterator it = xrefs.begin();
-               vector<docstring>::const_iterator en = xrefs.end();
-               for (; it != en; ++it) {
-                       BiblioInfo::const_iterator const xrefit = find(*it);
-                       if (xrefit != end())
-                               xrefptrs.push_back(&(xrefit->second));
-               }
+       for (docstring const & xref : getXRefs(data)) {
+               BiblioInfo::const_iterator const xrefit = find(xref);
+               if (xrefit != end())
+                       xrefptrs.push_back(&(xrefit->second));
        }
        return data.getInfo(xrefptrs, buf, ci);
 }
@@ -1114,9 +1348,15 @@ docstring const BiblioInfo::getLabel(vector<docstring> keys,
        LASSERT(max_size >= 16, max_size = 16);
 
        // we can't display more than 10 of these, anyway
+       // but since we truncate in the middle,
+       // we need to split into two halfs.
        bool const too_many_keys = keys.size() > 10;
-       if (too_many_keys)
-               keys.resize(10);
+       vector<docstring> lkeys;
+       if (too_many_keys) {
+               lkeys.insert(lkeys.end(), keys.end() - 5, keys.end());
+               keys.resize(5);
+               keys.insert(keys.end(), lkeys.begin(), lkeys.end());
+       }
 
        CiteEngineType const engine_type = buf.params().citeEngineType();
        DocumentClass const & dc = buf.params().documentClass();
@@ -1124,7 +1364,14 @@ docstring const BiblioInfo::getLabel(vector<docstring> keys,
        docstring ret = format;
        vector<docstring>::const_iterator key = keys.begin();
        vector<docstring>::const_iterator ken = keys.end();
+       vector<docstring> handled_keys;
        for (int i = 0; key != ken; ++key, ++i) {
+               handled_keys.push_back(*key);
+               int n = 0;
+               for (auto const k : handled_keys) {
+                       if (k == *key)
+                               ++n;
+               }
                BiblioInfo::const_iterator it = find(*key);
                BibTeXInfo empty_data;
                empty_data.key(*key);
@@ -1132,23 +1379,18 @@ docstring const BiblioInfo::getLabel(vector<docstring> keys,
                vector<BibTeXInfo const *> xrefptrs;
                if (it != end()) {
                        data = it->second;
-                       vector<docstring> const xrefs = getXRefs(data);
-                       if (!xrefs.empty()) {
-                               vector<docstring>::const_iterator it = xrefs.begin();
-                               vector<docstring>::const_iterator en = xrefs.end();
-                               for (; it != en; ++it) {
-                                       BiblioInfo::const_iterator const xrefit = find(*it);
-                                       if (xrefit != end())
-                                               xrefptrs.push_back(&(xrefit->second));
-                               }
+                       for (docstring const & xref : getXRefs(data)) {
+                               BiblioInfo::const_iterator const xrefit = find(xref);
+                               if (xrefit != end())
+                                       xrefptrs.push_back(&(xrefit->second));
                        }
                }
+               data.numKey(n);
                ret = data.getLabel(xrefptrs, buf, ret, ci, key + 1 != ken, i == 1);
        }
 
-       if (too_many_keys)
-               ret.push_back(0x2026);//HORIZONTAL ELLIPSIS
-       support::truncateWithEllipsis(ret, max_size);
+       support::truncateWithEllipsis(ret, max_size, true);
+
        return ret;
 }
 
@@ -1164,21 +1406,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;
 }
 
 
@@ -1206,7 +1448,7 @@ bool lSorter(BibTeXInfo const * lhs, BibTeXInfo const * rhs)
                || (lauth == rauth && lyear == ryear && ltitl < rtitl);
 }
 
-}
+} // namespace
 
 
 void BiblioInfo::collectCitedEntries(Buffer const & buf)
@@ -1263,7 +1505,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();
@@ -1278,12 +1520,10 @@ 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()
+                       // 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