]> git.lyx.org Git - lyx.git/blob - src/insets/insetcite.C
Final touch 'inset display()'; fix 'is a bit silly' bug
[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 lyx::support::ascii_lowercase;
27 using lyx::support::contains;
28 using lyx::support::getVectorFromString;
29 using lyx::support::ltrim;
30 using lyx::support::rtrim;
31 using lyx::support::split;
32
33 using std::string;
34 using std::ostream;
35 using std::vector;
36 using std::map;
37
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)
234         : InsetCommand(p)
235 {}
236
237
238 // InsetCitation::InsetCitation(InsetCommandParams const & p, bool)
239 //      : InsetCommand(p, false)
240 // {}
241
242
243 InsetCitation::~InsetCitation()
244 {
245         InsetCommandMailer mailer("citation", *this);
246         mailer.hideDialog();
247 }
248
249
250 string const InsetCitation::generateLabel(Buffer const & buffer) const
251 {
252         string const before = string();
253         string const after  = getOptions();
254
255         string label;
256         if (buffer.params().use_natbib) {
257                 string cmd = getCmdName();
258                 if (cmd == "cite") {
259                         // We may be "upgrading" from an older LyX version.
260                         // If, however, we use "cite" because the necessary
261                         // author/year info is not present in the biblio
262                         // database, then getNatbibLabel will exit gracefully
263                         // and we'll call getBasicLabel.
264                         if (buffer.params().use_numerical_citations)
265                                 cmd = "citep";
266                         else
267                                 cmd = "citet";
268                 }
269                 label = getNatbibLabel(buffer, cmd, getContents(),
270                                        before, after,
271                                        buffer.params().use_numerical_citations);
272         }
273
274         // Fallback to fail-safe
275         if (label.empty()) {
276                 label = getBasicLabel(getContents(), after);
277         }
278
279         return label;
280 }
281
282
283 InsetCitation::Cache::Style InsetCitation::getStyle(Buffer const & buffer) const
284 {
285         Cache::Style style = Cache::BASIC;
286
287         if (buffer.params().use_natbib) {
288                 if (buffer.params().use_numerical_citations) {
289                         style = Cache::NATBIB_NUM;
290                 } else {
291                         style = Cache::NATBIB_AY;
292                 }
293         }
294
295         return style;
296 }
297
298
299 string const InsetCitation::getScreenLabel(Buffer const & buffer) const
300 {
301         Cache::Style const style = getStyle(buffer);
302         if (cache.params == params() && cache.style == style)
303                 return cache.screen_label;
304
305         // The label has changed, so we have to re-create it.
306         string const before = string();
307         string const after  = getOptions();
308
309         string const glabel = generateLabel(buffer);
310
311         unsigned int const maxLabelChars = 45;
312
313         string label = glabel;
314         if (label.size() > maxLabelChars) {
315                 label.erase(maxLabelChars-3);
316                 label += "...";
317         }
318
319         cache.style  = style;
320         cache.params = params();
321         cache.generated_label = glabel;
322         cache.screen_label = label;
323
324         return label;
325 }
326
327
328 void InsetCitation::setLoadingBuffer(Buffer const & buffer, bool state) const
329 {
330         // Doesn't matter if there is no bv->buffer() entry in the map.
331         loading_buffer[&buffer] = state;
332 }
333
334
335 dispatch_result
336 InsetCitation::priv_dispatch(FuncRequest const & cmd,
337                              idx_type & idx, pos_type & pos)
338 {
339         switch (cmd.action) {
340         case LFUN_INSET_EDIT:
341                 // A call to edit indicates that we're no longer loading the
342                 // buffer but doing some real work.
343                 setLoadingBuffer(*cmd.view()->buffer(), false);
344                 InsetCommandMailer("citation", *this).showDialog(cmd.view());
345                 return DISPATCHED;
346
347         default:
348                 return InsetCommand::priv_dispatch(cmd, idx, pos);
349         }
350 }
351
352
353 int InsetCitation::ascii(Buffer const & buffer, ostream & os, int) const
354 {
355         if (cache.params == params() && cache.style == getStyle(buffer))
356                 os << cache.generated_label;
357         else
358                 os << generateLabel(buffer);
359         return 0;
360 }
361
362
363 // Have to overwrite the default InsetCommand method in order to check that
364 // the \cite command is valid. Eg, the user has natbib enabled, inputs some
365 // citations and then changes his mind, turning natbib support off. The output
366 // should revert to \cite[]{}
367 int InsetCitation::latex(Buffer const & buffer, ostream & os,
368                          LatexRunParams const &) const
369 {
370         os << "\\";
371         if (buffer.params().use_natbib)
372                 os << getCmdName();
373         else
374                 os << "cite";
375
376 #warning What is this code supposed to do? (Lgb)
377 // my guess is that this is just waiting for when we support before,
378 // so it's a oneliner. But this is very silly ! - jbl
379
380 #if 1
381         // The current strange code
382
383         string const before = string();
384         string const after  = getOptions();
385         if (!before.empty() && buffer.params().use_natbib)
386                 os << '[' << before << "][" << after << ']';
387         else if (!after.empty())
388                 os << '[' << after << ']';
389 #else
390         // and the cleaned up equvalent, should it just be changed? (Lgb)
391         string const after  = getOptions();
392         if (!after.empty())
393                 os << '[' << after << ']';
394 #endif
395         string::const_iterator it  = getContents().begin();
396         string::const_iterator end = getContents().end();
397         // Paranoia check: make sure that there is no whitespace in here
398         string content;
399         char last = ',';
400         for (; it != end; ++it) {
401                 if (*it != ' ')
402                         last = *it;
403                 if (*it != ' ' || last != ',')
404                         content += *it;
405         }
406
407         os << '{' << content << '}';
408
409         return 0;
410 }
411
412
413 void InsetCitation::validate(LaTeXFeatures & features) const
414 {
415         if (features.bufferParams().use_natbib)
416                 features.require("natbib");
417 }