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