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