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