]> git.lyx.org Git - lyx.git/blob - src/insets/InsetCitation.cpp
4941e09feddcb90bce7b0b805363301f5cba5f14
[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 "BufferParams.h"
18 #include "debug.h"
19 #include "DispatchResult.h"
20 #include "FuncRequest.h"
21 #include "LaTeXFeatures.h"
22
23 #include "support/lstrings.h"
24
25 #include <algorithm>
26
27
28 namespace lyx {
29
30 using support::FileName;
31 using support::getStringFromVector;
32 using support::getVectorFromString;
33 using support::ltrim;
34 using support::prefixIs;
35 using support::rtrim;
36 using support::split;
37 using support::tokenPos;
38
39 using std::endl;
40 using std::string;
41 using std::vector;
42
43 namespace {
44
45 vector<string> const init_possible_cite_commands()
46 {
47         char const * const possible[] = {
48                 "cite", "citet", "citep", "citealt", "citealp",
49                 "citeauthor", "citeyear", "citeyearpar",
50                 "citet*", "citep*", "citealt*", "citealp*", "citeauthor*",
51                 "Citet",  "Citep",  "Citealt",  "Citealp",  "Citeauthor",
52                 "Citet*", "Citep*", "Citealt*", "Citealp*", "Citeauthor*",
53                 "fullcite",
54                 "footcite", "footcitet", "footcitep", "footcitealt",
55                 "footcitealp", "footciteauthor", "footciteyear", "footciteyearpar",
56                 "citefield", "citetitle", "cite*"
57         };
58         size_t const size_possible = sizeof(possible) / sizeof(possible[0]);
59
60         return vector<string>(possible, possible + size_possible);
61 }
62
63
64 vector<string> const & possible_cite_commands()
65 {
66         static vector<string> const possible = init_possible_cite_commands();
67         return possible;
68 }
69
70
71 //FIXME See the header for the issue.
72 string const default_cite_command(biblio::CiteEngine engine)
73 {
74         string str;
75         switch (engine) {
76                 case biblio::ENGINE_BASIC:
77                         str = "cite";
78                         break;
79                 case biblio::ENGINE_NATBIB_AUTHORYEAR:
80                         str = "citet";
81                         break;
82                 case biblio::ENGINE_NATBIB_NUMERICAL:
83                         str = "citep";
84                         break;
85                 case biblio::ENGINE_JURABIB:
86                         str = "cite";
87                         break;
88         }
89         return str;
90 }
91
92                 
93 string const 
94                 asValidLatexCommand(string const & input, biblio::CiteEngine const engine)
95 {
96         string const default_str = default_cite_command(engine);
97         if (!InsetCitation::isCompatibleCommand(input))
98                 return default_str;
99
100         string output;
101         switch (engine) {
102                 case biblio::ENGINE_BASIC:
103                         output = default_str;
104                         break;
105
106                 case biblio::ENGINE_NATBIB_AUTHORYEAR:
107                 case biblio::ENGINE_NATBIB_NUMERICAL:
108                         if (input == "cite" || input == "citefield" ||
109                                                         input == "citetitle" || input == "cite*")
110                                 output = default_str;
111                         else if (prefixIs(input, "foot"))
112                                 output = input.substr(4);
113                         else
114                                 output = input;
115                         break;
116
117                 case biblio::ENGINE_JURABIB: {
118                         // Jurabib does not support the 'uppercase' natbib style.
119                         if (input[0] == 'C')
120                                 output = string(1, 'c') + input.substr(1);
121                         else
122                                 output = input;
123
124                         // Jurabib does not support the 'full' natbib style.
125                         string::size_type const n = output.size() - 1;
126                         if (output != "cite*" && output[n] == '*')
127                                 output = output.substr(0, n);
128
129                         break;
130                 }
131         }
132
133         return output;
134 }
135
136
137 docstring const getNatbibLabel(Buffer const & buffer,
138                             string const & citeType, docstring const & keyList,
139                             docstring const & before, docstring const & after,
140                             biblio::CiteEngine engine)
141 {
142         // Only start the process off after the buffer is loaded from file.
143         if (!buffer.isFullyLoaded())
144                 return docstring();
145
146         // Cache the labels
147         typedef std::map<Buffer const *, BiblioInfo> CachedMap;
148         static CachedMap cached_keys;
149
150         // and cache the timestamp of the bibliography files.
151         static std::map<FileName, time_t> bibfileStatus;
152
153         BiblioInfo biblist;
154
155         vector<FileName> const & bibfilesCache = buffer.getBibfilesCache();
156         // compare the cached timestamps with the actual ones.
157         bool changed = false;
158         for (vector<FileName>::const_iterator it = bibfilesCache.begin();
159                         it != bibfilesCache.end(); ++ it) {
160                 FileName const f = *it;
161                 std::time_t lastw = f.lastModified();
162                 if (lastw != bibfileStatus[f]) {
163                         changed = true;
164                         bibfileStatus[f] = lastw;
165                 }
166         }
167
168         // build the list only if the bibfiles have been changed
169         if (cached_keys[&buffer].empty() || bibfileStatus.empty() || changed) {
170                 biblist.fillWithBibKeys(&buffer);
171                 cached_keys[&buffer] = biblist;
172         } else {
173                 // use the cached keys
174                 biblist = cached_keys[&buffer];
175         }
176
177         if (biblist.empty())
178                 return docstring();
179
180         // the natbib citation-styles
181         // CITET:       author (year)
182         // CITEP:       (author,year)
183         // CITEALT:     author year
184         // CITEALP:     author, year
185         // CITEAUTHOR:  author
186         // CITEYEAR:    year
187         // CITEYEARPAR: (year)
188         // jurabib supports these plus
189         // CITE:        author/<before field>
190
191         // We don't currently use the full or forceUCase fields.
192         string cite_type = asValidLatexCommand(citeType, engine);
193         if (cite_type[0] == 'C')
194                 //If we were going to use them, this would mean ForceUCase
195                 cite_type = string(1, 'c') + cite_type.substr(1);
196         if (cite_type[cite_type.size() - 1] == '*')
197                 //and this would mean FULL
198                 cite_type = cite_type.substr(0, cite_type.size() - 1);
199
200         docstring before_str;
201         if (!before.empty()) {
202                 // In CITET and CITEALT mode, the "before" string is
203                 // attached to the label associated with each and every key.
204                 // In CITEP, CITEALP and CITEYEARPAR mode, it is attached
205                 // to the front of the whole only.
206                 // In other modes, it is not used at all.
207                 if (cite_type == "citet" ||
208                     cite_type == "citealt" ||
209                     cite_type == "citep" ||
210                     cite_type == "citealp" ||
211                     cite_type == "citeyearpar")
212                         before_str = before + ' ';
213                 // In CITE (jurabib), the "before" string is used to attach
214                 // the annotator (of legal texts) to the author(s) of the
215                 // first reference.
216                 else if (cite_type == "cite")
217                         before_str = '/' + before;
218         }
219
220         docstring after_str;
221         if (!after.empty()) {
222                 // The "after" key is appended only to the end of the whole.
223                 after_str = ", " + after;
224         }
225
226         // One day, these might be tunable (as they are in BibTeX).
227         char const op  = '('; // opening parenthesis.
228         char const cp  = ')'; // closing parenthesis.
229         // puctuation mark separating citation entries.
230         char const * const sep = ";";
231
232         docstring const op_str = ' ' + docstring(1, op);
233         docstring const cp_str = docstring(1, cp) + ' ';
234         docstring const sep_str = from_ascii(sep) + ' ';
235
236         docstring label;
237         vector<docstring> keys = getVectorFromString(keyList);
238         vector<docstring>::const_iterator it  = keys.begin();
239         vector<docstring>::const_iterator end = keys.end();
240         for (; it != end; ++it) {
241                 // get the bibdata corresponding to the key
242                 docstring const author(biblist.getAbbreviatedAuthor(*it));
243                 docstring const year(biblist.getYear(*it));
244
245                 // Something isn't right. Fail safely.
246                 if (author.empty() || year.empty())
247                         return docstring();
248
249                 // authors1/<before>;  ... ;
250                 //  authors_last, <after>
251                 if (cite_type == "cite" && engine == biblio::ENGINE_JURABIB) {
252                         if (it == keys.begin())
253                                 label += author + before_str + sep_str;
254                         else
255                                 label += author + sep_str;
256
257                 // (authors1 (<before> year);  ... ;
258                 //  authors_last (<before> year, <after>)
259                 } else if (cite_type == "citet") {
260                         switch (engine) {
261                         case biblio::ENGINE_NATBIB_AUTHORYEAR:
262                                 label += author + op_str + before_str +
263                                         year + cp + sep_str;
264                                 break;
265                         case biblio::ENGINE_NATBIB_NUMERICAL:
266                                 label += author + op_str + before_str + '#' + *it + cp + sep_str;
267                                 break;
268                         case biblio::ENGINE_JURABIB:
269                                 label += before_str + author + op_str +
270                                         year + cp + sep_str;
271                                 break;
272                         case biblio::ENGINE_BASIC:
273                                 break;
274                         }
275
276                 // author, year; author, year; ...
277                 } else if (cite_type == "citep" ||
278                            cite_type == "citealp") {
279                         if (engine == biblio::ENGINE_NATBIB_NUMERICAL) {
280                                 label += *it + sep_str;
281                         } else {
282                                 label += author + ", " + year + sep_str;
283                         }
284
285                 // (authors1 <before> year;
286                 //  authors_last <before> year, <after>)
287                 } else if (cite_type == "citealt") {
288                         switch (engine) {
289                         case biblio::ENGINE_NATBIB_AUTHORYEAR:
290                                 label += author + ' ' + before_str +
291                                         year + sep_str;
292                                 break;
293                         case biblio::ENGINE_NATBIB_NUMERICAL:
294                                 label += author + ' ' + before_str + '#' + *it + sep_str;
295                                 break;
296                         case biblio::ENGINE_JURABIB:
297                                 label += before_str + author + ' ' +
298                                         year + sep_str;
299                                 break;
300                         case biblio::ENGINE_BASIC:
301                                 break;
302                         }
303
304                 // author; author; ...
305                 } else if (cite_type == "citeauthor") {
306                         label += author + sep_str;
307
308                 // year; year; ...
309                 } else if (cite_type == "citeyear" ||
310                            cite_type == "citeyearpar") {
311                         label += year + sep_str;
312                 }
313         }
314         label = rtrim(rtrim(label), sep);
315
316         if (!after_str.empty()) {
317                 if (cite_type == "citet") {
318                         // insert "after" before last ')'
319                         label.insert(label.size() - 1, after_str);
320                 } else {
321                         bool const add =
322                                 !(engine == biblio::ENGINE_NATBIB_NUMERICAL &&
323                                   (cite_type == "citeauthor" ||
324                                    cite_type == "citeyear"));
325                         if (add)
326                                 label += after_str;
327                 }
328         }
329
330         if (!before_str.empty() && (cite_type == "citep" ||
331                                     cite_type == "citealp" ||
332                                     cite_type == "citeyearpar")) {
333                 label = before_str + label;
334         }
335
336         if (cite_type == "citep" || cite_type == "citeyearpar")
337                 label = op + label + cp;
338
339         return label;
340 }
341
342
343 docstring const getBasicLabel(docstring const & keyList, docstring const & after)
344 {
345         using support::contains;
346
347         docstring keys = keyList;
348         docstring label;
349
350         if (contains(keys, ',')) {
351                 // Final comma allows while loop to cover all keys
352                 keys = ltrim(split(keys, label, ',')) + ',';
353                 while (contains(keys, ',')) {
354                         docstring key;
355                         keys = ltrim(split(keys, key, ','));
356                         label += ", " + key;
357                 }
358         } else
359                 label = keys;
360
361         if (!after.empty())
362                 label += ", " + after;
363
364         return '[' + label + ']';
365 }
366
367 } // anon namespace
368
369
370 InsetCitation::InsetCitation(InsetCommandParams const & p)
371         : InsetCommand(p, "citation")
372 {}
373
374
375 CommandInfo const * InsetCitation::findInfo(std::string const & /* cmdName */)
376 {
377         // standard cite does only take one argument if jurabib is
378         // not used, but jurabib extends this to two arguments, so
379         // we have to allow both here. InsetCitation takes care that
380         // LaTeX output is nevertheless correct.
381         static const char * const paramnames[] =
382                 {"after", "before", "key", ""};
383         static const bool isoptional[] = {true, true, false};
384         static const CommandInfo info = {3, paramnames, isoptional};
385         return &info;
386 }
387
388
389 bool InsetCitation::isCompatibleCommand(std::string const & cmd)
390 {
391         vector<string> const & possibles = possible_cite_commands();
392         vector<string>::const_iterator const end = possibles.end();
393         return std::find(possibles.begin(), end, cmd) != end;
394 }
395
396
397 docstring const InsetCitation::generateLabel(Buffer const & buffer) const
398 {
399         docstring const before = getParam("before");
400         docstring const after  = getParam("after");
401
402         docstring label;
403         biblio::CiteEngine const engine = buffer.params().getEngine();
404         if (engine != biblio::ENGINE_BASIC) {
405                 label = getNatbibLabel(buffer, getCmdName(), getParam("key"),
406                                        before, after, engine);
407         }
408
409         // Fallback to fail-safe
410         if (label.empty())
411                 label = getBasicLabel(getParam("key"), after);
412
413         return label;
414 }
415
416
417 docstring const InsetCitation::getScreenLabel(Buffer const & buffer) const
418 {
419         biblio::CiteEngine const engine = buffer.params().getEngine();
420         if (cache.params == params() && cache.engine == engine)
421                 return cache.screen_label;
422
423         // The label has changed, so we have to re-create it.
424         docstring const glabel = generateLabel(buffer);
425
426         unsigned int const maxLabelChars = 45;
427
428         docstring label = glabel;
429         if (label.size() > maxLabelChars) {
430                 label.erase(maxLabelChars-3);
431                 label += "...";
432         }
433
434         cache.engine  = engine;
435         cache.params = params();
436         cache.generated_label = glabel;
437         cache.screen_label = label;
438
439         return label;
440 }
441
442
443 int InsetCitation::plaintext(Buffer const & buffer, odocstream & os,
444                              OutputParams const &) const
445 {
446         docstring str;
447
448         if (cache.params == params() &&
449             cache.engine == buffer.params().getEngine())
450                 str = cache.generated_label;
451         else
452                 str = generateLabel(buffer);
453
454         os << str;
455         return str.size();
456 }
457
458
459 static docstring const cleanupWhitespace(docstring const & citelist)
460 {
461         docstring::const_iterator it  = citelist.begin();
462         docstring::const_iterator end = citelist.end();
463         // Paranoia check: make sure that there is no whitespace in here
464         // -- at least not behind commas or at the beginning
465         docstring result;
466         char_type last = ',';
467         for (; it != end; ++it) {
468                 if (*it != ' ')
469                         last = *it;
470                 if (*it != ' ' || last != ',')
471                         result += *it;
472         }
473         return result;
474 }
475
476
477 int InsetCitation::docbook(Buffer const &, odocstream & os,
478                            OutputParams const &) const
479 {
480         os << "<citation>"
481            << cleanupWhitespace(getParam("key"))
482            << "</citation>";
483         return 0;
484 }
485
486
487 int InsetCitation::textString(Buffer const & buf, odocstream & os,
488                        OutputParams const & op) const
489 {
490         return plaintext(buf, os, op);
491 }
492
493
494 // Have to overwrite the default InsetCommand method in order to check that
495 // the \cite command is valid. Eg, the user has natbib enabled, inputs some
496 // citations and then changes his mind, turning natbib support off. The output
497 // should revert to \cite[]{}
498 int InsetCitation::latex(Buffer const & buffer, odocstream & os,
499                          OutputParams const &) const
500 {
501         biblio::CiteEngine cite_engine = buffer.params().getEngine();
502         // FIXME UNICODE
503         docstring const cite_str = from_utf8(
504                 asValidLatexCommand(getCmdName(), cite_engine));
505
506         os << "\\" << cite_str;
507
508         docstring const & before = getParam("before");
509         docstring const & after  = getParam("after");
510         if (!before.empty() && cite_engine != biblio::ENGINE_BASIC)
511                 os << '[' << before << "][" << after << ']';
512         else if (!after.empty())
513                 os << '[' << after << ']';
514
515         os << '{' << cleanupWhitespace(getParam("key")) << '}';
516
517         return 0;
518 }
519
520
521 void InsetCitation::validate(LaTeXFeatures & features) const
522 {
523         switch (features.bufferParams().getEngine()) {
524         case biblio::ENGINE_BASIC:
525                 break;
526         case biblio::ENGINE_NATBIB_AUTHORYEAR:
527         case biblio::ENGINE_NATBIB_NUMERICAL:
528                 features.require("natbib");
529                 break;
530         case biblio::ENGINE_JURABIB:
531                 features.require("jurabib");
532                 break;
533         }
534 }
535
536
537 } // namespace lyx