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