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 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;
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;
46 string const getNatbibLabel(Buffer const & buffer,
47 string const & citeType, string const & keyList,
48 string const & before, string const & after,
51 typedef std::map<Buffer const *, biblio::InfoMap> CachedMap;
52 static CachedMap cached_keys;
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;
60 bool loadkeys = !loading_buffer[&buffer];
62 CachedMap::iterator kit = cached_keys.find(&buffer);
63 loadkeys = kit == cached_keys.end();
68 typedef vector<std::pair<string, string> > InfoType;
70 buffer.fillWithBibKeys(bibkeys);
72 InfoType::const_iterator bit = bibkeys.begin();
73 InfoType::const_iterator bend = bibkeys.end();
75 biblio::InfoMap infomap;
76 for (; bit != bend; ++bit) {
77 infomap[bit->first] = bit->second;
82 cached_keys[&buffer] = infomap;
85 biblio::InfoMap infomap = cached_keys[&buffer];
87 // the natbib citation-styles
88 // CITET: author (year)
89 // CITEP: (author,year)
90 // CITEALT: author year
91 // CITEALP: author, year
94 // CITEYEARPAR: (year)
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] == '*';
100 string const cite_type = full ?
101 ascii_lowercase(citeType.substr(0, citeType.size() - 1)) :
102 ascii_lowercase(citeType);
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 + ' ';
120 if (!after.empty()) {
121 // The "after" key is appended only to the end of the whole.
122 after_str = ", " + after;
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 = ";";
131 string const op_str(' ' + string(1, op));
132 string const cp_str(string(1, cp) + ' ');
133 string const sep_str(string(sep) + ' ');
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));
144 // Something isn't right. Fail safely.
145 if (author.empty() || year.empty())
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 +
155 // author, year; author, year; ...
156 } else if (cite_type == "citep" ||
157 cite_type == "citealp") {
159 label += *it + sep_str;
161 label += author + ", " + year + sep_str;
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;
170 // author; author; ...
171 } else if (cite_type == "citeauthor") {
172 label += author + sep_str;
175 } else if (cite_type == "citeyear" ||
176 cite_type == "citeyearpar") {
177 label += year + sep_str;
180 label = rtrim(rtrim(label), sep);
182 if (!after_str.empty()) {
183 if (cite_type == "citet") {
184 // insert "after" before last ')'
185 label.insert(label.size() - 1, after_str);
187 bool const add = !(numerical &&
188 (cite_type == "citeauthor" ||
189 cite_type == "citeyear"));
195 if (!before_str.empty() && (cite_type == "citep" ||
196 cite_type == "citealp" ||
197 cite_type == "citeyearpar")) {
198 label = before_str + label;
201 if (cite_type == "citep" || cite_type == "citeyearpar")
202 label = string(1, op) + label + string(1, cp);
208 string const getBasicLabel(string const & keyList, string const & after)
210 string keys(keyList);
213 if (contains(keys, ",")) {
214 // Final comma allows while loop to cover all keys
215 keys = ltrim(split(keys, label, ',')) + ',';
216 while (contains(keys, ",")) {
218 keys = ltrim(split(keys, key, ','));
225 label += ", " + after;
227 return '[' + label + ']';
233 InsetCitation::InsetCitation(InsetCommandParams const & p)
238 // InsetCitation::InsetCitation(InsetCommandParams const & p, bool)
239 // : InsetCommand(p, false)
243 InsetCitation::~InsetCitation()
245 InsetCommandMailer mailer("citation", *this);
250 string const InsetCitation::generateLabel(Buffer const & buffer) const
252 string const before = string();
253 string const after = getOptions();
256 if (buffer.params().use_natbib) {
257 string cmd = getCmdName();
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)
269 label = getNatbibLabel(buffer, cmd, getContents(),
271 buffer.params().use_numerical_citations);
274 // Fallback to fail-safe
276 label = getBasicLabel(getContents(), after);
283 InsetCitation::Cache::Style InsetCitation::getStyle(Buffer const & buffer) const
285 Cache::Style style = Cache::BASIC;
287 if (buffer.params().use_natbib) {
288 if (buffer.params().use_numerical_citations) {
289 style = Cache::NATBIB_NUM;
291 style = Cache::NATBIB_AY;
299 string const InsetCitation::getScreenLabel(Buffer const & buffer) const
301 Cache::Style const style = getStyle(buffer);
302 if (cache.params == params() && cache.style == style)
303 return cache.screen_label;
305 // The label has changed, so we have to re-create it.
306 string const before = string();
307 string const after = getOptions();
309 string const glabel = generateLabel(buffer);
311 unsigned int const maxLabelChars = 45;
313 string label = glabel;
314 if (label.size() > maxLabelChars) {
315 label.erase(maxLabelChars-3);
320 cache.params = params();
321 cache.generated_label = glabel;
322 cache.screen_label = label;
328 void InsetCitation::setLoadingBuffer(Buffer const & buffer, bool state) const
330 // Doesn't matter if there is no bv->buffer() entry in the map.
331 loading_buffer[&buffer] = state;
336 InsetCitation::priv_dispatch(FuncRequest const & cmd,
337 idx_type & idx, pos_type & pos)
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());
348 return InsetCommand::priv_dispatch(cmd, idx, pos);
353 int InsetCitation::ascii(Buffer const & buffer, ostream & os, int) const
355 if (cache.params == params() && cache.style == getStyle(buffer))
356 os << cache.generated_label;
358 os << generateLabel(buffer);
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
371 if (buffer.params().use_natbib)
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
381 // The current strange code
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 << ']';
390 // and the cleaned up equvalent, should it just be changed? (Lgb)
391 string const after = getOptions();
393 os << '[' << after << ']';
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
400 for (; it != end; ++it) {
403 if (*it != ' ' || last != ',')
407 os << '{' << content << '}';
413 void InsetCitation::validate(LaTeXFeatures & features) const
415 if (features.bufferParams().use_natbib)
416 features.require("natbib");