]> git.lyx.org Git - lyx.git/blob - src/insets/InsetCitation.cpp
Fix functions that used functions but did not defined it
[lyx.git] / src / insets / InsetCitation.cpp
1 /**
2  * \file InsetCitation.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  *
9  * Full author contact details are available in file CREDITS.
10  */
11
12 #include <config.h>
13
14 #include "InsetCitation.h"
15
16 #include "BiblioInfo.h"
17 #include "Buffer.h"
18 #include "buffer_funcs.h"
19 #include "BufferParams.h"
20 #include "BufferView.h"
21 #include "Citation.h"
22 #include "DispatchResult.h"
23 #include "FuncCode.h"
24 #include "FuncRequest.h"
25 #include "FuncStatus.h"
26 #include "LaTeXFeatures.h"
27 #include "LyX.h"
28 #include "LyXRC.h"
29 #include "output_xhtml.h"
30 #include "output_docbook.h"
31 #include "ParIterator.h"
32 #include "texstream.h"
33 #include "TocBackend.h"
34
35 #include "support/debug.h"
36 #include "support/docstream.h"
37 #include "support/FileNameList.h"
38 #include "support/filetools.h"
39 #include "support/gettext.h"
40 #include "support/lstrings.h"
41
42 #include <algorithm>
43 #include <climits>
44
45 using namespace std;
46 using namespace lyx::support;
47
48 namespace lyx {
49
50 InsetCitation::InsetCitation(Buffer * buf, InsetCommandParams const & p)
51         : InsetCommand(buf, p)
52 {
53         buffer().scheduleBiblioTempRemoval();
54         cleanKeys();
55 }
56
57
58 InsetCitation::~InsetCitation()
59 {
60         if (isBufferLoaded())
61                 /* We do not use buffer() because Coverity believes that this
62                  * may throw an exception. Actually this code path is not
63                  * taken when buffer_ == 0 */
64                 buffer_->scheduleBiblioTempRemoval();
65 }
66
67
68 // May well be over-ridden when session settings are loaded
69 // in GuiCitation. Unfortunately, that will not happen until
70 // such a dialog is created.
71 bool InsetCitation::last_literal = true;
72
73
74 ParamInfo const & InsetCitation::findInfo(string const & /* cmdName */)
75 {
76         static ParamInfo param_info_;
77
78         // standard cite does only take one argument, but biblatex, jurabib
79         // and natbib extend this to two arguments, so
80         // we have to allow both here. InsetCitation takes care that
81         // LaTeX output is nevertheless correct.
82         if (param_info_.empty()) {
83                 param_info_.add("after", ParamInfo::LATEX_OPTIONAL,
84                                 ParamInfo::HANDLING_LATEXIFY);
85                 param_info_.add("before", ParamInfo::LATEX_OPTIONAL,
86                                 ParamInfo::HANDLING_LATEXIFY);
87                 param_info_.add("key", ParamInfo::LATEX_REQUIRED);
88                 param_info_.add("pretextlist", ParamInfo::LATEX_OPTIONAL,
89                                 ParamInfo::HANDLING_LATEXIFY);
90                 param_info_.add("posttextlist", ParamInfo::LATEX_OPTIONAL,
91                                 ParamInfo::HANDLING_LATEXIFY);
92                 param_info_.add("literal", ParamInfo::LYX_INTERNAL);
93         }
94         return param_info_;
95 }
96
97
98 // We allow any command here, since we fall back to cite
99 // anyway if a command is not allowed by a style
100 bool InsetCitation::isCompatibleCommand(string const &)
101 {
102         return true;
103 }
104
105
106 CitationStyle InsetCitation::getCitationStyle(BufferParams const & bp, string const & input,
107                                   vector<CitationStyle> const & valid_styles) const
108 {
109         CitationStyle cs = valid_styles[0];
110         cs.forceUpperCase = false;
111         cs.hasStarredVersion = false;
112
113         string normalized_input = input;
114         string::size_type const n = input.size() - 1;
115         if (isUpperCase(input[0]))
116                 normalized_input[0] = lowercase(input[0]);
117         if (input[n] == '*')
118                 normalized_input = normalized_input.substr(0, n);
119
120         string const alias = bp.getCiteAlias(normalized_input);
121         if (!alias.empty())
122                 normalized_input = alias;
123
124         vector<CitationStyle>::const_iterator it  = valid_styles.begin();
125         vector<CitationStyle>::const_iterator end = valid_styles.end();
126         for (; it != end; ++it) {
127                 CitationStyle this_cs = *it;
128                 if (this_cs.name == normalized_input) {
129                         cs = *it;
130                         break;
131                 }
132         }
133
134         return cs;
135 }
136
137
138 void InsetCitation::doDispatch(Cursor & cur, FuncRequest & cmd)
139 {
140         switch (cmd.action()) {
141         case LFUN_INSET_EDIT:
142                 openCitation();
143                 break;
144         case LFUN_INSET_MODIFY: {
145                 buffer().scheduleBiblioTempRemoval();
146                 cache.recalculate = true;
147                 if (cmd.getArg(0) == "toggleparam") {
148                         string cmdname = getCmdName();
149                         string const alias =
150                                 buffer().masterParams().getCiteAlias(cmdname);
151                         if (!alias.empty())
152                                 cmdname = alias;
153                         string const par = cmd.getArg(1);
154                         string newcmdname = cmdname;
155                         if (par == "star") {
156                                 if (suffixIs(cmdname, "*"))
157                                         newcmdname = rtrim(cmdname, "*");
158                                 else
159                                         newcmdname = cmdname + "*";
160                         } else if (par == "casing") {
161                                 if (isUpperCase(cmdname[0]))
162                                         newcmdname[0] = lowercase(cmdname[0]);
163                                 else
164                                         newcmdname[0] = uppercase(newcmdname[0]);
165                         }
166                         cmd = FuncRequest(LFUN_INSET_MODIFY, "changetype " + newcmdname);
167                 }
168         }
169         // fall through
170         default:
171                 InsetCommand::doDispatch(cur, cmd);
172                 if (cmd.action() == LFUN_INSET_MODIFY)
173                         cleanKeys();
174         }
175 }
176
177 bool InsetCitation::openCitationPossible() const
178 {
179         Buffer const & buf = *buffer_;
180         // only after the buffer is loaded from file...
181         if (!buf.isFullyLoaded())
182                 return false;
183
184         BiblioInfo const & bi = buf.masterBibInfo();
185         if (bi.empty())
186                 return false;
187
188         docstring const & key = getParam("key");
189         if (key.empty())
190                 return false;
191
192         // does bibtex item contains some locator?
193         vector<docstring> keys = getVectorFromString(key);
194         docstring doi, url, file;
195         for (docstring const & kvar : keys) {
196                 bi.getLocators(kvar, doi, url, file);
197                 if (!file.empty() || !doi.empty() || !url.empty())
198                         return true;
199         }
200
201         // last resort: is external script activated?
202         return lyxrc.citation_search;
203 }
204
205 void InsetCitation::openCitation()
206 {
207         Buffer const & buf = *buffer_;
208         BiblioInfo const & bi = buf.masterBibInfo();
209         docstring const & key = getParam("key");
210
211         vector<docstring> keys = getVectorFromString(key);
212         docstring titledata, doi, url, file;
213         for (docstring const & kvar : keys) {
214                 CiteItem ci;
215                 titledata = bi.getInfo(kvar, buffer(), ci,
216                                        from_ascii(lyxrc.citation_search_pattern));
217                 // some cleanup: commas, " and " and " et al.", as used in name lists,
218                 // are not expected in file names
219                 titledata = subst(titledata, from_ascii(","), docstring());
220                 titledata = subst(titledata, from_ascii(" and "), from_ascii(" "));
221                 titledata = subst(titledata, from_ascii(" et al."), docstring());
222                 bi.getLocators(kvar, doi, url, file);
223                 LYXERR(Debug::INSETS, "Locators: doi:" << doi << " url:"
224                         << url << " file:" << file << " title data:" << titledata
225                         << " citation search: " << lyxrc.citation_search
226                         << " citation search pattern: " << lyxrc.citation_search_pattern);
227                 docstring locator;
228                 if (!file.empty()) {
229                         locator = provideScheme(file, from_ascii("file"));
230                 } else if (!doi.empty()) {
231                         locator = provideScheme(doi, from_ascii("doi"));
232                 } else if (!url.empty()) {
233                         locator = url;
234                 } else {
235                         locator = "EXTERNAL " + titledata;
236                 }
237                 LYXERR(Debug::INSETS, "Resolved locator: " << locator);
238                 FuncRequest cmd = FuncRequest(LFUN_CITATION_OPEN, locator);
239                 lyx::dispatch(cmd);
240         }
241 }
242
243
244 bool InsetCitation::getStatus(Cursor & cur, FuncRequest const & cmd,
245         FuncStatus & status) const
246 {
247         switch (cmd.action()) {
248         // Handle the alias case
249         case LFUN_INSET_MODIFY:
250                 if (cmd.getArg(0) == "changetype") {
251                         string cmdname = getCmdName();
252                         string const alias =
253                                 buffer().masterParams().getCiteAlias(cmdname);
254                         if (!alias.empty())
255                                 cmdname = alias;
256                         if (suffixIs(cmdname, "*"))
257                                 cmdname = rtrim(cmdname, "*");
258                         string const newtype = cmd.getArg(1);
259                         status.setEnabled(isCompatibleCommand(newtype));
260                         status.setOnOff(newtype == cmdname);
261                 }
262                 if (cmd.getArg(0) == "toggleparam") {
263                         string cmdname = getCmdName();
264                         string const alias =
265                                 buffer().masterParams().getCiteAlias(cmdname);
266                         if (!alias.empty())
267                                 cmdname = alias;
268                         vector<CitationStyle> citation_styles =
269                                 buffer().masterParams().citeStyles();
270                         CitationStyle cs = getCitationStyle(buffer().masterParams(),
271                                                             cmdname, citation_styles);
272                         if (cmd.getArg(1) == "star") {
273                                 status.setEnabled(cs.hasStarredVersion);
274                                 status.setOnOff(suffixIs(cmdname, "*"));
275                         }
276                         else if (cmd.getArg(1) == "casing") {
277                                 status.setEnabled(cs.forceUpperCase);
278                                 status.setOnOff(isUpperCase(cmdname[0]));
279                         }
280                 }
281                 return true;
282         case LFUN_INSET_EDIT:
283                 return openCitationPossible();
284         default:
285                 return InsetCommand::getStatus(cur, cmd, status);
286         }
287 }
288
289
290 bool InsetCitation::addKey(string const & key)
291 {
292         docstring const ukey = from_utf8(trim(key));
293         docstring const & curkeys = getParam("key");
294         if (curkeys.empty()) {
295                 setParam("key", ukey);
296                 cache.recalculate = true;
297                 return true;
298         }
299
300         vector<docstring> keys = getVectorFromString(curkeys);
301         for (auto const & k : keys) {
302                 if (k == ukey) {
303                         LYXERR0("Key " << key << " already present.");
304                         return false;
305                 }
306         }
307         keys.push_back(ukey);
308         setParam("key", getStringFromVector(keys));
309         cache.recalculate = true;
310         return true;
311 }
312
313
314 docstring InsetCitation::toolTip(BufferView const & bv, int, int) const
315 {
316         Buffer const & buf = bv.buffer();
317         // Only after the buffer is loaded from file...
318         if (!buf.isFullyLoaded())
319                 return docstring();
320
321         BiblioInfo const & bi = buf.masterBibInfo();
322         if (bi.empty())
323                 return _("No bibliography defined!");
324
325         docstring const & key = getParam("key");
326         if (key.empty())
327                 return _("No citations selected!");
328
329         CiteItem ci;
330         ci.richtext = true;
331         vector<docstring> keys = getVectorFromString(key);
332         if (keys.size() == 1)
333                 return bi.getInfo(keys[0], buffer(), ci);
334
335         docstring tip;
336         tip += "<ol>";
337         int count = 0;
338         for (docstring const & kvar : keys) {
339                 docstring const key_info = bi.getInfo(kvar, buffer(), ci);
340                 // limit to reasonable size.
341                 if (count > 9 && keys.size() > 11) {
342                         tip.push_back(0x2026);// HORIZONTAL ELLIPSIS
343                         tip += "<p>"
344                                 + bformat(_("+ %1$d more entries."), int(keys.size() - count))
345                                 + "</p>";
346                         break;
347                 }
348                 if (key_info.empty())
349                         continue;
350                 tip += "<li>" + key_info + "</li>";
351                 ++count;
352         }
353         tip += "</ol>";
354         return tip;
355 }
356
357
358 namespace {
359
360 CitationStyle asValidLatexCommand(BufferParams const & bp, string const & input,
361                                   vector<CitationStyle> const & valid_styles)
362 {
363         CitationStyle cs = valid_styles[0];
364         cs.forceUpperCase = false;
365         cs.hasStarredVersion = false;
366
367         string normalized_input = input;
368         string::size_type const n = input.size() - 1;
369         if (isUpperCase(input[0]))
370                 normalized_input[0] = lowercase(input[0]);
371         if (input[n] == '*')
372                 normalized_input = normalized_input.substr(0, n);
373
374         string const alias = bp.getCiteAlias(normalized_input);
375         if (!alias.empty())
376                 normalized_input = alias;
377
378         vector<CitationStyle>::const_iterator it  = valid_styles.begin();
379         vector<CitationStyle>::const_iterator end = valid_styles.end();
380         for (; it != end; ++it) {
381                 CitationStyle this_cs = *it;
382                 if (this_cs.name == normalized_input) {
383                         cs = *it;
384                         break;
385                 }
386         }
387
388         cs.forceUpperCase &= input[0] == uppercase(input[0]);
389         cs.hasStarredVersion &= input[n] == '*';
390
391         return cs;
392 }
393
394
395 inline docstring wrapCitation(docstring const & key,
396                 docstring const & content, bool for_xhtml)
397 {
398         if (!for_xhtml)
399                 return content;
400         // we have to do the escaping here, because we will ultimately
401         // write this as a raw string, so as not to escape the tags.
402         return "<a href='#LyXCite-" + xml::cleanAttr(key) + "'>" +
403                         xml::escapeString(content, XMLStream::ESCAPE_ALL) + "</a>";
404 }
405
406 } // anonymous namespace
407
408
409 vector<pair<docstring, docstring>> InsetCitation::getQualifiedLists(docstring const & p) const
410 {
411         vector<docstring> ps =
412                 getVectorFromString(p, from_ascii("\t"));
413         QualifiedList res;
414         for (docstring const & s: ps) {
415                 docstring key = s;
416                 docstring val;
417                 if (contains(s, ' '))
418                         val = split(s, key, ' ');
419                 res.push_back(make_pair(key, val));
420         }
421         return res;
422 }
423
424 docstring InsetCitation::generateLabel(bool for_xhtml) const
425 {
426         docstring label;
427         label = complexLabel(for_xhtml);
428
429         // Fallback to fail-safe
430         if (label.empty())
431                 label = basicLabel(for_xhtml);
432
433         return label;
434 }
435
436
437 docstring InsetCitation::complexLabel(bool for_xhtml) const
438 {
439         Buffer const & buf = buffer();
440         // Only start the process off after the buffer is loaded from file.
441         if (!buf.isFullyLoaded())
442                 return docstring();
443
444         docstring const & key = getParam("key");
445
446         BiblioInfo const & biblist = buf.masterBibInfo();
447
448         // mark broken citations
449         setBroken(false);
450
451         if (biblist.empty()) {
452                 setBroken(true);
453                 return docstring();
454         }
455
456         if (key.empty())
457                 return _("No citations selected!");
458
459         // check all citations
460         // we only really want the last 'false', to suppress trimming, but
461         // we need to give the other defaults, too, to set it.
462         vector<docstring> keys =
463                 getVectorFromString(key, from_ascii(","), false, false);
464         for (auto const & k : keys) {
465                 if (biblist.find(k) == biblist.end()) {
466                         setBroken(true);
467                         break;
468                 }
469         }
470         
471         string cite_type = getCmdName();
472         bool const uppercase = isUpperCase(cite_type[0]);
473         if (uppercase)
474                 cite_type[0] = lowercase(cite_type[0]);
475         bool const starred = (cite_type[cite_type.size() - 1] == '*');
476         if (starred)
477                 cite_type = cite_type.substr(0, cite_type.size() - 1);
478
479         // handle alias
480         string const alias = buf.masterParams().getCiteAlias(cite_type);
481         if (!alias.empty())
482                 cite_type = alias;
483
484         // FIXME: allow to add cite macros
485         /*
486         buffer().params().documentClass().addCiteMacro("!textbefore", to_utf8(before));
487         buffer().params().documentClass().addCiteMacro("!textafter", to_utf8(after));
488         */
489         docstring label;
490         CitationStyle cs = getCitationStyle(buffer().masterParams(),
491                         cite_type, buffer().masterParams().citeStyles());
492         bool const qualified = cs.hasQualifiedList
493                 && (keys.size() > 1
494                     || !getParam("pretextlist").empty()
495                     || !getParam("posttextlist").empty());
496         QualifiedList pres = getQualifiedLists(getParam("pretextlist"));
497         QualifiedList posts = getQualifiedLists(getParam("posttextlist"));
498
499         CiteItem ci;
500         ci.textBefore = getParam("before");
501         ci.textAfter = getParam("after");
502         ci.forceUpperCase = uppercase;
503         ci.Starred = starred;
504         ci.max_size = UINT_MAX;
505         ci.isQualified = qualified;
506         ci.pretexts = pres;
507         ci.posttexts = posts;
508         if (for_xhtml) {
509                 ci.max_key_size = UINT_MAX;
510                 ci.context = CiteItem::Export;
511         }
512         ci.richtext = for_xhtml;
513         label = biblist.getLabel(keys, buffer(), cite_type, ci);
514         return label;
515 }
516
517
518 docstring InsetCitation::basicLabel(bool for_xhtml) const
519 {
520         docstring keys = getParam("key");
521         docstring label;
522
523         docstring key;
524         do {
525                 // if there is no comma, then everything goes into key
526                 // and keys will be empty.
527                 keys = split(keys, key, ',');
528                 if (!label.empty())
529                         label += ", ";
530                 label += wrapCitation(key, key, for_xhtml);
531         } while (!keys.empty());
532
533         docstring const & after = getParam("after");
534         if (!after.empty())
535                 label += ", " + after;
536
537         return '[' + label + ']';
538 }
539
540
541 bool InsetCitation::forceLTR(OutputParams const & rp) const
542 {
543         // We have to force LTR for numeric references
544         // [= bibliography, plain BibTeX, numeric natbib
545         // and biblatex]. Except for XeTeX/bidi. See #3005.
546         if (buffer().masterParams().useBidiPackage(rp))
547                 return false;
548         return (buffer().masterParams().citeEngine() == "basic"
549                 || buffer().masterParams().citeEngineType() == ENGINE_TYPE_NUMERICAL);
550 }
551
552 docstring InsetCitation::screenLabel() const
553 {
554         return cache.screen_label;
555 }
556
557
558 void InsetCitation::updateBuffer(ParIterator const &, UpdateType, bool const /*deleted*/)
559 {
560         if (!cache.recalculate && buffer().citeLabelsValid())
561                 return;
562         // The label may have changed, so we have to re-create it.
563         docstring const glabel = generateLabel();
564         cache.recalculate = false;
565         cache.generated_label = glabel;
566         unsigned int const maxLabelChars = 45;
567         cache.screen_label = glabel;
568         support::truncateWithEllipsis(cache.screen_label, maxLabelChars, true);
569 }
570
571
572 void InsetCitation::addToToc(DocIterator const & cpit, bool output_active,
573                                                          UpdateType, TocBackend & backend) const
574 {
575         // NOTE
576         // BiblioInfo::collectCitedEntries() uses the TOC to collect the citations
577         // from the document. It is used indirectly, via BiblioInfo::makeCitationLables,
578         // by both XHTML and plaintext output. So, if we change what goes into the TOC,
579         // then we will also need to change that routine.
580         docstring tocitem;
581         if (isBroken())
582                 tocitem = _("BROKEN: ");
583         tocitem += getParam("key");
584         TocBuilder & b = backend.builder("citation");
585         b.pushItem(cpit, tocitem, output_active);
586         b.pop();
587         if (isBroken()) {
588                 shared_ptr<Toc> toc2 = backend.toc("brokenrefs");
589                 toc2->push_back(TocItem(cpit, 0, tocitem, output_active));
590         }
591 }
592
593
594 int InsetCitation::plaintext(odocstringstream & os,
595        OutputParams const &, size_t) const
596 {
597         string const & cmd = getCmdName();
598         if (cmd == "nocite")
599                 return 0;
600
601         docstring const label = generateLabel();
602         os << label;
603         return label.size();
604 }
605
606
607 static docstring const cleanupWhitespace(docstring const & citelist)
608 {
609         // Paranoia check: make sure that there is no whitespace in here
610         // -- at least not behind commas or at the beginning
611         docstring result;
612         char_type last = ',';
613         for (char_type c : citelist) {
614                 if (c != ' ')
615                         last = c;
616                 if (c != ' ' || last != ',')
617                         result += c;
618         }
619         return result;
620 }
621
622
623 void InsetCitation::cleanKeys() {
624         docstring cleankeys = cleanupWhitespace(getParam("key"));
625         setParam("key", cleankeys);
626 }
627
628 void InsetCitation::docbook(XMLStream & xs, OutputParams const &) const
629 {
630         if (getCmdName() == "nocite")
631                 return;
632
633         // Split the different citations (on ","), so that one tag can be output for each of them.
634         // DocBook does not support having multiple citations in one tag, so that we have to deal with formatting here.
635         docstring citations = getParam("key");
636         if (citations.find(',') == string::npos) {
637                 xs << xml::CompTag("biblioref", "linkend=\"" + to_utf8(xml::cleanID(citations)) + "\"");
638         } else {
639                 size_t pos = 0;
640                 while (pos != string::npos) {
641                         pos = citations.find(',');
642                         xs << xml::CompTag("biblioref", "linkend=\"" + to_utf8(xml::cleanID(citations.substr(0, pos))) + "\"");
643                         citations.erase(0, pos + 1);
644
645                         if (pos != string::npos) {
646                                 xs << ", "; 
647                         }
648                 }
649         }
650 }
651
652
653 docstring InsetCitation::xhtml(XMLStream & xs, OutputParams const &) const
654 {
655         if (getCmdName() == "nocite")
656                 return docstring();
657
658         // have to output this raw, because generateLabel() will include tags
659         xs << XMLStream::ESCAPE_NONE << generateLabel(true);
660
661         return docstring();
662 }
663
664
665 void InsetCitation::toString(odocstream & os) const
666 {
667         odocstringstream ods;
668         plaintext(ods, OutputParams(nullptr));
669         os << ods.str();
670 }
671
672
673 void InsetCitation::forOutliner(docstring & os, size_t const, bool const) const
674 {
675         os += screenLabel();
676 }
677
678
679 // Have to overwrite the default InsetCommand method in order to check that
680 // the \cite command is valid. Eg, the user has natbib enabled, inputs some
681 // citations and then changes his mind, turning natbib support off. The output
682 // should revert to the default citation command as provided by the citation
683 // engine, e.g. \cite[]{} for the basic engine.
684 void InsetCitation::latex(otexstream & os, OutputParams const & runparams) const
685 {
686         // When this is a child compiled on its own, we use the children
687         // own bibinfo, else the master's
688         BiblioInfo const & bi = runparams.is_child
689                         ? buffer().masterBibInfo() : buffer().bibInfo();
690         docstring const key = getParam("key");
691         // "keyonly" command: output the plain key and stop.
692         if (getCmdName() == "keyonly") {
693                 // Special command to only return the key
694                 if (!bi.isBibtex(getParam("key")))
695                         // escape chars with bibitems
696                         os << escape(cleanupWhitespace(key));
697                 else
698                         os << cleanupWhitespace(key);
699                 return;
700         }
701         vector<CitationStyle> citation_styles = buffer().masterParams().citeStyles();
702         CitationStyle cs = asValidLatexCommand(buffer().masterParams(),
703                                                getCmdName(), citation_styles);
704         // FIXME UNICODE
705         docstring const cite_str = from_utf8(citationStyleToString(cs, true));
706
707         // check if we have to do a qualified list
708         vector<docstring> keys = getVectorFromString(cleanupWhitespace(key));
709         bool const qualified = cs.hasQualifiedList
710                 && (!getParam("pretextlist").empty()
711                     || !getParam("posttextlist").empty());
712
713         if (runparams.inulemcmd > 0)
714                 os << "\\mbox{";
715
716         os << "\\" << cite_str;
717
718         if (qualified)
719                 os << "s";
720
721         ParamInfo const & pinfo = findInfo(string());
722         docstring before = params().prepareCommand(runparams, getParam("before"),
723                                                    pinfo["before"].handling());
724         docstring after = params().prepareCommand(runparams, getParam("after"),
725                                                    pinfo["after"].handling());
726         if (!before.empty() && cs.textBefore) {
727                 if (qualified)
728                         os << '(' << protectArgument(before, '(', ')')
729                            << ")(" << protectArgument(after, '(', ')') << ')';
730                 else
731                         os << '[' << protectArgument(before) << "]["
732                            << protectArgument(after) << ']';
733         } else if (!after.empty() && cs.textAfter) {
734                 if (qualified)
735                         os << '(' << protectArgument(after, '(', ')') << ')';
736                 else
737                         os << '[' << protectArgument(after) << ']';
738         }
739
740         if (!bi.isBibtex(key))
741                 // escape chars with bibitems
742                 os << '{' << escape(cleanupWhitespace(key)) << '}';
743         else {
744                 if (qualified) {
745                         QualifiedList pres = getQualifiedLists(getParam("pretextlist"));
746                         QualifiedList posts = getQualifiedLists(getParam("posttextlist"));
747                         for (docstring const & k : keys) {
748                                 docstring prenote;
749                                 QualifiedList::iterator it = pres.begin();
750                                 for (; it != pres.end() ; ++it) {
751                                         if ((*it).first == k) {
752                                                 prenote = (*it).second;
753                                                 pres.erase(it);
754                                                 break;
755                                         }
756                                 }
757                                 docstring bef = params().prepareCommand(runparams, prenote,
758                                                    pinfo["pretextlist"].handling());
759                                 docstring postnote;
760                                 QualifiedList::iterator pit = posts.begin();
761                                 for (; pit != posts.end() ; ++pit) {
762                                         if ((*pit).first == k) {
763                                                 postnote = (*pit).second;
764                                                 posts.erase(pit);
765                                                 break;
766                                         }
767                                 }
768                                 docstring aft = params().prepareCommand(runparams, postnote,
769                                                    pinfo["posttextlist"].handling());
770                                 if (!bef.empty())
771                                         os << '[' << protectArgument(bef)
772                                            << "][" << protectArgument(aft) << ']';
773                                 else if (!aft.empty())
774                                         os << '[' << protectArgument(aft) << ']';
775                                 os << '{' << k << '}';
776                         }
777                 } else
778                         os << '{' << cleanupWhitespace(key) << '}';
779         }
780
781         if (runparams.inulemcmd)
782                 os << "}";
783 }
784
785
786 pair<int, int> InsetCitation::isWords() const
787 {
788         docstring const label = generateLabel(false);
789         return pair<int, int>(label.size(), wordCount(label));
790 }
791
792
793 string InsetCitation::contextMenuName() const
794 {
795         return "context-citation";
796 }
797
798
799 } // namespace lyx