]> git.lyx.org Git - lyx.git/blob - src/insets/InsetCitation.cpp
5921e7f1305b916ea2eaaf8eea77692bf81668ba
[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 "Buffer.h"
17 #include "buffer_funcs.h"
18 #include "BufferParams.h"
19 #include "BufferView.h"
20 #include "DispatchResult.h"
21 #include "FuncRequest.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, "citation")
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);
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 } // anonymous namespace
203
204 docstring InsetCitation::generateLabel() const
205 {
206         docstring label;
207         label = complexLabel();
208
209         // Fallback to fail-safe
210         if (label.empty())
211                 label = basicLabel();
212
213         return label;
214 }
215
216
217 docstring InsetCitation::complexLabel() const
218 {
219         Buffer const & buf = buffer();
220         // Only start the process off after the buffer is loaded from file.
221         if (!buf.isFullyLoaded())
222                 return docstring();
223
224         BiblioInfo const & biblist = buf.masterBibInfo();
225         if (biblist.empty())
226                 return docstring();
227
228         // the natbib citation-styles
229         // CITET:       author (year)
230         // CITEP:       (author,year)
231         // CITEALT:     author year
232         // CITEALP:     author, year
233         // CITEAUTHOR:  author
234         // CITEYEAR:    year
235         // CITEYEARPAR: (year)
236         // jurabib supports these plus
237         // CITE:        author/<before field>
238
239         CiteEngine const engine = buffer().params().citeEngine();
240         // We don't currently use the full or forceUCase fields.
241         string cite_type = asValidLatexCommand(getCmdName(), engine);
242         if (cite_type[0] == 'C')
243                 // If we were going to use them, this would mean ForceUCase
244                 cite_type = string(1, 'c') + cite_type.substr(1);
245         if (cite_type[cite_type.size() - 1] == '*')
246                 // and this would mean FULL
247                 cite_type = cite_type.substr(0, cite_type.size() - 1);
248
249         docstring const & before = getParam("before");
250         docstring before_str;
251         if (!before.empty()) {
252                 // In CITET and CITEALT mode, the "before" string is
253                 // attached to the label associated with each and every key.
254                 // In CITEP, CITEALP and CITEYEARPAR mode, it is attached
255                 // to the front of the whole only.
256                 // In other modes, it is not used at all.
257                 if (cite_type == "citet" ||
258                     cite_type == "citealt" ||
259                     cite_type == "citep" ||
260                     cite_type == "citealp" ||
261                     cite_type == "citeyearpar")
262                         before_str = before + ' ';
263                 // In CITE (jurabib), the "before" string is used to attach
264                 // the annotator (of legal texts) to the author(s) of the
265                 // first reference.
266                 else if (cite_type == "cite")
267                         before_str = '/' + before;
268         }
269
270         docstring const & after = getParam("after");
271         docstring after_str;
272         // The "after" key is appended only to the end of the whole.
273         if (cite_type == "nocite")
274                 after_str =  " (" + _("not cited") + ')';
275         else if (!after.empty()) {
276                 after_str = ", " + after;
277         }
278
279         // One day, these might be tunable (as they are in BibTeX).
280         char op, cp;    // opening and closing parenthesis.
281         const char * sep;       // punctuation mark separating citation entries.
282         if (engine == ENGINE_BASIC) {
283                 op  = '[';
284                 cp  = ']';
285                 sep = ",";
286         } else {
287                 op  = '(';
288                 cp  = ')';
289                 sep = ";";
290         }
291
292         docstring const op_str = ' ' + docstring(1, op);
293         docstring const cp_str = docstring(1, cp) + ' ';
294         docstring const sep_str = from_ascii(sep) + ' ';
295
296         docstring label;
297         vector<docstring> keys = getVectorFromString(getParam("key"));
298         vector<docstring>::const_iterator it  = keys.begin();
299         vector<docstring>::const_iterator end = keys.end();
300         for (; it != end; ++it) {
301                 // get the bibdata corresponding to the key
302                 docstring const author = biblist.getAbbreviatedAuthor(*it);
303                 docstring const year = biblist.getYear(*it);
304
305                 // Something isn't right. Fail safely.
306                 if (author.empty() || year.empty())
307                         return docstring();
308
309                 // authors1/<before>;  ... ;
310                 //  authors_last, <after>
311                 if (cite_type == "cite") {
312                         if (engine == ENGINE_BASIC) {
313                                 label += *it + sep_str;
314                         } else if (engine == ENGINE_JURABIB) {
315                                 if (it == keys.begin())
316                                         label += author + before_str + sep_str;
317                                 else
318                                         label += author + sep_str;
319                         }
320
321                 // nocite
322                 } else if (cite_type == "nocite") {
323                         label += *it + sep_str;
324
325                 // (authors1 (<before> year);  ... ;
326                 //  authors_last (<before> year, <after>)
327                 } else if (cite_type == "citet") {
328                         switch (engine) {
329                         case ENGINE_NATBIB_AUTHORYEAR:
330                                 label += author + op_str + before_str +
331                                         year + cp + sep_str;
332                                 break;
333                         case ENGINE_NATBIB_NUMERICAL:
334                                 label += author + op_str + before_str + '#' + *it + cp + sep_str;
335                                 break;
336                         case ENGINE_JURABIB:
337                                 label += before_str + author + op_str +
338                                         year + cp + sep_str;
339                                 break;
340                         case ENGINE_BASIC:
341                                 break;
342                         }
343
344                 // author, year; author, year; ...
345                 } else if (cite_type == "citep" ||
346                            cite_type == "citealp") {
347                         if (engine == ENGINE_NATBIB_NUMERICAL) {
348                                 label += *it + sep_str;
349                         } else {
350                                 label += author + ", " + year + sep_str;
351                         }
352
353                 // (authors1 <before> year;
354                 //  authors_last <before> year, <after>)
355                 } else if (cite_type == "citealt") {
356                         switch (engine) {
357                         case ENGINE_NATBIB_AUTHORYEAR:
358                                 label += author + ' ' + before_str +
359                                         year + sep_str;
360                                 break;
361                         case ENGINE_NATBIB_NUMERICAL:
362                                 label += author + ' ' + before_str + '#' + *it + sep_str;
363                                 break;
364                         case ENGINE_JURABIB:
365                                 label += before_str + author + ' ' +
366                                         year + sep_str;
367                                 break;
368                         case ENGINE_BASIC:
369                                 break;
370                         }
371
372                 // author; author; ...
373                 } else if (cite_type == "citeauthor") {
374                         label += author + sep_str;
375
376                 // year; year; ...
377                 } else if (cite_type == "citeyear" ||
378                            cite_type == "citeyearpar") {
379                         label += year + sep_str;
380                 }
381         }
382         label = rtrim(rtrim(label), sep);
383
384         if (!after_str.empty()) {
385                 if (cite_type == "citet") {
386                         // insert "after" before last ')'
387                         label.insert(label.size() - 1, after_str);
388                 } else {
389                         bool const add =
390                                 !(engine == ENGINE_NATBIB_NUMERICAL &&
391                                   (cite_type == "citeauthor" ||
392                                    cite_type == "citeyear"));
393                         if (add)
394                                 label += after_str;
395                 }
396         }
397
398         if (!before_str.empty() && (cite_type == "citep" ||
399                                     cite_type == "citealp" ||
400                                     cite_type == "citeyearpar")) {
401                 label = before_str + label;
402         }
403
404         if (cite_type == "citep" || cite_type == "citeyearpar" || 
405             (cite_type == "cite" && engine == ENGINE_BASIC) )
406                 label = op + label + cp;
407
408         return label;
409 }
410
411
412 docstring InsetCitation::basicLabel() const
413 {
414         docstring keys = getParam("key");
415         docstring label;
416
417         if (contains(keys, ',')) {
418                 // Final comma allows while loop to cover all keys
419                 keys = ltrim(split(keys, label, ',')) + ',';
420                 while (contains(keys, ',')) {
421                         docstring key;
422                         keys = ltrim(split(keys, key, ','));
423                         label += ", " + key;
424                 }
425         } else {
426                 label = keys;
427         }
428
429         docstring const & after = getParam("after");
430         if (!after.empty())
431                 label += ", " + after;
432
433         return '[' + label + ']';
434 }
435
436 docstring InsetCitation::screenLabel() const
437 {
438         return cache.screen_label;
439 }
440
441
442 void InsetCitation::updateLabels(ParIterator const &, bool)
443 {
444         CiteEngine const engine = buffer().params().citeEngine();
445         if (cache.params == params() && cache.engine == engine)
446                 return;
447
448         // The label has changed, so we have to re-create it.
449         docstring const glabel = generateLabel();
450
451         unsigned int const maxLabelChars = 45;
452
453         docstring label = glabel;
454         if (label.size() > maxLabelChars) {
455                 label.erase(maxLabelChars-3);
456                 label += "...";
457         }
458
459         cache.engine  = engine;
460         cache.params = params();
461         cache.generated_label = glabel;
462         cache.screen_label = label;
463 }
464
465
466 void InsetCitation::addToToc(DocIterator const & cpit)
467 {
468         Toc & toc = buffer().tocBackend().toc("citation");
469         toc.push_back(TocItem(cpit, 0, getParam("key")));
470 }
471
472
473 int InsetCitation::plaintext(odocstream & os, OutputParams const &) const
474 {
475         os << cache.generated_label;
476         return cache.generated_label.size();
477 }
478
479
480 static docstring const cleanupWhitespace(docstring const & citelist)
481 {
482         docstring::const_iterator it  = citelist.begin();
483         docstring::const_iterator end = citelist.end();
484         // Paranoia check: make sure that there is no whitespace in here
485         // -- at least not behind commas or at the beginning
486         docstring result;
487         char_type last = ',';
488         for (; it != end; ++it) {
489                 if (*it != ' ')
490                         last = *it;
491                 if (*it != ' ' || last != ',')
492                         result += *it;
493         }
494         return result;
495 }
496
497
498 int InsetCitation::docbook(odocstream & os, OutputParams const &) const
499 {
500         os << from_ascii("<citation>")
501            << cleanupWhitespace(getParam("key"))
502            << from_ascii("</citation>");
503         return 0;
504 }
505
506
507 docstring InsetCitation::xhtml(XHTMLStream & xs, OutputParams const &) const
508 {
509         string const & cmd = getCmdName();
510         if (cmd == "nocite")
511                 return docstring();
512
513         BiblioInfo const & bi = buffer().masterBibInfo();
514         docstring const & key_list = getParam("key");
515         if (key_list.empty())
516                 return docstring();
517
518         // FIXME We should do a better job outputing different things for the
519         // different citation styles.   For now, we use square brackets for every
520         // case.
521         xs << "[";
522         docstring const & before = getParam("before");
523         if (!before.empty())
524                 xs << before << " ";
525
526         vector<docstring> const keys = getVectorFromString(key_list);
527         vector<docstring>::const_iterator it = keys.begin();
528         vector<docstring>::const_iterator const en = keys.end();
529         bool first = true;
530         for (; it != en; ++it) {
531                 BiblioInfo::const_iterator const bt = bi.find(*it);
532                 if (bt == bi.end())
533                         continue;
534                 BibTeXInfo const & bibinfo = bt->second;
535                 if (!first) {
536                         xs << ", ";
537                         first = false;
538                 }
539                 docstring citekey = bibinfo.citeKey();
540                 if (citekey.empty()) {
541                         citekey = bibinfo.label();
542                         if (citekey.empty())
543                                 citekey = *it;
544                 }
545                 string const attr = "href='#" + to_utf8(*it) + "'";
546                 xs << StartTag("a", attr) << citekey << EndTag("a");
547         }
548
549         docstring const & after = getParam("after");
550         if (!after.empty())
551                 xs << ", " << after;
552         xs << "]";
553         return docstring();
554 }
555
556
557 void InsetCitation::tocString(odocstream & os) const
558 {
559         plaintext(os, OutputParams(0));
560 }
561
562
563 // Have to overwrite the default InsetCommand method in order to check that
564 // the \cite command is valid. Eg, the user has natbib enabled, inputs some
565 // citations and then changes his mind, turning natbib support off. The output
566 // should revert to \cite[]{}
567 int InsetCitation::latex(odocstream & os, OutputParams const & runparams) const
568 {
569         CiteEngine cite_engine = buffer().params().citeEngine();
570         // FIXME UNICODE
571         docstring const cite_str = from_utf8(
572                 asValidLatexCommand(getCmdName(), cite_engine));
573
574         if (runparams.inulemcmd)
575                 os << "\\mbox{";
576
577         os << "\\" << cite_str;
578
579         docstring const & before = getParam("before");
580         docstring const & after  = getParam("after");
581         if (!before.empty() && cite_engine != ENGINE_BASIC)
582                 os << '[' << before << "][" << after << ']';
583         else if (!after.empty())
584                 os << '[' << after << ']';
585
586         os << '{' << cleanupWhitespace(getParam("key")) << '}';
587
588         if (runparams.inulemcmd)
589                 os << "}";
590
591         return 0;
592 }
593
594
595 void InsetCitation::validate(LaTeXFeatures & features) const
596 {
597         switch (features.bufferParams().citeEngine()) {
598         case ENGINE_BASIC:
599                 break;
600         case ENGINE_NATBIB_AUTHORYEAR:
601         case ENGINE_NATBIB_NUMERICAL:
602                 features.require("natbib");
603                 break;
604         case ENGINE_JURABIB:
605                 features.require("jurabib");
606                 break;
607         }
608 }
609
610
611 docstring InsetCitation::contextMenu(BufferView const &, int, int) const
612 {
613         return from_ascii("context-citation");
614 }
615
616
617 } // namespace lyx