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