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