3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Angus Leeming
9 * Full author contact details are available in file CREDITS.
14 #include "insetcite.h"
17 #include "bufferparams.h"
18 #include "BufferView.h"
19 #include "funcrequest.h"
20 #include "LaTeXFeatures.h"
22 #include "frontends/controllers/biblio.h"
24 #include "support/lstrings.h"
26 using namespace lyx::support;
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;
40 string const getNatbibLabel(Buffer const & buffer,
41 string const & citeType, string const & keyList,
42 string const & before, string const & after,
45 typedef std::map<Buffer const *, biblio::InfoMap> CachedMap;
46 static CachedMap cached_keys;
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;
54 bool loadkeys = !loading_buffer[&buffer];
56 CachedMap::iterator kit = cached_keys.find(&buffer);
57 loadkeys = kit == cached_keys.end();
62 typedef vector<std::pair<string, string> > InfoType;
64 buffer.fillWithBibKeys(bibkeys);
66 InfoType::const_iterator bit = bibkeys.begin();
67 InfoType::const_iterator bend = bibkeys.end();
69 biblio::InfoMap infomap;
70 for (; bit != bend; ++bit) {
71 infomap[bit->first] = bit->second;
76 cached_keys[&buffer] = infomap;
79 biblio::InfoMap infomap = cached_keys[&buffer];
81 // the natbib citation-styles
82 // CITET: author (year)
83 // CITEP: (author,year)
84 // CITEALT: author year
85 // CITEALP: author, year
88 // CITEYEARPAR: (year)
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] == '*';
94 string const cite_type = full ?
95 ascii_lowercase(citeType.substr(0, citeType.size() - 1)) :
96 ascii_lowercase(citeType);
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 + ' ';
114 if (!after.empty()) {
115 // The "after" key is appended only to the end of the whole.
116 after_str = ", " + after;
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 = ";";
125 string const op_str(' ' + string(1, op));
126 string const cp_str(string(1, cp) + ' ');
127 string const sep_str(string(sep) + ' ');
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));
138 // Something isn't right. Fail safely.
139 if (author.empty() || year.empty())
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 +
149 // author, year; author, year; ...
150 } else if (cite_type == "citep" ||
151 cite_type == "citealp") {
153 label += *it + sep_str;
155 label += author + ", " + year + sep_str;
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;
164 // author; author; ...
165 } else if (cite_type == "citeauthor") {
166 label += author + sep_str;
169 } else if (cite_type == "citeyear" ||
170 cite_type == "citeyearpar") {
171 label += year + sep_str;
174 label = rtrim(rtrim(label), sep);
176 if (!after_str.empty()) {
177 if (cite_type == "citet") {
178 // insert "after" before last ')'
179 label.insert(label.size() - 1, after_str);
181 bool const add = !(numerical &&
182 (cite_type == "citeauthor" ||
183 cite_type == "citeyear"));
189 if (!before_str.empty() && (cite_type == "citep" ||
190 cite_type == "citealp" ||
191 cite_type == "citeyearpar")) {
192 label = before_str + label;
195 if (cite_type == "citep" || cite_type == "citeyearpar")
196 label = string(1, op) + label + string(1, cp);
202 string const getBasicLabel(string const & keyList, string const & after)
204 string keys(keyList);
207 if (contains(keys, ",")) {
208 // Final comma allows while loop to cover all keys
209 keys = ltrim(split(keys, label, ',')) + ',';
210 while (contains(keys, ",")) {
212 keys = ltrim(split(keys, key, ','));
219 label += ", " + after;
221 return '[' + label + ']';
227 InsetCitation::InsetCitation(InsetCommandParams const & p)
232 // InsetCitation::InsetCitation(InsetCommandParams const & p, bool)
233 // : InsetCommand(p, false)
237 InsetCitation::~InsetCitation()
239 InsetCommandMailer mailer("citation", *this);
244 string const InsetCitation::generateLabel(Buffer const & buffer) const
246 string const before = string();
247 string const after = getOptions();
250 if (buffer.params().use_natbib) {
251 string cmd = getCmdName();
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)
263 label = getNatbibLabel(buffer, cmd, getContents(),
265 buffer.params().use_numerical_citations);
268 // Fallback to fail-safe
270 label = getBasicLabel(getContents(), after);
277 InsetCitation::Cache::Style InsetCitation::getStyle(Buffer const & buffer) const
279 Cache::Style style = Cache::BASIC;
281 if (buffer.params().use_natbib) {
282 if (buffer.params().use_numerical_citations) {
283 style = Cache::NATBIB_NUM;
285 style = Cache::NATBIB_AY;
293 string const InsetCitation::getScreenLabel(Buffer const & buffer) const
295 Cache::Style const style = getStyle(buffer);
296 if (cache.params == params() && cache.style == style)
297 return cache.screen_label;
299 // The label has changed, so we have to re-create it.
300 string const before = string();
301 string const after = getOptions();
303 string const glabel = generateLabel(buffer);
305 unsigned int const maxLabelChars = 45;
307 string label = glabel;
308 if (label.size() > maxLabelChars) {
309 label.erase(maxLabelChars-3);
314 cache.params = params();
315 cache.generated_label = glabel;
316 cache.screen_label = label;
322 void InsetCitation::setLoadingBuffer(Buffer const & buffer, bool state) const
324 // Doesn't matter if there is no bv->buffer() entry in the map.
325 loading_buffer[&buffer] = state;
329 dispatch_result InsetCitation::localDispatch(FuncRequest const & cmd)
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());
340 return InsetCommand::localDispatch(cmd);
345 int InsetCitation::ascii(Buffer const & buffer, ostream & os, int) const
347 if (cache.params == params() && cache.style == getStyle(buffer))
348 os << cache.generated_label;
350 os << generateLabel(buffer);
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
363 if (buffer.params().use_natbib)
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
373 // The current strange code
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 << ']';
382 // and the cleaned up equvalent, should it just be changed? (Lgb)
383 string const after = getOptions();
385 os << '[' << after << ']';
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
392 for (; it != end; ++it) {
395 if (*it != ' ' || last != ',')
399 os << '{' << content << '}';
405 void InsetCitation::validate(LaTeXFeatures & features) const
407 if (features.bufferParams().use_natbib)
408 features.require("natbib");