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