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