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