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