]> git.lyx.org Git - lyx.git/blob - src/BiblioInfo.cpp
6437ccf8dd22e2a2bc91478ce343f5ce92932940
[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
293 docstring const BibTeXInfo::getAuthorOrEditorList(Buffer const * buf,
294                                           bool full, bool forceshort) const
295 {
296         docstring author = operator[]("author");
297         if (author.empty())
298                 author = operator[]("editor");
299
300         return getAuthorList(buf, author, full, forceshort);
301 }
302
303
304 docstring const BibTeXInfo::getAuthorList(Buffer const * buf, 
305                 docstring const & author, bool const full, bool const forceshort, 
306                 bool const allnames, bool const beginning) const
307 {
308         // Maxnames treshold depend on engine
309         size_t maxnames = buf ?
310                 buf->params().documentClass().max_citenames() : 2;
311
312         if (!is_bibtex_) {
313                 docstring const opt = label();
314                 if (opt.empty())
315                         return docstring();
316
317                 docstring authors;
318                 docstring const remainder = trim(split(opt, authors, '('));
319                 if (remainder.empty())
320                         // in this case, we didn't find a "(",
321                         // so we don't have author (year)
322                         return docstring();
323                 return authors;
324         }
325
326         if (author.empty())
327                 return author;
328
329         // FIXME Move this to a separate routine that can
330         // be called from elsewhere.
331         //
332         // OK, we've got some names. Let's format them.
333         // Try to split the author list on " and "
334         vector<docstring> const authors =
335                 getVectorFromString(author, from_ascii(" and "));
336
337         docstring retval;
338
339         CiteEngineType const engine_type = buf ? buf->params().citeEngineType()
340                                                : ENGINE_TYPE_DEFAULT;
341
342         // These are defined in the styles
343         string const etal =
344                 buf ? buf->params().documentClass().getCiteMacro(engine_type, "_etal")
345                     : " et al.";
346         string const namesep =
347                 buf ? buf->params().documentClass().getCiteMacro(engine_type, "_namesep")
348                    : ", ";
349         string const lastnamesep =
350                 buf ? buf->params().documentClass().getCiteMacro(engine_type, "_lastnamesep")
351                     : ", and ";
352         string const pairnamesep =
353                 buf ? buf->params().documentClass().getCiteMacro(engine_type, "_pairnamesep")
354                      : " and ";
355         string firstnameform =
356                         buf ? buf->params().documentClass().getCiteMacro(engine_type, "!firstnameform")
357                              : "%surname%, %prename%";
358         if (!beginning)
359                 firstnameform = buf ? buf->params().documentClass().getCiteMacro(engine_type, "!firstbynameform")
360                                              : "%prename% %surname%";
361         string othernameform = buf ? buf->params().documentClass().getCiteMacro(engine_type, "!othernameform")
362                              : "%surname%, %prename%";
363         if (!beginning)
364                 othernameform = buf ? buf->params().documentClass().getCiteMacro(engine_type, "!otherbynameform")
365                                              : "%prename% %surname%";
366
367         // Shorten the list (with et al.) if forceshort is set
368         // and the list can actually be shortened, else if maxcitenames
369         // is passed and full is not set.
370         bool shorten = forceshort && authors.size() > 1;
371         vector<docstring>::const_iterator it = authors.begin();
372         vector<docstring>::const_iterator en = authors.end();
373         for (size_t i = 0; it != en; ++it, ++i) {
374                 if (i >= maxnames && !full) {
375                         shorten = true;
376                         break;
377                 }
378                 if (*it == "others") {
379                         retval += buf ? buf->B_(etal) : from_ascii(etal);
380                         break;
381                 }
382                 if (i > 0 && i == authors.size() - 1) {
383                         if (authors.size() == 2)
384                                 retval += buf ? buf->B_(pairnamesep) : from_ascii(pairnamesep);
385                         else
386                                 retval += buf ? buf->B_(lastnamesep) : from_ascii(lastnamesep);
387                 } else if (i > 0)
388                         retval += buf ? buf->B_(namesep) : from_ascii(namesep);
389                 if (allnames)
390                         retval += (i == 0) ? constructName(*it, firstnameform)
391                                 : constructName(*it, othernameform);
392                 else
393                         retval += nameParts(*it).second;
394         }
395         if (shorten) {
396                 if (allnames)
397                         retval = constructName(authors[0], firstnameform) + (buf ? buf->B_(etal) : from_ascii(etal));
398                 else
399                         retval = nameParts(authors[0]).second + (buf ? buf->B_(etal) : from_ascii(etal));
400         }
401
402         return convertLaTeXCommands(retval);
403 }
404
405
406 docstring const BibTeXInfo::getYear() const
407 {
408         if (is_bibtex_) {
409                 // first try legacy year field
410                 docstring year = operator[]("year");
411                 if (!year.empty())
412                         return year;
413                 // now try biblatex's date field
414                 year = operator[]("date");
415                 // Format is [-]YYYY-MM-DD*/[-]YYYY-MM-DD*
416                 // We only want the years.
417                 static regex const yreg("[-]?([\\d]{4}).*");
418                 static regex const ereg(".*/[-]?([\\d]{4}).*");
419                 smatch sm;
420                 string const date = to_utf8(year);
421                 if (!regex_match(date, sm, yreg))
422                         // cannot parse year.
423                         return docstring();
424                 year = from_ascii(sm[1]);
425                 // check for an endyear
426                 if (regex_match(date, sm, ereg))
427                         year += char_type(0x2013) + from_ascii(sm[1]);
428                 return year;
429         }
430
431         docstring const opt = label();
432         if (opt.empty())
433                 return docstring();
434
435         docstring authors;
436         docstring tmp = split(opt, authors, '(');
437         if (tmp.empty())
438                 // we don't have author (year)
439                 return docstring();
440         docstring year;
441         tmp = split(tmp, year, ')');
442         return year;
443 }
444
445
446 namespace {
447
448 docstring parseOptions(docstring const & format, string & optkey,
449                     docstring & ifpart, docstring & elsepart);
450
451 // Calls parseOptions to deal with an embedded option, such as:
452 //   {%number%[[, no.~%number%]]}
453 // which must appear at the start of format. ifelsepart gets the
454 // whole of the option, and we return what's left after the option.
455 // we return format if there is an error.
456 docstring parseEmbeddedOption(docstring const & format, docstring & ifelsepart)
457 {
458         LASSERT(format[0] == '{' && format[1] == '%', return format);
459         string optkey;
460         docstring ifpart;
461         docstring elsepart;
462         docstring const rest = parseOptions(format, optkey, ifpart, elsepart);
463         if (format == rest) { // parse error
464                 LYXERR0("ERROR! Couldn't parse `" << format <<"'.");
465                 return format;
466         }
467         LASSERT(rest.size() <= format.size(),
468                 { ifelsepart = docstring(); return format; });
469         ifelsepart = format.substr(0, format.size() - rest.size());
470         return rest;
471 }
472
473
474 // Gets a "clause" from a format string, where the clause is
475 // delimited by '[[' and ']]'. Returns what is left after the
476 // clause is removed, and returns format if there is an error.
477 docstring getClause(docstring const & format, docstring & clause)
478 {
479         docstring fmt = format;
480         // remove '[['
481         fmt = fmt.substr(2);
482         // we'll remove characters from the front of fmt as we
483         // deal with them
484         while (!fmt.empty()) {
485                 if (fmt[0] == ']' && fmt.size() > 1 && fmt[1] == ']') {
486                         // that's the end
487                         fmt = fmt.substr(2);
488                         break;
489                 }
490                 // check for an embedded option
491                 if (fmt[0] == '{' && fmt.size() > 1 && fmt[1] == '%') {
492                         docstring part;
493                         docstring const rest = parseEmbeddedOption(fmt, part);
494                         if (fmt == rest) {
495                                 LYXERR0("ERROR! Couldn't parse embedded option in `" << format <<"'.");
496                                 return format;
497                         }
498                         clause += part;
499                         fmt = rest;
500                 } else { // it's just a normal character
501                                 clause += fmt[0];
502                                 fmt = fmt.substr(1);
503                 }
504         }
505         return fmt;
506 }
507
508
509 // parse an options string, which must appear at the start of the
510 // format parameter. puts the parsed bits in optkey, ifpart, and
511 // elsepart and returns what's left after the option is removed.
512 // if there's an error, it returns format itself.
513 docstring parseOptions(docstring const & format, string & optkey,
514                     docstring & ifpart, docstring & elsepart)
515 {
516         LASSERT(format[0] == '{' && format[1] == '%', return format);
517         // strip '{%'
518         docstring fmt = format.substr(2);
519         size_t pos = fmt.find('%'); // end of key
520         if (pos == string::npos) {
521                 LYXERR0("Error parsing  `" << format <<"'. Can't find end of key.");
522                 return format;
523         }
524         optkey = to_utf8(fmt.substr(0, pos));
525         fmt = fmt.substr(pos + 1);
526         // [[format]] should be next
527         if (fmt[0] != '[' || fmt[1] != '[') {
528                 LYXERR0("Error parsing  `" << format <<"'. Can't find '[[' after key.");
529                 return format;
530         }
531
532         docstring curfmt = fmt;
533         fmt = getClause(curfmt, ifpart);
534         if (fmt == curfmt) {
535                 LYXERR0("Error parsing  `" << format <<"'. Couldn't get if clause.");
536                 return format;
537         }
538
539         if (fmt[0] == '}') // we're done, no else clause
540                 return fmt.substr(1);
541
542         // else part should follow
543         if (fmt[0] != '[' || fmt[1] != '[') {
544                 LYXERR0("Error parsing  `" << format <<"'. Can't find else clause.");
545                 return format;
546         }
547
548         curfmt = fmt;
549         fmt = getClause(curfmt, elsepart);
550         // we should be done
551         if (fmt == curfmt || fmt[0] != '}') {
552                 LYXERR0("Error parsing  `" << format <<"'. Can't find end of option.");
553                 return format;
554         }
555         return fmt.substr(1);
556 }
557
558
559 } // anon namespace
560
561 /* FIXME
562 Bug #9131 revealed an oddity in how we are generating citation information
563 when more than one key is given. We end up building a longer and longer format 
564 string as we go, which we then have to re-parse, over and over and over again,
565 rather than generating the information for the individual keys and then putting
566 all of that together. We do that to deal with the way separators work, from what
567 I can tell, but it still feels like a hack. Fixing this would require quite a
568 bit of work, however.
569 */
570 docstring BibTeXInfo::expandFormat(docstring const & format,
571                 BibTeXInfoList const xrefs, int & counter, Buffer const & buf,
572                 CiteItem const & ci, bool next, bool second) const
573 {
574         // incorrect use of macros could put us in an infinite loop
575         static int const max_passes = 5000;
576         // the use of overly large keys can lead to performance problems, due
577         // to eventual attempts to convert LaTeX macros to unicode. See bug
578         // #8944. By default, the size is limited to 128 (in CiteItem), but
579         // for specific purposes (such as XHTML export), it needs to be enlarged
580         // This is perhaps not the best solution, but it will have to do for now.
581         size_t const max_keysize = ci.max_key_size;
582         odocstringstream ret; // return value
583         string key;
584         bool scanning_key = false;
585         bool scanning_rich = false;
586
587         CiteEngineType const engine_type = buf.params().citeEngineType();
588         docstring fmt = format;
589         // we'll remove characters from the front of fmt as we
590         // deal with them
591         while (!fmt.empty()) {
592                 if (counter > max_passes) {
593                         LYXERR0("Recursion limit reached while parsing `"
594                                 << format << "'.");
595                         return _("ERROR!");
596                 }
597
598                 char_type thischar = fmt[0];
599                 if (thischar == '%') {
600                         // beginning or end of key
601                         if (scanning_key) {
602                                 // end of key
603                                 scanning_key = false;
604                                 // so we replace the key with its value, which may be empty
605                                 if (key[0] == '!') {
606                                         // macro
607                                         string const val =
608                                                 buf.params().documentClass().getCiteMacro(engine_type, key);
609                                         fmt = from_utf8(val) + fmt.substr(1);
610                                         counter += 1;
611                                         continue;
612                                 } else if (key[0] == '_') {
613                                         // a translatable bit
614                                         string const val =
615                                                 buf.params().documentClass().getCiteMacro(engine_type, key);
616                                         docstring const trans =
617                                                 translateIfPossible(from_utf8(val), buf.params().language->code());
618                                         ret << trans;
619                                 } else {
620                                         docstring const val =
621                                                 getValueForKey(key, buf, ci, xrefs, max_keysize);
622                                         if (!scanning_rich)
623                                                 ret << from_ascii("{!<span class=\"bib-" + key + "\">!}");
624                                         ret << val;
625                                         if (!scanning_rich)
626                                                 ret << from_ascii("{!</span>!}");
627                                 }
628                         } else {
629                                 // beginning of key
630                                 key.clear();
631                                 scanning_key = true;
632                         }
633                 }
634                 else if (thischar == '{') {
635                         // beginning of option?
636                         if (scanning_key) {
637                                 LYXERR0("ERROR: Found `{' when scanning key in `" << format << "'.");
638                                 return _("ERROR!");
639                         }
640                         if (fmt.size() > 1) {
641                                 if (fmt[1] == '%') {
642                                         // it is the beginning of an optional format
643                                         string optkey;
644                                         docstring ifpart;
645                                         docstring elsepart;
646                                         docstring const newfmt =
647                                                 parseOptions(fmt, optkey, ifpart, elsepart);
648                                         if (newfmt == fmt) // parse error
649                                                 return _("ERROR!");
650                                         fmt = newfmt;
651                                         docstring const val =
652                                                 getValueForKey(optkey, buf, ci, xrefs);
653                                         if (optkey == "next" && next)
654                                                 ret << ifpart; // without expansion
655                                         else if (optkey == "second" && second) {
656                                                 int newcounter = 0;
657                                                 ret << expandFormat(ifpart, xrefs, newcounter, buf,
658                                                         ci, next);
659                                         } else if (!val.empty()) {
660                                                 int newcounter = 0;
661                                                 ret << expandFormat(ifpart, xrefs, newcounter, buf,
662                                                         ci, next);
663                                         } else if (!elsepart.empty()) {
664                                                 int newcounter = 0;
665                                                 ret << expandFormat(elsepart, xrefs, newcounter, buf,
666                                                         ci, next);
667                                         }
668                                         // fmt will have been shortened for us already
669                                         continue;
670                                 }
671                                 if (fmt[1] == '!') {
672                                         // beginning of rich text
673                                         scanning_rich = true;
674                                         fmt = fmt.substr(2);
675                                         ret << from_ascii("{!");
676                                         continue;
677                                 }
678                         }
679                         // we are here if '{' was not followed by % or !.
680                         // So it's just a character.
681                         ret << thischar;
682                 }
683                 else if (scanning_rich && thischar == '!'
684                          && fmt.size() > 1 && fmt[1] == '}') {
685                         // end of rich text
686                         scanning_rich = false;
687                         fmt = fmt.substr(2);
688                         ret << from_ascii("!}");
689                         continue;
690                 }
691                 else if (scanning_key)
692                         key += char(thischar);
693                 else {
694                         try {
695                                 ret.put(thischar);
696                         } catch (EncodingException & /* e */) {
697                                 LYXERR0("Uncodable character '" << docstring(1, thischar) << " in citation label!");
698                         }
699                 }
700                 fmt = fmt.substr(1);
701         } // for loop
702         if (scanning_key) {
703                 LYXERR0("Never found end of key in `" << format << "'!");
704                 return _("ERROR!");
705         }
706         if (scanning_rich) {
707                 LYXERR0("Never found end of rich text in `" << format << "'!");
708                 return _("ERROR!");
709         }
710         return ret.str();
711 }
712
713
714 docstring const & BibTeXInfo::getInfo(BibTeXInfoList const xrefs,
715         Buffer const & buf, CiteItem const & ci) const
716 {
717         bool const richtext = ci.richtext;
718
719         if (!richtext && !info_.empty())
720                 return info_;
721         if (richtext && !info_richtext_.empty())
722                 return info_richtext_;
723
724         if (!is_bibtex_) {
725                 BibTeXInfo::const_iterator it = find(from_ascii("ref"));
726                 info_ = it->second;
727                 return info_;
728         }
729
730         CiteEngineType const engine_type = buf.params().citeEngineType();
731         DocumentClass const & dc = buf.params().documentClass();
732         docstring const & format =
733                 from_utf8(dc.getCiteFormat(engine_type, to_utf8(entry_type_)));
734         int counter = 0;
735         info_ = expandFormat(format, xrefs, counter, buf,
736                 ci, false, false);
737
738         if (info_.empty()) {
739                 // this probably shouldn't happen
740                 return info_;
741         }
742
743         if (richtext) {
744                 info_richtext_ = convertLaTeXCommands(processRichtext(info_, true));
745                 return info_richtext_;
746         }
747
748         info_ = convertLaTeXCommands(processRichtext(info_, false));
749         return info_;
750 }
751
752
753 docstring const BibTeXInfo::getLabel(BibTeXInfoList const xrefs,
754         Buffer const & buf, docstring const & format,
755         CiteItem const & ci, bool next, bool second) const
756 {
757         docstring loclabel;
758
759         int counter = 0;
760         loclabel = expandFormat(format, xrefs, counter, buf, ci, next, second);
761
762         if (!loclabel.empty() && !next) {
763                 loclabel = processRichtext(loclabel, ci.richtext);
764                 loclabel = convertLaTeXCommands(loclabel);
765         }
766
767         return loclabel;
768 }
769
770
771 docstring const & BibTeXInfo::operator[](docstring const & field) const
772 {
773         BibTeXInfo::const_iterator it = find(field);
774         if (it != end())
775                 return it->second;
776         static docstring const empty_value = docstring();
777         return empty_value;
778 }
779
780
781 docstring const & BibTeXInfo::operator[](string const & field) const
782 {
783         return operator[](from_ascii(field));
784 }
785
786
787 docstring BibTeXInfo::getValueForKey(string const & oldkey, Buffer const & buf,
788         CiteItem const & ci, BibTeXInfoList const xrefs, size_t maxsize) const
789 {
790         // anything less is pointless
791         LASSERT(maxsize >= 16, maxsize = 16);
792         string key = oldkey;
793         bool cleanit = false;
794         if (prefixIs(oldkey, "clean:")) {
795                 key = oldkey.substr(6);
796                 cleanit = true;
797         }
798
799         docstring ret = operator[](key);
800         if (ret.empty() && !xrefs.empty()) {
801                 vector<BibTeXInfo const *>::const_iterator it = xrefs.begin();
802                 vector<BibTeXInfo const *>::const_iterator en = xrefs.end();
803                 for (; it != en; ++it) {
804                         if (*it && !(**it)[key].empty()) {
805                                 ret = (**it)[key];
806                                 break;
807                         }
808                 }
809         }
810         if (ret.empty()) {
811                 // some special keys
812                 // FIXME: dialog, textbefore and textafter have nothing to do with this
813                 if (key == "dialog" && ci.context == CiteItem::Dialog)
814                         ret = from_ascii("x"); // any non-empty string will do
815                 else if (key == "export" && ci.context == CiteItem::Export)
816                         ret = from_ascii("x"); // any non-empty string will do
817                 else if (key == "ifstar" && ci.Starred)
818                         ret = from_ascii("x"); // any non-empty string will do
819                 else if (key == "ifqualified" && ci.isQualified)
820                         ret = from_ascii("x"); // any non-empty string will do
821                 else if (key == "entrytype")
822                         ret = entry_type_;
823                 else if (prefixIs(key, "ifentrytype:")
824                          && from_ascii(key.substr(12)) == entry_type_)
825                         ret = from_ascii("x"); // any non-empty string will do
826                 else if (key == "key")
827                         ret = bib_key_;
828                 else if (key == "label")
829                         ret = label_;
830                 else if (key == "modifier" && modifier_ != 0)
831                         ret = modifier_;
832                 else if (key == "numericallabel")
833                         ret = cite_number_;
834                 else if (prefixIs(key, "ifmultiple:")) {
835                         // Return whether we have multiple authors
836                         docstring const kind = operator[](from_ascii(key.substr(11)));
837                         if (multipleAuthors(kind))
838                                 ret = from_ascii("x"); // any non-empty string will do
839                 }
840                 else if (prefixIs(key, "abbrvnames:")) {
841                         // Special key to provide abbreviated name list,
842                         // with respect to maxcitenames. Suitable for Bibliography
843                         // beginnings.
844                         docstring const kind = operator[](from_ascii(key.substr(11)));
845                         ret = getAuthorList(&buf, kind, false, false, true);
846                         if (ci.forceUpperCase && isLowerCase(ret[0]))
847                                 ret[0] = uppercase(ret[0]);
848                 } else if (prefixIs(key, "fullnames:")) {
849                         // Return a full name list. Suitable for Bibliography
850                         // beginnings.
851                         docstring const kind = operator[](from_ascii(key.substr(10)));
852                         ret = getAuthorList(&buf, kind, true, false, true);
853                         if (ci.forceUpperCase && isLowerCase(ret[0]))
854                                 ret[0] = uppercase(ret[0]);
855                 } else if (prefixIs(key, "forceabbrvnames:")) {
856                         // Special key to provide abbreviated name lists,
857                         // irrespective of maxcitenames. Suitable for Bibliography
858                         // beginnings.
859                         docstring const kind = operator[](from_ascii(key.substr(15)));
860                         ret = getAuthorList(&buf, kind, false, true, true);
861                         if (ci.forceUpperCase && isLowerCase(ret[0]))
862                                 ret[0] = uppercase(ret[0]);
863                 } else if (prefixIs(key, "abbrvbynames:")) {
864                         // Special key to provide abbreviated name list,
865                         // with respect to maxcitenames. Suitable for further names inside a
866                         // bibliography item // (such as "ed. by ...")
867                         docstring const kind = operator[](from_ascii(key.substr(11)));
868                         ret = getAuthorList(&buf, kind, false, false, true, false);
869                         if (ci.forceUpperCase && isLowerCase(ret[0]))
870                                 ret[0] = uppercase(ret[0]);
871                 } else if (prefixIs(key, "fullbynames:")) {
872                         // Return a full name list. Suitable for further names inside a
873                         // bibliography item // (such as "ed. by ...")
874                         docstring const kind = operator[](from_ascii(key.substr(10)));
875                         ret = getAuthorList(&buf, kind, true, false, true, false);
876                         if (ci.forceUpperCase && isLowerCase(ret[0]))
877                                 ret[0] = uppercase(ret[0]);
878                 } else if (prefixIs(key, "forceabbrvbynames:")) {
879                         // Special key to provide abbreviated name lists,
880                         // irrespective of maxcitenames. Suitable for further names inside a
881                         // bibliography item // (such as "ed. by ...")
882                         docstring const kind = operator[](from_ascii(key.substr(15)));
883                         ret = getAuthorList(&buf, kind, false, true, true, false);
884                         if (ci.forceUpperCase && isLowerCase(ret[0]))
885                                 ret[0] = uppercase(ret[0]);
886                 } else if (key == "abbrvciteauthor") {
887                         // Special key to provide abbreviated author or
888                         // editor names (suitable for citation labels),
889                         // with respect to maxcitenames.
890                         ret = getAuthorOrEditorList(&buf, false, false);
891                         if (ci.forceUpperCase && isLowerCase(ret[0]))
892                                 ret[0] = uppercase(ret[0]);
893                 } else if (key == "fullciteauthor") {
894                         // Return a full author or editor list (for citation labels)
895                         ret = getAuthorOrEditorList(&buf, true, false);
896                         if (ci.forceUpperCase && isLowerCase(ret[0]))
897                                 ret[0] = uppercase(ret[0]);
898                 } else if (key == "forceabbrvciteauthor") {
899                         // Special key to provide abbreviated author or
900                         // editor names (suitable for citation labels),
901                         // irrespective of maxcitenames.
902                         ret = getAuthorOrEditorList(&buf, false, true);
903                         if (ci.forceUpperCase && isLowerCase(ret[0]))
904                                 ret[0] = uppercase(ret[0]);
905                 } else if (key == "bibentry") {
906                         // Special key to provide the full bibliography entry: see getInfo()
907                         CiteEngineType const engine_type = buf.params().citeEngineType();
908                         DocumentClass const & dc = buf.params().documentClass();
909                         docstring const & format =
910                                 from_utf8(dc.getCiteFormat(engine_type, to_utf8(entry_type_), false));
911                         int counter = 0;
912                         ret = expandFormat(format, xrefs, counter, buf, ci, false, false);
913                 } else if (key == "textbefore")
914                         ret = ci.textBefore;
915                 else if (key == "textafter")
916                         ret = ci.textAfter;
917                 else if (key == "curpretext")
918                         ret = ci.getPretexts()[bib_key_];
919                 else if (key == "curposttext")
920                         ret = ci.getPosttexts()[bib_key_];
921                 else if (key == "year")
922                         ret = getYear();
923         }
924
925         if (cleanit)
926                 ret = html::cleanAttr(ret);
927
928         // make sure it is not too big
929         support::truncateWithEllipsis(ret, maxsize);
930         return ret;
931 }
932
933
934 //////////////////////////////////////////////////////////////////////
935 //
936 // BiblioInfo
937 //
938 //////////////////////////////////////////////////////////////////////
939
940 namespace {
941
942 // A functor for use with sort, leading to case insensitive sorting
943 class compareNoCase: public binary_function<docstring, docstring, bool>
944 {
945 public:
946         bool operator()(docstring const & s1, docstring const & s2) const {
947                 return compare_no_case(s1, s2) < 0;
948         }
949 };
950
951 } // namespace anon
952
953
954 vector<docstring> const BiblioInfo::getXRefs(BibTeXInfo const & data, bool const nested) const
955 {
956         vector<docstring> result;
957         if (!data.isBibTeX())
958                 return result;
959         // Legacy crossref field. This is not nestable.
960         if (!nested && !data["crossref"].empty()) {
961                 docstring const xrefkey = data["crossref"];
962                 result.push_back(xrefkey);
963                 // However, check for nested xdatas
964                 BiblioInfo::const_iterator it = find(xrefkey);
965                 if (it != end()) {
966                         BibTeXInfo const & xref = it->second;
967                         vector<docstring> const nxdata = getXRefs(xref, true);
968                         if (!nxdata.empty())
969                                 result.insert(result.end(), nxdata.begin(), nxdata.end());
970                 }
971         }
972         // Biblatex's xdata field. Infinitely nestable.
973         // XData field can consist of a comma-separated list of keys
974         vector<docstring> const xdatakeys = getVectorFromString(data["xdata"]);
975         if (!xdatakeys.empty()) {
976                 vector<docstring>::const_iterator xit = xdatakeys.begin();
977                 vector<docstring>::const_iterator xen = xdatakeys.end();
978                 for (; xit != xen; ++xit) {
979                         docstring const xdatakey = *xit;
980                         result.push_back(xdatakey);
981                         BiblioInfo::const_iterator it = find(xdatakey);
982                         if (it != end()) {
983                                 BibTeXInfo const & xdata = it->second;
984                                 vector<docstring> const nxdata = getXRefs(xdata, true);
985                                 if (!nxdata.empty())
986                                         result.insert(result.end(), nxdata.begin(), nxdata.end());
987                         }
988                 }
989         }
990         return result;
991 }
992
993
994 vector<docstring> const BiblioInfo::getKeys() const
995 {
996         vector<docstring> bibkeys;
997         BiblioInfo::const_iterator it  = begin();
998         for (; it != end(); ++it)
999                 bibkeys.push_back(it->first);
1000         sort(bibkeys.begin(), bibkeys.end(), compareNoCase());
1001         return bibkeys;
1002 }
1003
1004
1005 vector<docstring> const BiblioInfo::getFields() const
1006 {
1007         vector<docstring> bibfields;
1008         set<docstring>::const_iterator it = field_names_.begin();
1009         set<docstring>::const_iterator end = field_names_.end();
1010         for (; it != end; ++it)
1011                 bibfields.push_back(*it);
1012         sort(bibfields.begin(), bibfields.end());
1013         return bibfields;
1014 }
1015
1016
1017 vector<docstring> const BiblioInfo::getEntries() const
1018 {
1019         vector<docstring> bibentries;
1020         set<docstring>::const_iterator it = entry_types_.begin();
1021         set<docstring>::const_iterator end = entry_types_.end();
1022         for (; it != end; ++it)
1023                 bibentries.push_back(*it);
1024         sort(bibentries.begin(), bibentries.end());
1025         return bibentries;
1026 }
1027
1028
1029 docstring const BiblioInfo::getAuthorOrEditorList(docstring const & key, Buffer const & buf) const
1030 {
1031         BiblioInfo::const_iterator it = find(key);
1032         if (it == end())
1033                 return docstring();
1034         BibTeXInfo const & data = it->second;
1035         return data.getAuthorOrEditorList(&buf, false);
1036 }
1037
1038
1039 docstring const BiblioInfo::getCiteNumber(docstring const & key) const
1040 {
1041         BiblioInfo::const_iterator it = find(key);
1042         if (it == end())
1043                 return docstring();
1044         BibTeXInfo const & data = it->second;
1045         return data.citeNumber();
1046 }
1047
1048
1049 docstring const BiblioInfo::getYear(docstring const & key, bool use_modifier) const
1050 {
1051         BiblioInfo::const_iterator it = find(key);
1052         if (it == end())
1053                 return docstring();
1054         BibTeXInfo const & data = it->second;
1055         docstring year = data.getYear();
1056         if (year.empty()) {
1057                 // let's try the crossrefs
1058                 vector<docstring> const xrefs = getXRefs(data);
1059                 if (xrefs.empty())
1060                         // no luck
1061                         return docstring();
1062                 vector<docstring>::const_iterator it = xrefs.begin();
1063                 vector<docstring>::const_iterator en = xrefs.end();
1064                 for (; it != en; ++it) {
1065                         BiblioInfo::const_iterator const xrefit = find(*it);
1066                         if (xrefit == end())
1067                                 continue;
1068                         BibTeXInfo const & xref_data = xrefit->second;
1069                         year = xref_data.getYear();
1070                         if (!year.empty())
1071                                 // success!
1072                                 break;
1073                 }
1074         }
1075         if (use_modifier && data.modifier() != 0)
1076                 year += data.modifier();
1077         return year;
1078 }
1079
1080
1081 docstring const BiblioInfo::getYear(docstring const & key, Buffer const & buf, bool use_modifier) const
1082 {
1083         docstring const year = getYear(key, use_modifier);
1084         if (year.empty())
1085                 return buf.B_("No year");
1086         return year;
1087 }
1088
1089
1090 docstring const BiblioInfo::getInfo(docstring const & key,
1091         Buffer const & buf, CiteItem const & ci) const
1092 {
1093         BiblioInfo::const_iterator it = find(key);
1094         if (it == end())
1095                 return docstring(_("Bibliography entry not found!"));
1096         BibTeXInfo const & data = it->second;
1097         BibTeXInfoList xrefptrs;
1098         vector<docstring> const xrefs = getXRefs(data);
1099         if (!xrefs.empty()) {
1100                 vector<docstring>::const_iterator it = xrefs.begin();
1101                 vector<docstring>::const_iterator en = xrefs.end();
1102                 for (; it != en; ++it) {
1103                         BiblioInfo::const_iterator const xrefit = find(*it);
1104                         if (xrefit != end())
1105                                 xrefptrs.push_back(&(xrefit->second));
1106                 }
1107         }
1108         return data.getInfo(xrefptrs, buf, ci);
1109 }
1110
1111
1112 docstring const BiblioInfo::getLabel(vector<docstring> keys,
1113         Buffer const & buf, string const & style, CiteItem const & ci) const
1114 {
1115         size_t max_size = ci.max_size;
1116         // shorter makes no sense
1117         LASSERT(max_size >= 16, max_size = 16);
1118
1119         // we can't display more than 10 of these, anyway
1120         bool const too_many_keys = keys.size() > 10;
1121         if (too_many_keys)
1122                 keys.resize(10);
1123
1124         CiteEngineType const engine_type = buf.params().citeEngineType();
1125         DocumentClass const & dc = buf.params().documentClass();
1126         docstring const & format = from_utf8(dc.getCiteFormat(engine_type, style, false, "cite"));
1127         docstring ret = format;
1128         vector<docstring>::const_iterator key = keys.begin();
1129         vector<docstring>::const_iterator ken = keys.end();
1130         for (int i = 0; key != ken; ++key, ++i) {
1131                 BiblioInfo::const_iterator it = find(*key);
1132                 BibTeXInfo empty_data;
1133                 empty_data.key(*key);
1134                 BibTeXInfo & data = empty_data;
1135                 vector<BibTeXInfo const *> xrefptrs;
1136                 if (it != end()) {
1137                         data = it->second;
1138                         vector<docstring> const xrefs = getXRefs(data);
1139                         if (!xrefs.empty()) {
1140                                 vector<docstring>::const_iterator it = xrefs.begin();
1141                                 vector<docstring>::const_iterator en = xrefs.end();
1142                                 for (; it != en; ++it) {
1143                                         BiblioInfo::const_iterator const xrefit = find(*it);
1144                                         if (xrefit != end())
1145                                                 xrefptrs.push_back(&(xrefit->second));
1146                                 }
1147                         }
1148                 }
1149                 ret = data.getLabel(xrefptrs, buf, ret, ci, key + 1 != ken, i == 1);
1150         }
1151
1152         if (too_many_keys)
1153                 ret.push_back(0x2026);//HORIZONTAL ELLIPSIS
1154         support::truncateWithEllipsis(ret, max_size);
1155         return ret;
1156 }
1157
1158
1159 bool BiblioInfo::isBibtex(docstring const & key) const
1160 {
1161         docstring key1;
1162         split(key, key1, ',');
1163         BiblioInfo::const_iterator it = find(key1);
1164         if (it == end())
1165                 return false;
1166         return it->second.isBibTeX();
1167 }
1168
1169
1170 vector<docstring> const BiblioInfo::getCiteStrings(
1171         vector<docstring> const & keys, vector<CitationStyle> const & styles,
1172         Buffer const & buf, CiteItem const & ci) const
1173 {
1174         if (empty())
1175                 return vector<docstring>();
1176
1177         string style;
1178         vector<docstring> vec(styles.size());
1179         for (size_t i = 0; i != vec.size(); ++i) {
1180                 style = styles[i].name;
1181                 vec[i] = getLabel(keys, buf, style, ci);
1182         }
1183
1184         return vec;
1185 }
1186
1187
1188 void BiblioInfo::mergeBiblioInfo(BiblioInfo const & info)
1189 {
1190         bimap_.insert(info.begin(), info.end());
1191         field_names_.insert(info.field_names_.begin(), info.field_names_.end());
1192         entry_types_.insert(info.entry_types_.begin(), info.entry_types_.end());
1193 }
1194
1195
1196 namespace {
1197
1198 // used in xhtml to sort a list of BibTeXInfo objects
1199 bool lSorter(BibTeXInfo const * lhs, BibTeXInfo const * rhs)
1200 {
1201         docstring const lauth = lhs->getAuthorOrEditorList();
1202         docstring const rauth = rhs->getAuthorOrEditorList();
1203         docstring const lyear = lhs->getYear();
1204         docstring const ryear = rhs->getYear();
1205         docstring const ltitl = lhs->operator[]("title");
1206         docstring const rtitl = rhs->operator[]("title");
1207         return  (lauth < rauth)
1208                 || (lauth == rauth && lyear < ryear)
1209                 || (lauth == rauth && lyear == ryear && ltitl < rtitl);
1210 }
1211
1212 }
1213
1214
1215 void BiblioInfo::collectCitedEntries(Buffer const & buf)
1216 {
1217         cited_entries_.clear();
1218         // We are going to collect all the citation keys used in the document,
1219         // getting them from the TOC.
1220         // FIXME We may want to collect these differently, in the first case,
1221         // so that we might have them in order of appearance.
1222         set<docstring> citekeys;
1223         shared_ptr<Toc const> toc = buf.tocBackend().toc("citation");
1224         Toc::const_iterator it = toc->begin();
1225         Toc::const_iterator const en = toc->end();
1226         for (; it != en; ++it) {
1227                 if (it->str().empty())
1228                         continue;
1229                 vector<docstring> const keys = getVectorFromString(it->str());
1230                 citekeys.insert(keys.begin(), keys.end());
1231         }
1232         if (citekeys.empty())
1233                 return;
1234
1235         // We have a set of the keys used in this document.
1236         // We will now convert it to a list of the BibTeXInfo objects used in
1237         // this document...
1238         vector<BibTeXInfo const *> bi;
1239         set<docstring>::const_iterator cit = citekeys.begin();
1240         set<docstring>::const_iterator const cen = citekeys.end();
1241         for (; cit != cen; ++cit) {
1242                 BiblioInfo::const_iterator const bt = find(*cit);
1243                 if (bt == end() || !bt->second.isBibTeX())
1244                         continue;
1245                 bi.push_back(&(bt->second));
1246         }
1247         // ...and sort it.
1248         sort(bi.begin(), bi.end(), lSorter);
1249
1250         // Now we can write the sorted keys
1251         vector<BibTeXInfo const *>::const_iterator bit = bi.begin();
1252         vector<BibTeXInfo const *>::const_iterator ben = bi.end();
1253         for (; bit != ben; ++bit)
1254                 cited_entries_.push_back((*bit)->key());
1255 }
1256
1257
1258 void BiblioInfo::makeCitationLabels(Buffer const & buf)
1259 {
1260         collectCitedEntries(buf);
1261         CiteEngineType const engine_type = buf.params().citeEngineType();
1262         bool const numbers = (engine_type & ENGINE_TYPE_NUMERICAL);
1263
1264         int keynumber = 0;
1265         char modifier = 0;
1266         // used to remember the last one we saw
1267         // we'll be comparing entries to see if we need to add
1268         // modifiers, like "1984a"
1269         map<docstring, BibTeXInfo>::iterator last = bimap_.end();
1270
1271         vector<docstring>::const_iterator it = cited_entries_.begin();
1272         vector<docstring>::const_iterator const en = cited_entries_.end();
1273         for (; it != en; ++it) {
1274                 map<docstring, BibTeXInfo>::iterator const biit = bimap_.find(*it);
1275                 // this shouldn't happen, but...
1276                 if (biit == bimap_.end())
1277                         // ...fail gracefully, anyway.
1278                         continue;
1279                 BibTeXInfo & entry = biit->second;
1280                 if (numbers) {
1281                         docstring const num = convert<docstring>(++keynumber);
1282                         entry.setCiteNumber(num);
1283                 } else {
1284                         // The first test here is checking whether this is the first
1285                         // time through the loop. If so, then we do not have anything
1286                         // with which to compare.
1287                         if (last != bimap_.end()
1288                             && entry.getAuthorOrEditorList() == last->second.getAuthorOrEditorList()
1289                             // we access the year via getYear() so as to get it from the xref,
1290                             // if we need to do so
1291                             && getYear(entry.key()) == getYear(last->second.key())) {
1292                                 if (modifier == 0) {
1293                                         // so the last one should have been 'a'
1294                                         last->second.setModifier('a');
1295                                         modifier = 'b';
1296                                 } else if (modifier == 'z')
1297                                         modifier = 'A';
1298                                 else
1299                                         modifier++;
1300                         } else {
1301                                 modifier = 0;
1302                         }
1303                         entry.setModifier(modifier);
1304                         // remember the last one
1305                         last = biit;
1306                 }
1307         }
1308         // Set the labels
1309         it = cited_entries_.begin();
1310         for (; it != en; ++it) {
1311                 map<docstring, BibTeXInfo>::iterator const biit = bimap_.find(*it);
1312                 // this shouldn't happen, but...
1313                 if (biit == bimap_.end())
1314                         // ...fail gracefully, anyway.
1315                         continue;
1316                 BibTeXInfo & entry = biit->second;
1317                 if (numbers) {
1318                         entry.label(entry.citeNumber());
1319                 } else {
1320                         docstring const auth = entry.getAuthorOrEditorList(&buf, false);
1321                         // we do it this way so as to access the xref, if necessary
1322                         // note that this also gives us the modifier
1323                         docstring const year = getYear(*it, buf, true);
1324                         if (!auth.empty() && !year.empty())
1325                                 entry.label(auth + ' ' + year);
1326                         else
1327                                 entry.label(entry.key());
1328                 }
1329         }
1330 }
1331
1332
1333 //////////////////////////////////////////////////////////////////////
1334 //
1335 // CitationStyle
1336 //
1337 //////////////////////////////////////////////////////////////////////
1338
1339
1340 CitationStyle citationStyleFromString(string const & command,
1341                                       BufferParams const & params)
1342 {
1343         CitationStyle cs;
1344         if (command.empty())
1345                 return cs;
1346
1347         string const alias = params.getCiteAlias(command);
1348         string cmd = alias.empty() ? command : alias;
1349         if (isUpperCase(command[0])) {
1350                 cs.forceUpperCase = true;
1351                 cmd[0] = lowercase(cmd[0]);
1352         }
1353
1354         size_t const n = command.size() - 1;
1355         if (command[n] == '*') {
1356                 cs.hasStarredVersion = true;
1357                 if (suffixIs(cmd, '*'))
1358                         cmd = cmd.substr(0, cmd.size() - 1);
1359         }
1360
1361         cs.name = cmd;
1362         return cs;
1363 }
1364
1365
1366 string citationStyleToString(const CitationStyle & cs, bool const latex)
1367 {
1368         string cmd = latex ? cs.cmd : cs.name;
1369         if (cs.forceUpperCase)
1370                 cmd[0] = uppercase(cmd[0]);
1371         if (cs.hasStarredVersion)
1372                 cmd += '*';
1373         return cmd;
1374 }
1375
1376 } // namespace lyx