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