]> git.lyx.org Git - lyx.git/blob - src/insets/insetcite.C
203b96b8d1e239fe8bdee2ccae07717b61df3583
[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 "BufferView.h"
19 #include "dispatchresult.h"
20 #include "funcrequest.h"
21 #include "LaTeXFeatures.h"
22
23 #include "frontends/controllers/biblio.h"
24
25 #include "support/lstrings.h"
26
27 using lyx::support::ascii_lowercase;
28 using lyx::support::contains;
29 using lyx::support::getVectorFromString;
30 using lyx::support::ltrim;
31 using lyx::support::rtrim;
32 using lyx::support::split;
33
34 using std::string;
35 using std::ostream;
36 using std::vector;
37 using std::map;
38
39
40 namespace {
41
42 string const getNatbibLabel(Buffer const & buffer,
43                             string const & citeType, string const & keyList,
44                             string const & before, string const & after,
45                             biblio::CiteEngine engine)
46 {
47         // Only start the process off after the buffer is loaded from file.
48         if (!buffer.fully_loaded())
49                 return string();
50
51         typedef std::map<Buffer const *, biblio::InfoMap> CachedMap;
52         static CachedMap cached_keys;
53
54         // build the keylist
55         typedef vector<std::pair<string, string> > InfoType;
56         InfoType bibkeys;
57         buffer.fillWithBibKeys(bibkeys);
58
59         InfoType::const_iterator bit  = bibkeys.begin();
60         InfoType::const_iterator bend = bibkeys.end();
61
62         biblio::InfoMap infomap;
63         for (; bit != bend; ++bit) {
64                 infomap[bit->first] = bit->second;
65         }
66         if (infomap.empty())
67                 return string();
68
69         cached_keys[&buffer] = infomap;
70
71         // the natbib citation-styles
72         // CITET:       author (year)
73         // CITEP:       (author,year)
74         // CITEALT:     author year
75         // CITEALP:     author, year
76         // CITEAUTHOR:  author
77         // CITEYEAR:    year
78         // CITEYEARPAR: (year)
79         // jurabib supports these plus
80         // CITE:        author/<before field>
81
82         // We don't currently use the full or forceUCase fields.
83         // bool const forceUCase = citeType[0] == 'C';
84         bool const full = citeType[citeType.size() - 1] == '*';
85
86         string const cite_type = full ?
87                 ascii_lowercase(citeType.substr(0, citeType.size() - 1)) :
88                 ascii_lowercase(citeType);
89
90         string before_str;
91         if (!before.empty()) {
92                 // In CITET and CITEALT mode, the "before" string is
93                 // attached to the label associated with each and every key.
94                 // In CITEP, CITEALP and CITEYEARPAR mode, it is attached
95                 // to the front of the whole only.
96                 // In other modes, it is not used at all.
97                 if (cite_type == "citet" ||
98                     cite_type == "citealt" ||
99                     cite_type == "citep" ||
100                     cite_type == "citealp" ||
101                     cite_type == "citeyearpar")
102                         before_str = before + ' ';
103                 // In CITE (jurabib), the "before" string is used to attach
104                 // the annotator (of legal texts) to the author(s) of the
105                 // first reference.
106                 else if (cite_type == "cite")
107                         before_str = '/' + before;
108         }
109
110         string after_str;
111         if (!after.empty()) {
112                 // The "after" key is appended only to the end of the whole.
113                 after_str = ", " + after;
114         }
115
116         // One day, these might be tunable (as they are in BibTeX).
117         char const op  = '('; // opening parenthesis.
118         char const cp  = ')'; // closing parenthesis.
119         // puctuation mark separating citation entries.
120         char const * const sep = ";";
121
122         string const op_str(' ' + string(1, op));
123         string const cp_str(string(1, cp) + ' ');
124         string const sep_str(string(sep) + ' ');
125
126         string label;
127         vector<string> keys = getVectorFromString(keyList);
128         vector<string>::const_iterator it  = keys.begin();
129         vector<string>::const_iterator end = keys.end();
130         for (; it != end; ++it) {
131                 // get the bibdata corresponding to the key
132                 string const author(biblio::getAbbreviatedAuthor(infomap, *it));
133                 string const year(biblio::getYear(infomap, *it));
134
135                 // Something isn't right. Fail safely.
136                 if (author.empty() || year.empty())
137                         return string();
138
139                 // authors1/<before>;  ... ;
140                 //  authors_last, <after>
141                 if (cite_type == "cite" && engine == biblio::ENGINE_JURABIB) {
142                         if (it == keys.begin())
143                                 label += author + before_str + sep_str;
144                         else
145                                 label += author + sep_str;
146
147                 // (authors1 (<before> year);  ... ;
148                 //  authors_last (<before> year, <after>)
149                 } else if (cite_type == "citet") {
150                         switch (engine) {
151                         case biblio::ENGINE_NATBIB_AUTHORYEAR:
152                                 label += author + op_str + before_str +
153                                         year + cp + sep_str;
154                                 break;
155                         case biblio::ENGINE_NATBIB_NUMERICAL:
156                                 label += author + op_str + before_str +
157                                         '#' + *it + cp + sep_str;
158                                 break;
159                         case biblio::ENGINE_JURABIB:
160                                 label += before_str + author + op_str +
161                                         year + cp + sep_str;
162                                 break;
163                         case biblio::ENGINE_BASIC:
164                                 break;
165                         }
166
167                 // author, year; author, year; ...
168                 } else if (cite_type == "citep" ||
169                            cite_type == "citealp") {
170                         if (engine == biblio::ENGINE_NATBIB_NUMERICAL) {
171                                 label += *it + sep_str;
172                         } else {
173                                 label += author + ", " + year + sep_str;
174                         }
175
176                 // (authors1 <before> year;
177                 //  authors_last <before> year, <after>)
178                 } else if (cite_type == "citealt") {
179                         switch (engine) {
180                         case biblio::ENGINE_NATBIB_AUTHORYEAR:
181                                 label += author + ' ' + before_str +
182                                         year + sep_str;
183                                 break;
184                         case biblio::ENGINE_NATBIB_NUMERICAL:
185                                 label += author + ' ' + before_str +
186                                         '#' + *it + sep_str;
187                                 break;
188                         case biblio::ENGINE_JURABIB:
189                                 label += before_str + author + ' ' +
190                                         year + sep_str;
191                                 break;
192                         case biblio::ENGINE_BASIC:
193                                 break;
194                         }
195
196                 // author; author; ...
197                 } else if (cite_type == "citeauthor") {
198                         label += author + sep_str;
199
200                 // year; year; ...
201                 } else if (cite_type == "citeyear" ||
202                            cite_type == "citeyearpar") {
203                         label += year + sep_str;
204                 }
205         }
206         label = rtrim(rtrim(label), sep);
207
208         if (!after_str.empty()) {
209                 if (cite_type == "citet") {
210                         // insert "after" before last ')'
211                         label.insert(label.size() - 1, after_str);
212                 } else {
213                         bool const add =
214                                 !(engine == biblio::ENGINE_NATBIB_NUMERICAL &&
215                                   (cite_type == "citeauthor" ||
216                                    cite_type == "citeyear"));
217                         if (add)
218                                 label += after_str;
219                 }
220         }
221
222         if (!before_str.empty() && (cite_type == "citep" ||
223                                     cite_type == "citealp" ||
224                                     cite_type == "citeyearpar")) {
225                 label = before_str + label;
226         }
227
228         if (cite_type == "citep" || cite_type == "citeyearpar")
229                 label = string(1, op) + label + string(1, cp);
230
231         return label;
232 }
233
234
235 string const getBasicLabel(string const & keyList, string const & after)
236 {
237         string keys(keyList);
238         string label;
239
240         if (contains(keys, ',')) {
241                 // Final comma allows while loop to cover all keys
242                 keys = ltrim(split(keys, label, ',')) + ',';
243                 while (contains(keys, ',')) {
244                         string key;
245                         keys = ltrim(split(keys, key, ','));
246                         label += ", " + key;
247                 }
248         } else
249                 label = keys;
250
251         if (!after.empty())
252                 label += ", " + after;
253
254         return '[' + label + ']';
255 }
256
257 } // anon namespace
258
259
260 InsetCitation::InsetCitation(InsetCommandParams const & p)
261         : InsetCommand(p, "citation")
262 {}
263
264
265 string const InsetCitation::generateLabel(Buffer const & buffer) const
266 {
267         string const before = getSecOptions();
268         string const after  = getOptions();
269
270         string label;
271         biblio::CiteEngine const engine = buffer.params().cite_engine;
272         if (engine != biblio::ENGINE_BASIC) {
273                 string cmd = getCmdName();
274                 if (cmd == "cite") {
275                         // We may be "upgrading" from an older LyX version.
276                         // If, however, we use "cite" because the necessary
277                         // author/year info is not present in the biblio
278                         // database, then getNatbibLabel will exit gracefully
279                         // and we'll call getBasicLabel.
280                         if (engine == biblio::ENGINE_NATBIB_NUMERICAL)
281                                 cmd = "citep";
282                         else if (engine == biblio::ENGINE_NATBIB_AUTHORYEAR)
283                                 cmd = "citet";
284                 }
285                 label = getNatbibLabel(buffer, cmd, getContents(),
286                                        before, after, engine);
287         }
288
289         // Fallback to fail-safe
290         if (label.empty()) {
291                 label = getBasicLabel(getContents(), after);
292         }
293
294         return label;
295 }
296
297
298 string const InsetCitation::getScreenLabel(Buffer const & buffer) const
299 {
300         biblio::CiteEngine const engine = biblio::getEngine(buffer);
301         if (cache.params == params() && cache.engine == engine)
302                 return cache.screen_label;
303
304         // The label has changed, so we have to re-create it.
305         string const before = getSecOptions();
306         string const after  = getOptions();
307
308         string const glabel = generateLabel(buffer);
309
310         unsigned int const maxLabelChars = 45;
311
312         string label = glabel;
313         if (label.size() > maxLabelChars) {
314                 label.erase(maxLabelChars-3);
315                 label += "...";
316         }
317
318         cache.engine  = engine;
319         cache.params = params();
320         cache.generated_label = glabel;
321         cache.screen_label = label;
322
323         return label;
324 }
325
326
327 int InsetCitation::plaintext(Buffer const & buffer, ostream & os, int) const
328 {
329         if (cache.params == params() &&
330             cache.engine == biblio::getEngine(buffer))
331                 os << cache.generated_label;
332         else
333                 os << generateLabel(buffer);
334         return 0;
335 }
336
337
338 // Have to overwrite the default InsetCommand method in order to check that
339 // the \cite command is valid. Eg, the user has natbib enabled, inputs some
340 // citations and then changes his mind, turning natbib support off. The output
341 // should revert to \cite[]{}
342 int InsetCitation::latex(Buffer const & buffer, ostream & os,
343                          OutputParams const &) const
344 {
345         biblio::CiteEngine const cite_engine = buffer.params().cite_engine;
346         
347         os << "\\";
348         switch (cite_engine) {
349         case biblio::ENGINE_BASIC:
350                 os << "cite";
351                 break;
352         case biblio::ENGINE_NATBIB_AUTHORYEAR:
353         case biblio::ENGINE_NATBIB_NUMERICAL:
354                 os << getCmdName();
355                 break;
356         case biblio::ENGINE_JURABIB: 
357         {
358                 // jurabib does not (yet) support "force upper case"
359                 // and "full author name". Fallback.
360                 string cmd = getCmdName();
361                 if (cmd[0] == 'C')
362                         cmd[0] = 'c';
363                 size_t n = cmd.size() - 1;
364                 if (cmd[n] == '*')
365                         cmd = cmd.substr(0,n);
366                 os << cmd;
367                 break;
368         }
369         }
370         
371         string const before = getSecOptions();
372         string const after  = getOptions();
373         if (!before.empty() && cite_engine != biblio::ENGINE_BASIC)
374                 os << '[' << before << "][" << after << ']';
375         else if (!after.empty())
376                 os << '[' << after << ']';
377
378         string::const_iterator it  = getContents().begin();
379         string::const_iterator end = getContents().end();
380         // Paranoia check: make sure that there is no whitespace in here
381         string content;
382         char last = ',';
383         for (; it != end; ++it) {
384                 if (*it != ' ')
385                         last = *it;
386                 if (*it != ' ' || last != ',')
387                         content += *it;
388         }
389
390         os << '{' << content << '}';
391
392         return 0;
393 }
394
395
396 void InsetCitation::validate(LaTeXFeatures & features) const
397 {
398         switch (features.bufferParams().cite_engine) {
399         case biblio::ENGINE_BASIC:
400                 break;
401         case biblio::ENGINE_NATBIB_AUTHORYEAR:
402         case biblio::ENGINE_NATBIB_NUMERICAL:
403                 features.require("natbib");
404                 break;
405         case biblio::ENGINE_JURABIB:
406                 features.require("jurabib");
407                 break;
408         }
409 }