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