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