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