]> git.lyx.org Git - lyx.git/blob - src/insets/InsetCitation.cpp
fdd6e0ba1bace0ad0e69761939cd58610033ea45
[lyx.git] / src / insets / InsetCitation.cpp
1 /**
2  * \file InsetCitation.cpp
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 "InsetCitation.h"
15
16 #include "BiblioInfo.h"
17 #include "Buffer.h"
18 #include "buffer_funcs.h"
19 #include "BufferParams.h"
20 #include "BufferView.h"
21 #include "DispatchResult.h"
22 #include "FuncCode.h"
23 #include "FuncRequest.h"
24 #include "LaTeXFeatures.h"
25 #include "output_xhtml.h"
26 #include "ParIterator.h"
27 #include "TocBackend.h"
28
29 #include "support/debug.h"
30 #include "support/docstream.h"
31 #include "support/FileNameList.h"
32 #include "support/gettext.h"
33 #include "support/lstrings.h"
34
35 #include <algorithm>
36
37 using namespace std;
38 using namespace lyx::support;
39
40 namespace lyx {
41
42 ParamInfo InsetCitation::param_info_;
43
44
45 InsetCitation::InsetCitation(Buffer * buf, InsetCommandParams const & p)
46         : InsetCommand(buf, p)
47 {}
48
49
50 ParamInfo const & InsetCitation::findInfo(string const & /* cmdName */)
51 {
52         // standard cite does only take one argument if jurabib is
53         // not used, but jurabib extends this to two arguments, so
54         // we have to allow both here. InsetCitation takes care that
55         // LaTeX output is nevertheless correct.
56         if (param_info_.empty()) {
57                 param_info_.add("after", ParamInfo::LATEX_OPTIONAL);
58                 param_info_.add("before", ParamInfo::LATEX_OPTIONAL);
59                 param_info_.add("key", ParamInfo::LATEX_REQUIRED);
60         }
61         return param_info_;
62 }
63
64
65 namespace {
66
67 vector<string> const init_possible_cite_commands()
68 {
69         char const * const possible[] = {
70                 "cite", "nocite", "citet", "citep", "citealt", "citealp",
71                 "citeauthor", "citeyear", "citeyearpar",
72                 "citet*", "citep*", "citealt*", "citealp*", "citeauthor*",
73                 "Citet",  "Citep",  "Citealt",  "Citealp",  "Citeauthor",
74                 "Citet*", "Citep*", "Citealt*", "Citealp*", "Citeauthor*",
75                 "fullcite",
76                 "footcite", "footcitet", "footcitep", "footcitealt",
77                 "footcitealp", "footciteauthor", "footciteyear", "footciteyearpar",
78                 "citefield", "citetitle", "cite*"
79         };
80         size_t const size_possible = sizeof(possible) / sizeof(possible[0]);
81
82         return vector<string>(possible, possible + size_possible);
83 }
84
85
86 vector<string> const & possibleCiteCommands()
87 {
88         static vector<string> const possible = init_possible_cite_commands();
89         return possible;
90 }
91
92
93 } // anon namespace
94
95
96 // FIXME: use the citeCommands provided by the TextClass
97 // instead of possibleCiteCommands defined in this file.
98 bool InsetCitation::isCompatibleCommand(string const & cmd)
99 {
100         vector<string> const & possibles = possibleCiteCommands();
101         vector<string>::const_iterator const end = possibles.end();
102         return find(possibles.begin(), end, cmd) != end;
103 }
104
105
106 void InsetCitation::doDispatch(Cursor & cur, FuncRequest & cmd)
107 {
108         if (cmd.action() == LFUN_INSET_MODIFY)
109                 cache.recalculate = true;
110         InsetCommand::doDispatch(cur, cmd);
111 }
112
113
114 docstring InsetCitation::toolTip(BufferView const & bv, int, int) const
115 {
116         Buffer const & buf = bv.buffer();
117         // Only after the buffer is loaded from file...
118         if (!buf.isFullyLoaded())
119                 return docstring();
120
121         BiblioInfo const & bi = buf.masterBibInfo();
122         if (bi.empty())
123                 return _("No bibliography defined!");
124
125         docstring const & key = getParam("key");
126         if (key.empty())
127                 return _("No citations selected!");
128
129         vector<docstring> keys = getVectorFromString(key);
130         vector<docstring>::const_iterator it = keys.begin();
131         vector<docstring>::const_iterator en = keys.end();
132         docstring tip;
133         for (; it != en; ++it) {
134                 docstring const key_info = bi.getInfo(*it, buffer());
135                 if (key_info.empty())
136                         continue;
137                 if (!tip.empty())
138                         tip += "\n";
139                 tip += wrap(key_info, -4);
140         }
141         return tip;
142 }
143
144
145 namespace {
146
147
148 CitationStyle asValidLatexCommand(string const & input, vector<CitationStyle> const valid_styles)
149 {
150         CitationStyle cs = valid_styles[0];
151         cs.forceUpperCase = false;
152         cs.fullAuthorList = false;
153         if (!InsetCitation::isCompatibleCommand(input))
154                 return cs;
155
156         string normalized_input = input;
157         string::size_type const n = input.size() - 1;
158         if (input[0] == 'C')
159                 normalized_input[0] = 'c';
160         if (input[n] == '*')
161                 normalized_input = normalized_input.substr(0, n);
162
163         vector<CitationStyle>::const_iterator it  = valid_styles.begin();
164         vector<CitationStyle>::const_iterator end = valid_styles.end();
165         for (; it != end; ++it) {
166                 CitationStyle this_cs = *it;
167                 if (this_cs.cmd == normalized_input) {
168                         cs = *it;
169                         break;
170                 }
171         }
172
173         cs.forceUpperCase &= input[0] == 'C';
174         cs.fullAuthorList &= input[n] == '*';
175
176         return cs;
177 }
178
179
180 inline docstring wrapCitation(docstring const & key,
181                 docstring const & content, bool for_xhtml)
182 {
183         if (!for_xhtml)
184                 return content;
185         // we have to do the escaping here, because we will ultimately
186         // write this as a raw string, so as not to escape the tags.
187         return "<a href='#" + key + "'>" +
188                         html::htmlize(content, XHTMLStream::ESCAPE_ALL) + "</a>";
189 }
190
191 } // anonymous namespace
192
193 docstring InsetCitation::generateLabel(bool for_xhtml) const
194 {
195         docstring label;
196         label = complexLabel(for_xhtml);
197
198         // Fallback to fail-safe
199         if (label.empty())
200                 label = basicLabel(for_xhtml);
201
202         return label;
203 }
204
205
206 docstring InsetCitation::complexLabel(bool for_xhtml) const
207 {
208         Buffer const & buf = buffer();
209         // Only start the process off after the buffer is loaded from file.
210         if (!buf.isFullyLoaded())
211                 return docstring();
212
213         BiblioInfo const & biblist = buf.masterBibInfo();
214         if (biblist.empty())
215                 return docstring();
216
217         docstring const & key = getParam("key");
218         if (key.empty())
219                 return _("No citations selected!");
220
221         // We don't currently use the full or forceUCase fields.
222         string cite_type = getCmdName();
223         if (cite_type[0] == 'C')
224                 // If we were going to use them, this would mean ForceUCase
225                 cite_type = string(1, 'c') + cite_type.substr(1);
226         if (cite_type[cite_type.size() - 1] == '*')
227                 // and this would mean FULL
228                 cite_type = cite_type.substr(0, cite_type.size() - 1);
229
230         docstring const & before = getParam("before");
231         docstring const & after = getParam("after");
232
233         // FIXME: allow to add cite macros
234         /*
235         buffer().params().documentClass().addCiteMacro("!textbefore", to_utf8(before));
236         buffer().params().documentClass().addCiteMacro("!textafter", to_utf8(after));
237         */
238         docstring label;
239         vector<docstring> const keys = getVectorFromString(key);
240         label = biblist.getLabel(keys, buffer(), cite_type, for_xhtml, before, after);
241         return label;
242 }
243
244
245 docstring InsetCitation::basicLabel(bool for_xhtml) const
246 {
247         docstring keys = getParam("key");
248         docstring label;
249
250         docstring key;
251         do {
252                 // if there is no comma, then everything goes into key
253                 // and keys will be empty.
254                 keys = trim(split(keys, key, ','));
255                 key = trim(key);
256                 if (!label.empty())
257                         label += ", ";
258                 label += wrapCitation(key, key, for_xhtml);
259         } while (!keys.empty());
260
261         docstring const & after = getParam("after");
262         if (!after.empty())
263                 label += ", " + after;
264
265         return '[' + label + ']';
266 }
267
268 docstring InsetCitation::screenLabel() const
269 {
270         return cache.screen_label;
271 }
272
273
274 void InsetCitation::updateBuffer(ParIterator const &, UpdateType)
275 {
276         if (!cache.recalculate && buffer().citeLabelsValid())
277                 return;
278
279         // The label may have changed, so we have to re-create it.
280         docstring const glabel = generateLabel();
281
282         unsigned int const maxLabelChars = 45;
283
284         docstring label = glabel;
285         if (label.size() > maxLabelChars) {
286                 label.erase(maxLabelChars - 3);
287                 label += "...";
288         }
289
290         cache.recalculate = false;
291         cache.generated_label = glabel;
292         cache.screen_label = label;
293 }
294
295
296 void InsetCitation::addToToc(DocIterator const & cpit) const
297 {
298         // NOTE
299         // XHTML output uses the TOC to collect the citations
300         // from the document. So if this gets changed, then we
301         // will need to change how the citations are collected.
302         docstring const tocitem = getParam("key");
303         Toc & toc = buffer().tocBackend().toc("citation");
304         toc.push_back(TocItem(cpit, 0, tocitem));
305 }
306
307
308 int InsetCitation::plaintext(odocstream & os, OutputParams const &) const
309 {
310         os << cache.generated_label;
311         return cache.generated_label.size();
312 }
313
314
315 static docstring const cleanupWhitespace(docstring const & citelist)
316 {
317         docstring::const_iterator it  = citelist.begin();
318         docstring::const_iterator end = citelist.end();
319         // Paranoia check: make sure that there is no whitespace in here
320         // -- at least not behind commas or at the beginning
321         docstring result;
322         char_type last = ',';
323         for (; it != end; ++it) {
324                 if (*it != ' ')
325                         last = *it;
326                 if (*it != ' ' || last != ',')
327                         result += *it;
328         }
329         return result;
330 }
331
332
333 int InsetCitation::docbook(odocstream & os, OutputParams const &) const
334 {
335         os << from_ascii("<citation>")
336            << cleanupWhitespace(getParam("key"))
337            << from_ascii("</citation>");
338         return 0;
339 }
340
341
342 docstring InsetCitation::xhtml(XHTMLStream & xs, OutputParams const &) const
343 {
344         string const & cmd = getCmdName();
345         if (cmd == "nocite")
346                 return docstring();
347
348         // have to output this raw, because generateLabel() will include tags
349         xs << XHTMLStream::ESCAPE_NONE << generateLabel(true);
350
351         return docstring();
352 }
353
354
355 void InsetCitation::toString(odocstream & os) const
356 {
357         plaintext(os, OutputParams(0));
358 }
359
360
361 void InsetCitation::forToc(docstring & os, size_t) const
362 {
363         os += screenLabel();
364 }
365
366
367 // Have to overwrite the default InsetCommand method in order to check that
368 // the \cite command is valid. Eg, the user has natbib enabled, inputs some
369 // citations and then changes his mind, turning natbib support off. The output
370 // should revert to the default citation command as provided by the citation
371 // engine, e.g. \cite[]{} for the basic engine.
372 void InsetCitation::latex(otexstream & os, OutputParams const & runparams) const
373 {
374         vector<CitationStyle> citation_styles = buffer().params().citeStyles();
375         CitationStyle cs = asValidLatexCommand(getCmdName(), citation_styles);
376         BiblioInfo const & bi = buffer().masterBibInfo();
377         // FIXME UNICODE
378         docstring const cite_str = from_utf8(citationStyleToString(cs));
379
380         if (runparams.inulemcmd)
381                 os << "\\mbox{";
382
383         os << "\\" << cite_str;
384
385         docstring const & before = getParam("before");
386         docstring const & after  = getParam("after");
387         if (!before.empty() && cs.textBefore)
388                 os << '[' << before << "][" << after << ']';
389         else if (!after.empty() && cs.textAfter)
390                 os << '[' << after << ']';
391
392         if (!bi.isBibtex(getParam("key")))
393                 // escape chars with bibitems
394                 os << '{' << escape(cleanupWhitespace(getParam("key"))) << '}';
395         else
396                 os << '{' << cleanupWhitespace(getParam("key")) << '}';
397
398         if (runparams.inulemcmd)
399                 os << "}";
400 }
401
402
403 string InsetCitation::contextMenuName() const
404 {
405         return "context-citation";
406 }
407
408
409 } // namespace lyx