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