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