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