]> git.lyx.org Git - lyx.git/blob - src/insets/InsetCitation.cpp
Fix bug #6315: counters in insets that don't produce output have ghost values.
[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 "DispatchResult.h"
22 #include "LaTeXFeatures.h"
23 #include "output_xhtml.h"
24 #include "ParIterator.h"
25 #include "TocBackend.h"
26
27 #include "support/debug.h"
28 #include "support/docstream.h"
29 #include "support/FileNameList.h"
30 #include "support/gettext.h"
31 #include "support/lstrings.h"
32
33 #include <algorithm>
34
35 using namespace std;
36 using namespace lyx::support;
37
38 namespace lyx {
39
40 ParamInfo InsetCitation::param_info_;
41
42
43 InsetCitation::InsetCitation(Buffer * buf, InsetCommandParams const & p)
44         : InsetCommand(buf, p)
45 {}
46
47
48 ParamInfo const & InsetCitation::findInfo(string const & /* cmdName */)
49 {
50         // standard cite does only take one argument if jurabib is
51         // not used, but jurabib extends this to two arguments, so
52         // we have to allow both here. InsetCitation takes care that
53         // LaTeX output is nevertheless correct.
54         if (param_info_.empty()) {
55                 param_info_.add("after", ParamInfo::LATEX_OPTIONAL);
56                 param_info_.add("before", ParamInfo::LATEX_OPTIONAL);
57                 param_info_.add("key", ParamInfo::LATEX_REQUIRED);
58         }
59         return param_info_;
60 }
61
62
63 namespace {
64
65 vector<string> const init_possible_cite_commands()
66 {
67         char const * const possible[] = {
68                 "cite", "nocite", "citet", "citep", "citealt", "citealp",
69                 "citeauthor", "citeyear", "citeyearpar",
70                 "citet*", "citep*", "citealt*", "citealp*", "citeauthor*",
71                 "Citet",  "Citep",  "Citealt",  "Citealp",  "Citeauthor",
72                 "Citet*", "Citep*", "Citealt*", "Citealp*", "Citeauthor*",
73                 "fullcite",
74                 "footcite", "footcitet", "footcitep", "footcitealt",
75                 "footcitealp", "footciteauthor", "footciteyear", "footciteyearpar",
76                 "citefield", "citetitle", "cite*"
77         };
78         size_t const size_possible = sizeof(possible) / sizeof(possible[0]);
79
80         return vector<string>(possible, possible + size_possible);
81 }
82
83
84 vector<string> const & possibleCiteCommands()
85 {
86         static vector<string> const possible = init_possible_cite_commands();
87         return possible;
88 }
89
90
91 } // anon namespace
92
93
94 bool InsetCitation::isCompatibleCommand(string const & cmd)
95 {
96         vector<string> const & possibles = possibleCiteCommands();
97         vector<string>::const_iterator const end = possibles.end();
98         return find(possibles.begin(), end, cmd) != end;
99 }
100
101
102 docstring InsetCitation::toolTip(BufferView const & bv, int, int) const
103 {
104         Buffer const & buf = bv.buffer();
105         // Only after the buffer is loaded from file...
106         if (!buf.isFullyLoaded())
107                 return docstring();
108
109         BiblioInfo const & bi = buf.masterBibInfo();
110         if (bi.empty())
111                 return _("No bibliography defined!");
112
113         docstring const & key = getParam("key");
114         if (key.empty())
115                 return _("No citations selected!");
116
117         vector<docstring> keys = getVectorFromString(key);
118         vector<docstring>::const_iterator it = keys.begin();
119         vector<docstring>::const_iterator en = keys.end();
120         docstring tip;
121         for (; it != en; ++it) {
122                 docstring const key_info = bi.getInfo(*it, buffer());
123                 if (key_info.empty())
124                         continue;
125                 if (!tip.empty())
126                         tip += "\n";
127                 tip += wrap(key_info, -4);
128         }
129         return tip;
130 }
131
132
133 namespace {
134         
135 // FIXME See the header for the issue.
136 string defaultCiteCommand(CiteEngine engine)
137 {
138         string str;
139         switch (engine) {
140                 case ENGINE_BASIC:
141                         str = "cite";
142                         break;
143                 case ENGINE_NATBIB_AUTHORYEAR:
144                         str = "citet";
145                         break;
146                 case ENGINE_NATBIB_NUMERICAL:
147                         str = "citep";
148                         break;
149                 case ENGINE_JURABIB:
150                         str = "cite";
151                         break;
152         }
153         return str;
154 }
155
156         
157 string asValidLatexCommand(string const & input, CiteEngine const engine)
158 {
159         string const default_str = defaultCiteCommand(engine);
160         if (!InsetCitation::isCompatibleCommand(input))
161                 return default_str;
162
163         string output;
164         switch (engine) {
165                 case ENGINE_BASIC:
166                         if (input == "nocite")
167                                 output = input;
168                         else
169                                 output = default_str;
170                         break;
171
172                 case ENGINE_NATBIB_AUTHORYEAR:
173                 case ENGINE_NATBIB_NUMERICAL:
174                         if (input == "cite" || input == "citefield"
175                             || input == "citetitle" || input == "cite*")
176                                 output = default_str;
177                         else if (prefixIs(input, "foot"))
178                                 output = input.substr(4);
179                         else
180                                 output = input;
181                         break;
182
183                 case ENGINE_JURABIB: {
184                         // Jurabib does not support the 'uppercase' natbib style.
185                         if (input[0] == 'C')
186                                 output = string(1, 'c') + input.substr(1);
187                         else
188                                 output = input;
189
190                         // Jurabib does not support the 'full' natbib style.
191                         string::size_type const n = output.size() - 1;
192                         if (output != "cite*" && output[n] == '*')
193                                 output = output.substr(0, n);
194
195                         break;
196                 }
197         }
198
199         return output;
200 }
201
202
203 inline docstring wrapCitation(docstring const & key, 
204                 docstring const & content, bool for_xhtml)
205 {
206         if (!for_xhtml)
207                 return content;
208         // we have to do the escaping here, because we will ultimately
209         // write this as a raw string, so as not to escape the tags.
210         return "<a href='#" + key + "'>" +
211                         html::htmlize(content, XHTMLStream::ESCAPE_ALL) + "</a>";
212 }
213
214 } // anonymous namespace
215
216 docstring InsetCitation::generateLabel(bool for_xhtml) const
217 {
218         docstring label;
219         label = complexLabel(for_xhtml);
220
221         // Fallback to fail-safe
222         if (label.empty())
223                 label = basicLabel(for_xhtml);
224
225         return label;
226 }
227
228
229 docstring InsetCitation::complexLabel(bool for_xhtml) const
230 {
231         Buffer const & buf = buffer();
232         // Only start the process off after the buffer is loaded from file.
233         if (!buf.isFullyLoaded())
234                 return docstring();
235
236         BiblioInfo const & biblist = buf.masterBibInfo();
237         if (biblist.empty())
238                 return docstring();
239
240         // the natbib citation-styles
241         // CITET:       author (year)
242         // CITEP:       (author,year)
243         // CITEALT:     author year
244         // CITEALP:     author, year
245         // CITEAUTHOR:  author
246         // CITEYEAR:    year
247         // CITEYEARPAR: (year)
248         // jurabib supports these plus
249         // CITE:        author/<before field>
250
251         CiteEngine const engine = buffer().params().citeEngine();
252         // We don't currently use the full or forceUCase fields.
253         string cite_type = asValidLatexCommand(getCmdName(), engine);
254         if (cite_type[0] == 'C')
255                 // If we were going to use them, this would mean ForceUCase
256                 cite_type = string(1, 'c') + cite_type.substr(1);
257         if (cite_type[cite_type.size() - 1] == '*')
258                 // and this would mean FULL
259                 cite_type = cite_type.substr(0, cite_type.size() - 1);
260
261         docstring const & before = getParam("before");
262         docstring before_str;
263         if (!before.empty()) {
264                 // In CITET and CITEALT mode, the "before" string is
265                 // attached to the label associated with each and every key.
266                 // In CITEP, CITEALP and CITEYEARPAR mode, it is attached
267                 // to the front of the whole only.
268                 // In other modes, it is not used at all.
269                 if (cite_type == "citet" ||
270                     cite_type == "citealt" ||
271                     cite_type == "citep" ||
272                     cite_type == "citealp" ||
273                     cite_type == "citeyearpar")
274                         before_str = before + ' ';
275                 // In CITE (jurabib), the "before" string is used to attach
276                 // the annotator (of legal texts) to the author(s) of the
277                 // first reference.
278                 else if (cite_type == "cite")
279                         before_str = '/' + before;
280         }
281
282         docstring const & after = getParam("after");
283         docstring after_str;
284         // The "after" key is appended only to the end of the whole.
285         if (cite_type == "nocite")
286                 after_str =  " (" + _("not cited") + ')';
287         else if (!after.empty()) {
288                 after_str = ", " + after;
289         }
290
291         // One day, these might be tunable (as they are in BibTeX).
292         char op, cp;    // opening and closing parenthesis.
293         const char * sep;       // punctuation mark separating citation entries.
294         if (engine == ENGINE_BASIC) {
295                 op  = '[';
296                 cp  = ']';
297                 sep = ",";
298         } else {
299                 op  = '(';
300                 cp  = ')';
301                 sep = ";";
302         }
303
304         docstring const op_str = ' ' + docstring(1, op);
305         docstring const cp_str = docstring(1, cp) + ' ';
306         docstring const sep_str = from_ascii(sep) + ' ';
307
308         docstring label;
309         vector<docstring> keys = getVectorFromString(getParam("key"));
310         vector<docstring>::const_iterator it  = keys.begin();
311         vector<docstring>::const_iterator end = keys.end();
312         for (; it != end; ++it) {
313                 // get the bibdata corresponding to the key
314                 docstring const author = biblist.getAbbreviatedAuthor(*it);
315                 docstring const year = biblist.getYear(*it, for_xhtml);
316                 docstring const citenum = for_xhtml ? biblist.getCiteNumber(*it) : *it;
317
318                 if (author.empty() || year.empty())
319                         // We can't construct a "complex" label without that info.
320                         // So fail safely.
321                         return docstring();
322
323                 // authors1/<before>;  ... ;
324                 //  authors_last, <after>
325                 if (cite_type == "cite") {
326                         if (engine == ENGINE_BASIC) {
327                                 label += wrapCitation(*it, citenum, for_xhtml) + sep_str;
328                         } else if (engine == ENGINE_JURABIB) {
329                                 if (it == keys.begin())
330                                         label += wrapCitation(*it, author, for_xhtml) + before_str + sep_str;
331                                 else
332                                         label += wrapCitation(*it, author, for_xhtml) + sep_str;
333                         }
334                 } 
335                 // nocite
336                 else if (cite_type == "nocite") {
337                         label += *it + sep_str;
338                 } 
339                 // (authors1 (<before> year);  ... ;
340                 //  authors_last (<before> year, <after>)
341                 else if (cite_type == "citet") {
342                         switch (engine) {
343                         case ENGINE_NATBIB_AUTHORYEAR:
344                                 label += author + op_str + before_str +
345                                         wrapCitation(*it, year, for_xhtml) + cp + sep_str;
346                                 break;
347                         case ENGINE_NATBIB_NUMERICAL:
348                                 label += author + op_str + before_str + 
349                                         wrapCitation(*it, citenum, for_xhtml) + cp + sep_str;
350                                 break;
351                         case ENGINE_JURABIB:
352                                 label += before_str + author + op_str +
353                                         wrapCitation(*it, year, for_xhtml) + cp + sep_str;
354                                 break;
355                         case ENGINE_BASIC:
356                                 break;
357                         }
358                 } 
359                 // author, year; author, year; ...      
360                 else if (cite_type == "citep" ||
361                            cite_type == "citealp") {
362                         if (engine == ENGINE_NATBIB_NUMERICAL) {
363                                 label += wrapCitation(*it, citenum, for_xhtml) + sep_str;
364                         } else {
365                                 label += wrapCitation(*it, author + ", " + year, for_xhtml) + sep_str;
366                         }
367
368                 } 
369                 // (authors1 <before> year;
370                 //  authors_last <before> year, <after>)
371                 else if (cite_type == "citealt") {
372                         switch (engine) {
373                         case ENGINE_NATBIB_AUTHORYEAR:
374                                 label += author + ' ' + before_str +
375                                         wrapCitation(*it, year, for_xhtml) + sep_str;
376                                 break;
377                         case ENGINE_NATBIB_NUMERICAL:
378                                 label += author + ' ' + before_str + '#' + 
379                                         wrapCitation(*it, citenum, for_xhtml) + sep_str;
380                                 break;
381                         case ENGINE_JURABIB:
382                                 label += before_str + 
383                                         wrapCitation(*it, author + ' ' + year, for_xhtml) + sep_str;
384                                 break;
385                         case ENGINE_BASIC:
386                                 break;
387                         }
388
389                 
390                 } 
391                 // author; author; ...
392                 else if (cite_type == "citeauthor") {
393                         label += wrapCitation(*it, author, for_xhtml) + sep_str;
394                 }
395                 // year; year; ...
396                 else if (cite_type == "citeyear" ||
397                            cite_type == "citeyearpar") {
398                         label += wrapCitation(*it, year, for_xhtml) + sep_str;
399                 }
400         }
401         label = rtrim(rtrim(label), sep);
402
403         if (!after_str.empty()) {
404                 if (cite_type == "citet") {
405                         // insert "after" before last ')'
406                         label.insert(label.size() - 1, after_str);
407                 } else {
408                         bool const add =
409                                 !(engine == ENGINE_NATBIB_NUMERICAL &&
410                                   (cite_type == "citeauthor" ||
411                                    cite_type == "citeyear"));
412                         if (add)
413                                 label += after_str;
414                 }
415         }
416
417         if (!before_str.empty() && (cite_type == "citep" ||
418                                     cite_type == "citealp" ||
419                                     cite_type == "citeyearpar")) {
420                 label = before_str + label;
421         }
422
423         if (cite_type == "citep" || cite_type == "citeyearpar" || 
424             (cite_type == "cite" && engine == ENGINE_BASIC) )
425                 label = op + label + cp;
426
427         return label;
428 }
429
430
431 docstring InsetCitation::basicLabel(bool for_xhtml) const
432 {
433         docstring keys = getParam("key");
434         docstring label;
435
436         docstring key;
437         do {
438                 // if there is no comma, then everything goes into key
439                 // and keys will be empty.
440                 keys = trim(split(keys, key, ','));
441                 key = trim(key);
442                 if (!label.empty())
443                         label += ", ";
444                 label += wrapCitation(key, key, for_xhtml);
445         } while (!keys.empty());
446
447         docstring const & after = getParam("after");
448         if (!after.empty())
449                 label += ", " + after;
450
451         return '[' + label + ']';
452 }
453
454 docstring InsetCitation::screenLabel() const
455 {
456         return cache.screen_label;
457 }
458
459
460 void InsetCitation::updateBuffer(ParIterator const &, UpdateType)
461 {
462         CiteEngine const engine = buffer().params().citeEngine();
463         if (cache.params == params() && cache.engine == engine)
464                 return;
465
466         // The label has changed, so we have to re-create it.
467         docstring const glabel = generateLabel();
468
469         unsigned int const maxLabelChars = 45;
470
471         docstring label = glabel;
472         if (label.size() > maxLabelChars) {
473                 label.erase(maxLabelChars - 3);
474                 label += "...";
475         }
476
477         cache.engine  = engine;
478         cache.params = params();
479         cache.generated_label = glabel;
480         cache.screen_label = label;
481 }
482
483
484 void InsetCitation::addToToc(DocIterator const & cpit)
485 {
486         // NOTE
487         // XHTML output uses the TOC to collect the citations
488         // from the document. So if this gets changed, then we
489         // will need to change how the citations are collected.
490         docstring const tocitem = getParam("key");
491         Toc & toc = buffer().tocBackend().toc("citation");
492         toc.push_back(TocItem(cpit, 0, tocitem));
493 }
494
495
496 int InsetCitation::plaintext(odocstream & os, OutputParams const &) const
497 {
498         os << cache.generated_label;
499         return cache.generated_label.size();
500 }
501
502
503 static docstring const cleanupWhitespace(docstring const & citelist)
504 {
505         docstring::const_iterator it  = citelist.begin();
506         docstring::const_iterator end = citelist.end();
507         // Paranoia check: make sure that there is no whitespace in here
508         // -- at least not behind commas or at the beginning
509         docstring result;
510         char_type last = ',';
511         for (; it != end; ++it) {
512                 if (*it != ' ')
513                         last = *it;
514                 if (*it != ' ' || last != ',')
515                         result += *it;
516         }
517         return result;
518 }
519
520
521 int InsetCitation::docbook(odocstream & os, OutputParams const &) const
522 {
523         os << from_ascii("<citation>")
524            << cleanupWhitespace(getParam("key"))
525            << from_ascii("</citation>");
526         return 0;
527 }
528
529
530 docstring InsetCitation::xhtml(XHTMLStream & xs, OutputParams const &) const
531 {
532         string const & cmd = getCmdName();
533         if (cmd == "nocite")
534                 return docstring();
535
536         // have to output this raw, because generateLabel() will include tags
537         xs << XHTMLStream::ESCAPE_NONE << generateLabel(true);
538
539         return docstring();
540 }
541
542
543 void InsetCitation::tocString(odocstream & os) const
544 {
545         plaintext(os, OutputParams(0));
546 }
547
548
549 // Have to overwrite the default InsetCommand method in order to check that
550 // the \cite command is valid. Eg, the user has natbib enabled, inputs some
551 // citations and then changes his mind, turning natbib support off. The output
552 // should revert to \cite[]{}
553 int InsetCitation::latex(odocstream & os, OutputParams const & runparams) const
554 {
555         CiteEngine cite_engine = buffer().params().citeEngine();
556         BiblioInfo const & bi = buffer().masterBibInfo();
557         // FIXME UNICODE
558         docstring const cite_str = from_utf8(
559                 asValidLatexCommand(getCmdName(), cite_engine));
560
561         if (runparams.inulemcmd)
562                 os << "\\mbox{";
563
564         os << "\\" << cite_str;
565
566         docstring const & before = getParam("before");
567         docstring const & after  = getParam("after");
568         if (!before.empty() && cite_engine != ENGINE_BASIC)
569                 os << '[' << before << "][" << after << ']';
570         else if (!after.empty())
571                 os << '[' << after << ']';
572
573         if (!bi.isBibtex(getParam("key")))
574                 // escape chars with bibitems
575                 os << '{' << escape(cleanupWhitespace(getParam("key"))) << '}';
576         else
577                 os << '{' << cleanupWhitespace(getParam("key")) << '}';
578
579         if (runparams.inulemcmd)
580                 os << "}";
581
582         return 0;
583 }
584
585
586 void InsetCitation::validate(LaTeXFeatures & features) const
587 {
588         switch (features.bufferParams().citeEngine()) {
589         case ENGINE_BASIC:
590                 break;
591         case ENGINE_NATBIB_AUTHORYEAR:
592         case ENGINE_NATBIB_NUMERICAL:
593                 features.require("natbib");
594                 break;
595         case ENGINE_JURABIB:
596                 features.require("jurabib");
597                 break;
598         }
599 }
600
601
602 docstring InsetCitation::contextMenu(BufferView const &, int, int) const
603 {
604         return from_ascii("context-citation");
605 }
606
607
608 } // namespace lyx