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