]> git.lyx.org Git - lyx.git/blobdiff - src/frontends/controllers/biblio.C
fix crash due to invalidated iterator
[lyx.git] / src / frontends / controllers / biblio.C
index 37db0f17492cea19f44158707d4c5737af4d1bdf..32d4f8af3610f172bc9b250fe5d6afe14d716add 100644 (file)
-/* This file is part of
- * ======================================================
- *
- *           LyX, The Document Processor
- *
- *           Copyright 2001 The LyX Team.
+/**
+ * \file biblio.C
+ * This file is part of LyX, the document processor.
+ * Licence details can be found in the file COPYING.
  *
- * ======================================================
+ * \author Angus Leeming
+ * \author Herbert Voß
  *
- * \file biblio.C
- * \author Angus Leeming <leeming@lyx.org>
- * \author Herbert Voss <voss@perce.de>
+ * Full author contact details are available in file CREDITS.
  */
 
 #include <config.h>
 
-#ifdef __GNUG__
-#pragma implementation
-#endif
-
-#include "LString.h"
 #include "biblio.h"
-#include "gettext.h" // for _()
-#include "helper_funcs.h"
+
+#include "buffer.h"
+#include "bufferparams.h"
+#include "gettext.h"
+
 #include "support/lstrings.h"
-#include "support/LAssert.h"
 
 #include <boost/regex.hpp>
 
 #include <algorithm>
+#include <sstream>
 
-using std::find;
-using std::min;
+using std::string;
+using std::ostringstream;
 using std::vector;
-using std::sort;
-
-namespace biblio
-{
-
-namespace {
-
-using namespace biblio;
-
-char const * const citeCommands[] = {
-       "cite", "citet", "citep", "citealt", "citealp", "citeauthor",
-       "citeyear", "citeyearpar" };
 
-unsigned int const nCiteCommands =
-       sizeof(citeCommands) / sizeof(char *);
-
-CiteStyle const citeStyles[] = {
-       CITE, CITET, CITEP, CITEALT, CITEALP,
-       CITEAUTHOR, CITEYEAR, CITEYEARPAR };
 
-unsigned int const nCiteStyles =
-       sizeof(citeStyles) / sizeof(CiteStyle);
+namespace lyx {
 
-CiteStyle const citeStylesFull[] = {
-       CITET, CITEP, CITEALT, CITEALP, CITEAUTHOR };
+using support::ascii_lowercase;
+using support::bformat;
+using support::compare_ascii_no_case;
+using support::contains;
+using support::getVectorFromString;
+using support::ltrim;
+using support::prefixIs;
+using support::rtrim;
+using support::split;
+using support::subst;
+using support::token;
+using support::trim;
 
-unsigned int const nCiteStylesFull =
-       sizeof(citeStylesFull) / sizeof(CiteStyle);
+namespace biblio {
 
-CiteStyle const citeStylesUCase[] = {
-       CITET, CITEP, CITEALT, CITEALP, CITEAUTHOR };
+namespace {
 
-unsigned int const nCiteStylesUCase =
-       sizeof(citeStylesUCase) / sizeof(CiteStyle);
+vector<string> const init_possible_cite_commands()
+{
+       char const * const pos[] = {
+               "cite",
+               "citet", "citep", "citealt", "citealp",
+               "citeauthor", "citeyear", "citeyearpar",
+               "citet*", "citep*", "citealt*", "citealp*", "citeauthor*",
+               "Citet",  "Citep",  "Citealt",  "Citealp",  "Citeauthor",
+               "Citet*", "Citep*", "Citealt*", "Citealp*", "Citeauthor*",
+               "fullcite",
+               "footcite", "footcitet", "footcitep", "footcitealt",
+               "footcitealp", "footciteauthor", "footciteyear",
+               "footciteyearpar",
+               "citefield",
+               "citetitle",
+               "cite*"
+       };
+       size_t const size_pos = sizeof(pos) / sizeof(pos[0]);
+
+       return vector<string>(pos, pos + size_pos);
+}
 
 
-// The functions doing the dirty work for the search.
-vector<string>::const_iterator
-simpleSearch(InfoMap const & theMap,
-            vector<string> const & keys,
-            string const & expr,
-            vector<string>::const_iterator start,
-            Direction dir,
-            bool caseSensitive)
+vector<string> const & possible_cite_commands()
 {
-       string tmp = expr;
-       if (!caseSensitive)
-               tmp = lowercase(tmp);
-
-       vector<string> searchwords = getVectorFromString(tmp, " ");
-
-       // Loop over all keys from start...
-       for (vector<string>::const_iterator it = start;
-            // End condition is direction-dependent.
-            (dir == FORWARD) ? (it<keys.end()) : (it>=keys.begin());
-            // increment is direction-dependent.
-            (dir == FORWARD) ? (++it) : (--it)) {
-
-               string data = (*it);
-               InfoMap::const_iterator info = theMap.find(*it);
-               if (info != theMap.end())
-                       data += " " + info->second;
-               if (!caseSensitive)
-                       data = lowercase(data);
-
-               bool found = true;
-
-               // Loop over all search words...
-               for (vector<string>::const_iterator sit = searchwords.begin();
-                    sit != searchwords.end(); ++sit) {
-                       if (data.find(*sit) == string::npos) {
-                               found = false;
-                               break;
-                       }
-               }
+       static vector<string> const pos = init_possible_cite_commands();
+       return pos;
+}
 
-               if (found) return it;
-       }
 
-       return keys.end();
+bool is_possible_cite_command(string const & input)
+{
+       vector<string> const & possibles = possible_cite_commands();
+       vector<string>::const_iterator const end = possibles.end();
+       return std::find(possibles.begin(), end, input) != end;
 }
 
 
-vector<string>::const_iterator
-regexSearch(InfoMap const & theMap,
-           vector<string> const & keys,
-           string const & expr,
-           vector<string>::const_iterator start,
-           Direction dir)
+string const default_cite_command(CiteEngine engine)
 {
-       boost::regex reg(expr);
+       string str;
+       switch (engine) {
+       case ENGINE_BASIC:
+               str = "cite";
+               break;
+       case ENGINE_NATBIB_AUTHORYEAR:
+               str = "citet";
+               break;
+       case ENGINE_NATBIB_NUMERICAL:
+               str = "citep";
+               break;
+       case ENGINE_JURABIB:
+               str = "cite";
+               break;
+       }
+       return str;
+}
 
-       for (vector<string>::const_iterator it = start;
-            // End condition is direction-dependent.
-            (dir == FORWARD) ? (it < keys.end()) : (it >= keys.begin());
-            // increment is direction-dependent.
-            (dir == FORWARD) ? (++it) : (--it)) {
+} // namespace anon
 
-               string data = (*it);
-               InfoMap::const_iterator info = theMap.find(*it);
-               if (info != theMap.end())
-                       data += " " + info->second;
 
-               if (boost::regex_match(data, reg)) {
-                       return it;
-               }
+string const asValidLatexCommand(string const & input,
+                                CiteEngine_enum const & engine)
+{
+       string const default_str = default_cite_command(engine);
+       if (!is_possible_cite_command(input))
+               return default_str;
+
+       string output;
+       switch (engine) {
+       case ENGINE_BASIC:
+               output = default_str;
+               break;
+
+       case ENGINE_NATBIB_AUTHORYEAR:
+       case ENGINE_NATBIB_NUMERICAL:
+               if (input == "cite" || input == "citefield" ||
+                   input == "citetitle" || input == "cite*")
+                       output = default_str;
+               else if (prefixIs(input, "foot"))
+                       output = input.substr(4);
+               else
+                       output = input;
+               break;
+
+       case ENGINE_JURABIB: {
+               // Jurabib does not support the 'uppercase' natbib style.
+               if (input[0] == 'C')
+                       output = string(1, 'c') + input.substr(1);
+               else
+                       output = input;
+
+               // Jurabib does not support the 'full' natbib style.
+               string::size_type const n = output.size() - 1;
+               if (output != "cite*" && output[n] == '*')
+                       output = output.substr(0, n);
+
+               break;
+       }
        }
 
-       return keys.end();
+       return output;
 }
 
 
-} // namespace anon
-
-
 string const familyName(string const & name)
 {
        // Very simple parser
@@ -157,35 +163,37 @@ string const familyName(string const & name)
        // "Surname, F."
        // "FirstName Surname"
        // "F. Surname"
-       string::size_type idx = fname.find(",");
+       string::size_type idx = fname.find(',');
        if (idx != string::npos)
                return ltrim(fname.substr(0, idx));
-       idx = fname.rfind(".");
+       idx = fname.rfind('.');
        if (idx != string::npos)
                fname = ltrim(fname.substr(idx + 1));
        // test if we have a LaTeX Space in front
        if (fname[0] == '\\')
                return fname.substr(2);
 
-       return fname;
+       return rtrim(fname);
 }
 
 
 string const getAbbreviatedAuthor(InfoMap const & map, string const & key)
 {
-       lyx::Assert(!map.empty());
+       BOOST_ASSERT(!map.empty());
 
        InfoMap::const_iterator it = map.find(key);
        if (it == map.end())
                return string();
+       string const & data = it->second;
 
-       string::size_type const pos = it->second.find("TheBibliographyRef");
+       // Is the entry a BibTeX one or one from lyx-layout "bibliography"?
+       string::size_type const pos = data.find("TheBibliographyRef");
        if (pos != string::npos) {
                if (pos <= 2) {
                        return string();
                }
 
-               string const opt = trim(it->second.substr(0, pos - 1));
+               string const opt = trim(data.substr(0, pos - 1));
                if (opt.empty())
                        return string();
 
@@ -194,54 +202,51 @@ string const getAbbreviatedAuthor(InfoMap const & map, string const & key)
                return authors;
        }
 
-       string author = parseBibTeX(it->second, "author");
+       string author = parseBibTeX(data, "author");
+
        if (author.empty())
-               author = parseBibTeX(it->second, "editor");
+               author = parseBibTeX(data, "editor");
 
        if (author.empty()) {
-               author = parseBibTeX(it->second, "key");
+               author = parseBibTeX(data, "key");
                if (author.empty())
                        author = key;
                return author;
        }
 
-       vector<string> authors = getVectorFromString(author, " and");
-
-       if (!authors.empty()) {
-               author.erase();
+       vector<string> const authors = getVectorFromString(author, " and ");
+       if (authors.empty())
+               return author;
 
-               for (vector<string>::iterator it = authors.begin();
-                    it != authors.end(); ++it) {
-                       *it = familyName(rtrim(*it));
-               }
+       if (authors.size() == 2)
+               return bformat(_("%1$s and %2$s"),
+                       familyName(authors[0]), familyName(authors[1]));
 
-               author = authors[0];
-               if (authors.size() == 2)
-                       author += _(" and ") + authors[1];
-               else if (authors.size() > 2)
-                       author += _(" et al.");
-       }
+       if (authors.size() > 2)
+               return bformat(_("%1$s et al."), familyName(authors[0]));
 
-       return author;
+       return familyName(authors[0]);
 }
 
 
 string const getYear(InfoMap const & map, string const & key)
 {
-       lyx::Assert(!map.empty());
+       BOOST_ASSERT(!map.empty());
 
        InfoMap::const_iterator it = map.find(key);
        if (it == map.end())
                return string();
+       string const & data = it->second;
 
-       string::size_type const pos = it->second.find("TheBibliographyRef");
+       // Is the entry a BibTeX one or one from lyx-layout "bibliography"?
+       string::size_type const pos = data.find("TheBibliographyRef");
        if (pos != string::npos) {
                if (pos <= 2) {
                        return string();
                }
 
                string const opt =
-                       trim(it->second.substr(0, pos - 1));
+                       trim(data.substr(0, pos - 1));
                if (opt.empty())
                        return string();
 
@@ -253,7 +258,7 @@ string const getYear(InfoMap const & map, string const & key)
 
        }
 
-       string year = parseBibTeX(it->second, "year");
+       string year = parseBibTeX(data, "year");
        if (year.empty())
                year = _("No year");
 
@@ -261,64 +266,73 @@ string const getYear(InfoMap const & map, string const & key)
 }
 
 
+namespace {
+
 // A functor for use with std::sort, leading to case insensitive sorting
-struct compareNoCase: public std::binary_function<string, string, bool>
+class compareNoCase: public std::binary_function<string, string, bool>
 {
+public:
        bool operator()(string const & s1, string const & s2) const {
                return compare_ascii_no_case(s1, s2) < 0;
        }
 };
 
+} // namespace anon
+
+
 vector<string> const getKeys(InfoMap const & map)
 {
        vector<string> bibkeys;
-
-       for (InfoMap::const_iterator it = map.begin(); it != map.end(); ++it) {
+       InfoMap::const_iterator it  = map.begin();
+       InfoMap::const_iterator end = map.end();
+       for (; it != end; ++it) {
                bibkeys.push_back(it->first);
        }
 
-       sort(bibkeys.begin(), bibkeys.end(), compareNoCase());
+       std::sort(bibkeys.begin(), bibkeys.end(), compareNoCase());
        return bibkeys;
 }
 
 
 string const getInfo(InfoMap const & map, string const & key)
 {
-       lyx::Assert(!map.empty());
+       BOOST_ASSERT(!map.empty());
 
        InfoMap::const_iterator it = map.find(key);
        if (it == map.end())
                return string();
+       string const & data = it->second;
 
        // is the entry a BibTeX one or one from lyx-layout "bibliography"?
        string const separator("TheBibliographyRef");
-       string::size_type const pos = it->second.find(separator);
+       string::size_type const pos = data.find(separator);
        if (pos != string::npos) {
                string::size_type const pos2 = pos + separator.size();
-               string const info = trim(it->second.substr(pos2));
+               string const info = trim(data.substr(pos2));
                return info;
        }
 
        // Search for all possible "required" keys
-       string author = parseBibTeX(it->second, "author");
+       string author = parseBibTeX(data, "author");
        if (author.empty())
-               author = parseBibTeX(it->second, "editor");
-
-       string year       = parseBibTeX(it->second, "year");
-       string title      = parseBibTeX(it->second, "title");
-       string booktitle  = parseBibTeX(it->second, "booktitle");
-       string chapter    = parseBibTeX(it->second, "chapter");
-       string number     = parseBibTeX(it->second, "number");
-       string volume     = parseBibTeX(it->second, "volume");
-       string pages      = parseBibTeX(it->second, "pages");
-
-       string media      = parseBibTeX(it->second, "journal");
+               author = parseBibTeX(data, "editor");
+
+       string year       = parseBibTeX(data, "year");
+       string title      = parseBibTeX(data, "title");
+       string booktitle  = parseBibTeX(data, "booktitle");
+       string chapter    = parseBibTeX(data, "chapter");
+       string number     = parseBibTeX(data, "number");
+       string volume     = parseBibTeX(data, "volume");
+       string pages      = parseBibTeX(data, "pages");
+       string annote     = parseBibTeX(data, "annote");
+
+       string media      = parseBibTeX(data, "journal");
        if (media.empty())
-               media = parseBibTeX(it->second, "publisher");
+               media = parseBibTeX(data, "publisher");
        if (media.empty())
-               media = parseBibTeX(it->second, "school");
+               media = parseBibTeX(data, "school");
        if (media.empty())
-               media = parseBibTeX(it->second, "institution");
+               media = parseBibTeX(data, "institution");
 
        ostringstream result;
        if (!author.empty())
@@ -339,20 +353,79 @@ string const getInfo(InfoMap const & map, string const & key)
                result << ", pp. " << pages;
        if (!year.empty())
                result << ", " << year;
+       if (!annote.empty())
+               result << "\n\n" << annote;
 
-       string const result_str = rtrim(result.str().c_str());
+       string const result_str = rtrim(result.str());
        if (!result_str.empty())
                return result_str;
 
        // This should never happen (or at least be very unusual!)
-       return it->second;
+       return data;
+}
+
+
+namespace {
+
+// Escape special chars.
+// All characters are literals except: '.|*?+(){}[]^$\'
+// These characters are literals when preceded by a "\", which is done here
+string const escape_special_chars(string const & expr)
+{
+       // Search for all chars '.|*?+(){}[^$]\'
+       // Note that '[' and '\' must be escaped.
+       // This is a limitation of boost::regex, but all other chars in BREs
+       // are assumed literal.
+       boost::RegEx reg("[].|*?+(){}^$\\[\\\\]");
+
+       // $& is a perl-like expression that expands to all of the current match
+       // The '$' must be prefixed with the escape character '\' for
+       // boost to treat it as a literal.
+       // Thus, to prefix a matched expression with '\', we use:
+       return reg.Merge(expr, "\\\\$&");
 }
 
 
+// A functor for use with std::find_if, used to ascertain whether a
+// data entry matches the required regex_
+class RegexMatch : public std::unary_function<string, bool>
+{
+public:
+       // re and icase are used to construct an instance of boost::RegEx.
+       // if icase is true, then matching is insensitive to case
+       RegexMatch(InfoMap const & m, string const & re, bool icase)
+               : map_(m), regex_(re, icase) {}
+
+       bool operator()(string const & key) const {
+               if (!validRE())
+                       return false;
+
+               // the data searched is the key + its associated BibTeX/biblio
+               // fields
+               string data = key;
+               InfoMap::const_iterator info = map_.find(key);
+               if (info != map_.end())
+                       data += ' ' + info->second;
+
+               // Attempts to find a match for the current RE
+               // somewhere in data.
+               return regex_.Search(data);
+       }
+
+       bool validRE() const { return regex_.error_code() == 0; }
+
+private:
+       InfoMap const map_;
+       mutable boost::RegEx regex_;
+};
+
+} // namespace anon
+
+
 vector<string>::const_iterator
 searchKeys(InfoMap const & theMap,
           vector<string> const & keys,
-          string const & expr,
+          string const & search_expr,
           vector<string>::const_iterator start,
           Search type,
           Direction dir,
@@ -362,15 +435,34 @@ searchKeys(InfoMap const & theMap,
        if (start < keys.begin() || start >= keys.end())
                return keys.end();
 
-       string search_expr = trim(expr);
-       if (search_expr.empty())
+       string expr = trim(search_expr);
+       if (expr.empty())
                return keys.end();
 
        if (type == SIMPLE)
-               return simpleSearch(theMap, keys, search_expr, start, dir,
-                                   caseSensitive);
+               // We must escape special chars in the search_expr so that
+               // it is treated as a simple string by boost::regex.
+               expr = escape_special_chars(expr);
 
-       return regexSearch(theMap, keys, search_expr, start, dir);
+       // Build the functor that will be passed to find_if.
+       RegexMatch const match(theMap, expr, !caseSensitive);
+       if (!match.validRE())
+               return keys.end();
+
+       // Search the vector of 'keys' from 'start' for one that matches the
+       // predicate 'match'. Searching can be forward or backward from start.
+       if (dir == FORWARD)
+               return std::find_if(start, keys.end(), match);
+
+       vector<string>::const_reverse_iterator rit(start);
+       vector<string>::const_reverse_iterator rend = keys.rend();
+       rit = std::find_if(rit, rend, match);
+
+       if (rit == rend)
+               return keys.end();
+       // This is correct and always safe.
+       // (See Meyer's Effective STL, Item 28.)
+       return (++rit).base();
 }
 
 
@@ -391,14 +483,18 @@ string const parseBibTeX(string data, string const & findkey)
                string::size_type const idx =
                        dummy.empty() ? string::npos : dummy.find('%');
                if (idx != string::npos)
-                       dummy.erase(idx, string::npos);
+                       // Check if this is really a comment or just "\%"
+                       if (idx == 0 || dummy[idx - 1] != '\\')
+                               dummy.erase(idx, string::npos);
+                       else  //  This is "\%", so just erase the '\'
+                               dummy.erase(idx - 1, 1);
                // do we have a new token or a new line of
                // the same one? In the first case we ignore
                // the \n and in the second we replace it
                // with a space
                if (!dummy.empty()) {
-                       if (!contains(dummy, "="))
-                               data_ += (' ' + dummy);
+                       if (!contains(dummy, '='))
+                               data_ += ' ' + dummy;
                        else
                                data_ += dummy;
                }
@@ -440,7 +536,7 @@ string const parseBibTeX(string data, string const & findkey)
        keyvalue = dummy;
        dummy = token(data, ',', Entries++);
        while (!contains(dummy, '=') && !dummy.empty()) {
-               keyvalue += (',' + dummy);
+               keyvalue += ',' + dummy;
                dummy = token(data, ',', Entries++);
        }
 
@@ -504,21 +600,53 @@ string const parseBibTeX(string data, string const & findkey)
 }
 
 
-CitationStyle const getCitationStyle(string const & command)
+namespace {
+
+
+char const * const citeCommands[] = {
+       "cite", "citet", "citep", "citealt", "citealp", "citeauthor",
+       "citeyear", "citeyearpar" };
+
+unsigned int const nCiteCommands =
+       sizeof(citeCommands) / sizeof(char *);
+
+CiteStyle const citeStyles[] = {
+       CITE, CITET, CITEP, CITEALT, CITEALP,
+       CITEAUTHOR, CITEYEAR, CITEYEARPAR };
+
+unsigned int const nCiteStyles =
+       sizeof(citeStyles) / sizeof(CiteStyle);
+
+CiteStyle const citeStylesFull[] = {
+       CITET, CITEP, CITEALT, CITEALP, CITEAUTHOR };
+
+unsigned int const nCiteStylesFull =
+       sizeof(citeStylesFull) / sizeof(CiteStyle);
+
+CiteStyle const citeStylesUCase[] = {
+       CITET, CITEP, CITEALT, CITEALP, CITEAUTHOR };
+
+unsigned int const nCiteStylesUCase =
+       sizeof(citeStylesUCase) / sizeof(CiteStyle);
+
+} // namespace anon
+
+
+CitationStyle::CitationStyle(string const & command)
+       : style(CITE), full(false), forceUCase(false)
 {
-       if (command.empty()) return CitationStyle();
+       if (command.empty())
+               return;
 
-       CitationStyle cs;
        string cmd = command;
-
        if (cmd[0] == 'C') {
-               cs.forceUCase = true;
+               forceUCase = true;
                cmd[0] = 'c';
        }
 
-       size_t n = cmd.size() - 1;
-       if (cmd[n] == '*') {
-               cs.full = true;
+       string::size_type const n = cmd.size() - 1;
+       if (cmd != "cite" && cmd[n] == '*') {
+               full = true;
                cmd = cmd.substr(0,n);
        }
 
@@ -527,25 +655,23 @@ CitationStyle const getCitationStyle(string const & command)
 
        if (ptr != last) {
                size_t idx = ptr - citeCommands;
-               cs.style = citeStyles[idx];
+               style = citeStyles[idx];
        }
-
-       return cs;
 }
 
 
-string const getCiteCommand(CiteStyle command, bool full, bool forceUCase)
+string const CitationStyle::asLatexStr() const
 {
-       string cite = citeCommands[command];
+       string cite = citeCommands[style];
        if (full) {
                CiteStyle const * last = citeStylesFull + nCiteStylesFull;
-               if (std::find(citeStylesFull, last, command) != last)
-                       cite += "*";
+               if (std::find(citeStylesFull, last, style) != last)
+                       cite += '*';
        }
 
        if (forceUCase) {
                CiteStyle const * last = citeStylesUCase + nCiteStylesUCase;
-               if (std::find(citeStylesUCase, last, command) != last)
+               if (std::find(citeStylesUCase, last, style) != last)
                        cite[0] = 'C';
        }
 
@@ -553,22 +679,40 @@ string const getCiteCommand(CiteStyle command, bool full, bool forceUCase)
 }
 
 
-vector<CiteStyle> const getCiteStyles(bool usingNatbib)
+CiteEngine_enum getEngine(Buffer const & buffer)
 {
-       unsigned int nStyles = 1;
+       return buffer.params().cite_engine;
+}
+
+
+vector<CiteStyle> const getCiteStyles(CiteEngine_enum const & engine)
+{
+       unsigned int nStyles = 0;
        unsigned int start = 0;
-       if (usingNatbib) {
+
+       switch (engine) {
+       case ENGINE_BASIC:
+               nStyles = 1;
+               start = 0;
+               break;
+       case ENGINE_NATBIB_AUTHORYEAR:
+       case ENGINE_NATBIB_NUMERICAL:
                nStyles = nCiteStyles - 1;
                start = 1;
+               break;
+       case ENGINE_JURABIB:
+               nStyles = nCiteStyles;
+               start = 0;
+               break;
        }
 
-       vector<CiteStyle> styles(nStyles);
+       typedef vector<CiteStyle> cite_vec;
 
-       vector<CiteStyle>::size_type i = 0;
+       cite_vec styles(nStyles);
+       cite_vec::size_type i = 0;
        int j = start;
-       for (; i != styles.size(); ++i, ++j) {
+       for (; i != styles.size(); ++i, ++j)
                styles[i] = citeStyles[j];
-       }
 
        return styles;
 }
@@ -618,7 +762,7 @@ getNumericalStrings(string const & key,
                        break;
 
                case CITEYEARPAR:
-                       str = "(" + year + ")";
+                       str = '(' + year + ')';
                        break;
                }
 
@@ -648,16 +792,21 @@ getAuthorYearStrings(string const & key,
 
                switch (styles[i]) {
                case CITE:
+                       // jurabib only: Author/Annotator
+                       // (i.e. the "before" field, 2nd opt arg)
+                       str = author + "/<" + _("before") + '>';
+                       break;
+
                case CITET:
-                       str = author + " (" + year + ")";
+                       str = author + " (" + year + ')';
                        break;
 
                case CITEP:
-                       str = "(" + author + ", " + year + ")";
+                       str = '(' + author + ", " + year + ')';
                        break;
 
                case CITEALT:
-                       str = author + " " + year ;
+                       str = author + ' ' + year ;
                        break;
 
                case CITEALP:
@@ -673,7 +822,7 @@ getAuthorYearStrings(string const & key,
                        break;
 
                case CITEYEARPAR:
-                       str = "(" + year + ")";
+                       str = '(' + year + ')';
                        break;
                }
 
@@ -684,3 +833,4 @@ getAuthorYearStrings(string const & key,
 }
 
 } // namespace biblio
+} // namespace lyx