]> git.lyx.org Git - lyx.git/blob - src/insets/insetcite.C
use more specific smart_ptr headers
[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 "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 loding 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                 lowercase(citeType.substr(0,citeType.size()-1)) :
97                 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         char const sep = ';'; // puctuation mark separating citation entries.
124
125         string const op_str(string(1, ' ')  + string(1, op));
126         string const cp_str(string(1, cp)   + string(1, ' '));
127         string const sep_str(string(1, sep) + string(1, ' '));
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 = strip(strip(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 = frontStrip(split(keys, label, ',')) + ",";
210                 while (contains(keys, ",")) {
211                         string key;
212                         keys = frontStrip(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, bool)
228         : InsetCommand(p)
229 {}
230
231
232 string const InsetCitation::generateLabel(Buffer const * buffer) const
233 {
234         string const before = string();
235         string const after  = getOptions();
236
237         string label;
238         if (buffer->params.use_natbib) {
239                 string cmd = getCmdName();
240                 if (cmd == "cite") {
241                         // We may be "upgrading" from an older LyX version.
242                         // If, however, we use "cite" because the necessary
243                         // author/year info is not present in the biblio
244                         // database, then getNatbibLabel will exit gracefully
245                         // and we'll call getBasicLabel.
246                         if (buffer->params.use_numerical_citations)
247                                 cmd = "citep";
248                         else
249                                 cmd = "citet";
250                 }
251                 label = getNatbibLabel(buffer, cmd, getContents(), 
252                                        before, after,
253                                        buffer->params.use_numerical_citations);
254         }
255
256         // Fallback to fail-safe
257         if (label.empty()) {
258                 label = getBasicLabel(getContents(), after);
259         }
260
261         return label;
262 }
263
264
265 InsetCitation::Cache::Style InsetCitation::getStyle(Buffer const * buffer) const
266 {
267         Cache::Style style = Cache::BASIC;
268
269         if (buffer->params.use_natbib) {
270                 if (buffer->params.use_numerical_citations) {
271                         style = Cache::NATBIB_NUM;
272                 } else {
273                         style = Cache::NATBIB_AY;
274                 }
275         }
276
277         return style;
278 }
279
280
281 string const InsetCitation::getScreenLabel(Buffer const * buffer) const
282 {
283         Cache::Style const style = getStyle(buffer);
284         if (cache.params == params() && cache.style == style)
285                 return cache.screen_label;
286
287         // The label has changed, so we have to re-create it.
288         string const before = string();
289         string const after  = getOptions();
290
291         string const glabel = generateLabel(buffer);
292
293         unsigned int const maxLabelChars = 45;
294
295         string label = glabel;
296         if (label.size() > maxLabelChars) {
297                 label.erase(maxLabelChars-3);
298                 label += "...";
299         }
300
301         cache.style  = style;
302         cache.params = params();
303         cache.generated_label = glabel;
304         cache.screen_label = label;
305
306         return label;
307 }
308
309
310 void InsetCitation::edit(BufferView * bv, int, int, unsigned int)
311 {
312         // A call to edit() indicates that we're no longer loading the
313         // buffer but doing some real work.
314         // Doesn't matter if there is no bv->buffer() entry in the map.
315         loading_buffer[bv->buffer()] = false;
316
317         bv->owner()->getDialogs()->showCitation(this);
318 }
319
320
321 void InsetCitation::edit(BufferView * bv, bool)
322 {
323         edit(bv, 0, 0, 0);
324 }
325
326
327 int InsetCitation::ascii(Buffer const * buffer, ostream & os, int) const
328 {
329         string label;
330
331         if (cache.params == params() && cache.style == getStyle(buffer))
332                 label = cache.generated_label;
333         else
334                 label = generateLabel(buffer);
335
336         os << label;
337         return 0;
338 }
339
340
341 // Have to overwrite the default InsetCommand method in order to check that
342 // the \cite command is valid. Eg, the user has natbib enabled, inputs some
343 // citations and then changes his mind, turning natbib support off. The output
344 // should revert to \cite[]{}
345 int InsetCitation::latex(Buffer const * buffer, ostream & os,
346                         bool /*fragile*/, bool/*fs*/) const
347 {
348         os << "\\";
349         if (buffer->params.use_natbib)
350                 os << getCmdName();
351         else
352                 os << "cite";
353         
354         string const before = string();
355         string const after  = getOptions();
356         if (!before.empty() && buffer->params.use_natbib)
357                 os << "[" << before << "][" << after << "]";
358         else if (!after.empty())
359                 os << "[" << after << "]";
360
361         string::const_iterator it  = getContents().begin();
362         string::const_iterator end = getContents().end();
363         // Paranoia check: make sure that there is no whitespace in here
364         string content;
365         for (; it != end; ++it) {
366                 if (*it != ' ') content += *it;
367         }
368
369         os << "{" << content << "}";
370
371         return 0;
372 }
373
374
375 void InsetCitation::validate(LaTeXFeatures & features) const
376 {
377         if (features.bufferParams().use_natbib)
378                 features.require("natbib");
379 }