lyx_mirror/src/insets/InsetCitation.cpp

611 lines
15 KiB
C++
Raw Normal View History

/**
* \file InsetCitation.cpp
* This file is part of LyX, the document processor.
* Licence details can be found in the file COPYING.
*
* \author Angus Leeming
* \author Herbert Voß
*
* Full author contact details are available in file CREDITS.
*/
#include <config.h>
#include "InsetCitation.h"
#include "Buffer.h"
#include "buffer_funcs.h"
#include "BufferParams.h"
#include "BufferView.h"
#include "DispatchResult.h"
#include "FuncRequest.h"
#include "LaTeXFeatures.h"
#include "output_xhtml.h"
#include "ParIterator.h"
#include "TocBackend.h"
#include "support/debug.h"
#include "support/docstream.h"
#include "support/FileNameList.h"
#include "support/gettext.h"
#include "support/lstrings.h"
#include <algorithm>
using namespace std;
using namespace lyx::support;
namespace lyx {
namespace {
vector<string> const init_possible_cite_commands()
{
char const * const possible[] = {
"cite", "nocite", "citet", "citep", "citealt", "citealp",
"citeauthor", "citeyear", "citeyearpar",
"citet*", "citep*", "citealt*", "citealp*", "citeauthor*",
"Citet", "Citep", "Citealt", "Citealp", "Citeauthor",
"Citet*", "Citep*", "Citealt*", "Citealp*", "Citeauthor*",
"fullcite",
"footcite", "footcitet", "footcitep", "footcitealt",
"footcitealp", "footciteauthor", "footciteyear", "footciteyearpar",
"citefield", "citetitle", "cite*"
};
size_t const size_possible = sizeof(possible) / sizeof(possible[0]);
return vector<string>(possible, possible + size_possible);
}
vector<string> const & possibleCiteCommands()
{
static vector<string> const possible = init_possible_cite_commands();
return possible;
}
// FIXME See the header for the issue.
string defaultCiteCommand(CiteEngine engine)
{
string str;
switch (engine) {
case ENGINE_BASIC:
str = "cite";
break;
case ENGINE_NATBIB_AUTHORYEAR:
str = "citet";
break;
case ENGINE_NATBIB_NUMERICAL:
str = "citep";
break;
case ENGINE_JURABIB:
str = "cite";
break;
}
return str;
}
string asValidLatexCommand(string const & input, CiteEngine const engine)
{
string const default_str = defaultCiteCommand(engine);
if (!InsetCitation::isCompatibleCommand(input))
return default_str;
string output;
switch (engine) {
case ENGINE_BASIC:
if (input == "nocite")
output = input;
else
output = default_str;
break;
case ENGINE_NATBIB_AUTHORYEAR:
case ENGINE_NATBIB_NUMERICAL:
if (input == "cite" || input == "citefield"
|| input == "citetitle" || input == "cite*")
output = default_str;
else if (prefixIs(input, "foot"))
output = input.substr(4);
else
output = input;
break;
case ENGINE_JURABIB: {
// Jurabib does not support the 'uppercase' natbib style.
if (input[0] == 'C')
output = string(1, 'c') + input.substr(1);
else
output = input;
// Jurabib does not support the 'full' natbib style.
string::size_type const n = output.size() - 1;
if (output != "cite*" && output[n] == '*')
output = output.substr(0, n);
break;
}
}
return output;
}
docstring complexLabel(Buffer const & buffer,
string const & citeType, docstring const & keyList,
docstring const & before, docstring const & after,
CiteEngine engine)
{
// Only start the process off after the buffer is loaded from file.
if (!buffer.isFullyLoaded())
return docstring();
BiblioInfo const & biblist = buffer.masterBibInfo();
if (biblist.empty())
return docstring();
// the natbib citation-styles
// CITET: author (year)
// CITEP: (author,year)
// CITEALT: author year
// CITEALP: author, year
// CITEAUTHOR: author
// CITEYEAR: year
// CITEYEARPAR: (year)
// jurabib supports these plus
// CITE: author/<before field>
// We don't currently use the full or forceUCase fields.
string cite_type = asValidLatexCommand(citeType, engine);
if (cite_type[0] == 'C')
// If we were going to use them, this would mean ForceUCase
cite_type = string(1, 'c') + cite_type.substr(1);
if (cite_type[cite_type.size() - 1] == '*')
// and this would mean FULL
cite_type = cite_type.substr(0, cite_type.size() - 1);
docstring before_str;
if (!before.empty()) {
// In CITET and CITEALT mode, the "before" string is
// attached to the label associated with each and every key.
// In CITEP, CITEALP and CITEYEARPAR mode, it is attached
// to the front of the whole only.
// In other modes, it is not used at all.
if (cite_type == "citet" ||
cite_type == "citealt" ||
cite_type == "citep" ||
cite_type == "citealp" ||
cite_type == "citeyearpar")
before_str = before + ' ';
// In CITE (jurabib), the "before" string is used to attach
// the annotator (of legal texts) to the author(s) of the
// first reference.
else if (cite_type == "cite")
before_str = '/' + before;
}
docstring after_str;
// The "after" key is appended only to the end of the whole.
if (cite_type == "nocite")
after_str = " (" + _("not cited") + ')';
else if (!after.empty()) {
after_str = ", " + after;
}
// One day, these might be tunable (as they are in BibTeX).
char op, cp; // opening and closing parenthesis.
const char * sep; // punctuation mark separating citation entries.
if (engine == ENGINE_BASIC) {
op = '[';
cp = ']';
sep = ",";
} else {
op = '(';
cp = ')';
sep = ";";
}
docstring const op_str = ' ' + docstring(1, op);
docstring const cp_str = docstring(1, cp) + ' ';
docstring const sep_str = from_ascii(sep) + ' ';
docstring label;
vector<docstring> keys = getVectorFromString(keyList);
vector<docstring>::const_iterator it = keys.begin();
vector<docstring>::const_iterator end = keys.end();
for (; it != end; ++it) {
// get the bibdata corresponding to the key
docstring const author(biblist.getAbbreviatedAuthor(*it));
docstring const year(biblist.getYear(*it));
// Something isn't right. Fail safely.
if (author.empty() || year.empty())
return docstring();
// authors1/<before>; ... ;
// authors_last, <after>
if (cite_type == "cite") {
if (engine == ENGINE_BASIC) {
label += *it + sep_str;
} else if (engine == ENGINE_JURABIB) {
if (it == keys.begin())
label += author + before_str + sep_str;
else
label += author + sep_str;
}
// nocite
} else if (cite_type == "nocite") {
label += *it + sep_str;
// (authors1 (<before> year); ... ;
// authors_last (<before> year, <after>)
} else if (cite_type == "citet") {
switch (engine) {
case ENGINE_NATBIB_AUTHORYEAR:
label += author + op_str + before_str +
year + cp + sep_str;
break;
case ENGINE_NATBIB_NUMERICAL:
label += author + op_str + before_str + '#' + *it + cp + sep_str;
break;
case ENGINE_JURABIB:
label += before_str + author + op_str +
year + cp + sep_str;
break;
case ENGINE_BASIC:
break;
}
// author, year; author, year; ...
} else if (cite_type == "citep" ||
cite_type == "citealp") {
if (engine == ENGINE_NATBIB_NUMERICAL) {
label += *it + sep_str;
} else {
label += author + ", " + year + sep_str;
}
// (authors1 <before> year;
// authors_last <before> year, <after>)
} else if (cite_type == "citealt") {
switch (engine) {
case ENGINE_NATBIB_AUTHORYEAR:
label += author + ' ' + before_str +
year + sep_str;
break;
case ENGINE_NATBIB_NUMERICAL:
label += author + ' ' + before_str + '#' + *it + sep_str;
break;
case ENGINE_JURABIB:
label += before_str + author + ' ' +
year + sep_str;
break;
case ENGINE_BASIC:
break;
}
// author; author; ...
} else if (cite_type == "citeauthor") {
label += author + sep_str;
// year; year; ...
} else if (cite_type == "citeyear" ||
cite_type == "citeyearpar") {
label += year + sep_str;
}
}
label = rtrim(rtrim(label), sep);
if (!after_str.empty()) {
if (cite_type == "citet") {
// insert "after" before last ')'
label.insert(label.size() - 1, after_str);
} else {
bool const add =
!(engine == ENGINE_NATBIB_NUMERICAL &&
(cite_type == "citeauthor" ||
cite_type == "citeyear"));
if (add)
label += after_str;
}
}
if (!before_str.empty() && (cite_type == "citep" ||
cite_type == "citealp" ||
cite_type == "citeyearpar")) {
label = before_str + label;
}
if (cite_type == "citep" || cite_type == "citeyearpar" ||
(cite_type == "cite" && engine == ENGINE_BASIC) )
label = op + label + cp;
return label;
}
docstring basicLabel(docstring const & keyList, docstring const & after)
{
docstring keys = keyList;
docstring label;
if (contains(keys, ',')) {
// Final comma allows while loop to cover all keys
keys = ltrim(split(keys, label, ',')) + ',';
while (contains(keys, ',')) {
docstring key;
keys = ltrim(split(keys, key, ','));
label += ", " + key;
}
} else {
label = keys;
}
if (!after.empty())
label += ", " + after;
return '[' + label + ']';
}
} // anon namespace
ParamInfo InsetCitation::param_info_;
InsetCitation::InsetCitation(Buffer * buf, InsetCommandParams const & p)
: InsetCommand(buf, p, "citation")
{}
ParamInfo const & InsetCitation::findInfo(string const & /* cmdName */)
{
// standard cite does only take one argument if jurabib is
// not used, but jurabib extends this to two arguments, so
// we have to allow both here. InsetCitation takes care that
// LaTeX output is nevertheless correct.
if (param_info_.empty()) {
Per Abdel's suggestion that we focus on bug-fixing at this point, this will be the last patch in this series for a bit. But I wanted to get this done before I forget what it is I was doing, so here it is. The idea behind this patch is to make real key-value support for InsetCommand parameters possible. This should be particularly useful for the listings version of InsetInclude, though we would need some kind of UI for it before it would really be helpful. (See below for some thoughts.) This doesn't substantially change anything else, though some things do get re-arranged a bit. Basically, the idea is this. First, we introduce a whole range of parameter types: Normal LaTeX optional and required parameters; ones for LyX's internal use (like embed); and finally, in connection with keyval, ones that represent keys and ones that represent optional and required arguments where the keyval stuff will appear. (I'm assuming here that there will always be exactly one of those, and that it will accept only keyval-type material.) The parameters themselves are stored in a map, so it's really only the output routines that need to care about the different types of parameters. Regarding the frontend, it seems to me that something like the following would work: (i) scan the parameter list for LATEX_KEY type parameters (ii) the dialog will have a series of lines, each of which has a combo box listing the acceptable keys and a QLineEdit for entering its value, as well as a "delete" button of some sort for removing this key and its value (iii) there should be an "add line" button to add a new line, activated only when all other lines are filled with values Probably not even too hard. git-svn-id: svn://svn.lyx.org/lyx/lyx-devel/trunk@23235 a592a061-630c-0410-9148-cb99ea01b6c8
2008-02-25 22:13:45 +00:00
param_info_.add("after", ParamInfo::LATEX_OPTIONAL);
param_info_.add("before", ParamInfo::LATEX_OPTIONAL);
param_info_.add("key", ParamInfo::LATEX_REQUIRED);
}
return param_info_;
}
bool InsetCitation::isCompatibleCommand(string const & cmd)
{
vector<string> const & possibles = possibleCiteCommands();
vector<string>::const_iterator const end = possibles.end();
return find(possibles.begin(), end, cmd) != end;
}
docstring InsetCitation::toolTip(BufferView const & bv, int, int) const
{
Buffer const & buf = bv.buffer();
// Only after the buffer is loaded from file...
if (!buf.isFullyLoaded())
return docstring();
BiblioInfo const & bi = buf.masterBibInfo();
if (bi.empty())
return _("No bibliography defined!");
docstring const & key = getParam("key");
if (key.empty())
return _("No citations selected!");
vector<docstring> keys = getVectorFromString(key);
vector<docstring>::const_iterator it = keys.begin();
vector<docstring>::const_iterator en = keys.end();
docstring tip;
for (; it != en; ++it) {
docstring const key_info = bi.getInfo(*it);
if (key_info.empty())
continue;
if (!tip.empty())
tip += "\n";
tip += wrap(key_info, -4);
}
return tip;
}
docstring InsetCitation::generateLabel() const
{
docstring const & before = getParam("before");
docstring const & after = getParam("after");
docstring label;
CiteEngine const engine = buffer().params().citeEngine();
label = complexLabel(buffer(), getCmdName(), getParam("key"),
before, after, engine);
// Fallback to fail-safe
if (label.empty())
label = basicLabel(getParam("key"), after);
return label;
}
docstring InsetCitation::screenLabel() const
{
return cache.screen_label;
}
void InsetCitation::updateLabels(ParIterator const &, bool)
{
CiteEngine const engine = buffer().params().citeEngine();
if (cache.params == params() && cache.engine == engine)
return;
// The label has changed, so we have to re-create it.
docstring const glabel = generateLabel();
unsigned int const maxLabelChars = 45;
docstring label = glabel;
if (label.size() > maxLabelChars) {
label.erase(maxLabelChars-3);
label += "...";
}
cache.engine = engine;
cache.params = params();
cache.generated_label = glabel;
cache.screen_label = label;
}
void InsetCitation::addToToc(DocIterator const & cpit)
{
Toc & toc = buffer().tocBackend().toc("citation");
toc.push_back(TocItem(cpit, 0, getParam("key")));
}
int InsetCitation::plaintext(odocstream & os, OutputParams const &) const
{
os << cache.generated_label;
return cache.generated_label.size();
}
static docstring const cleanupWhitespace(docstring const & citelist)
{
docstring::const_iterator it = citelist.begin();
docstring::const_iterator end = citelist.end();
// Paranoia check: make sure that there is no whitespace in here
// -- at least not behind commas or at the beginning
docstring result;
char_type last = ',';
for (; it != end; ++it) {
if (*it != ' ')
last = *it;
if (*it != ' ' || last != ',')
result += *it;
}
return result;
}
int InsetCitation::docbook(odocstream & os, OutputParams const &) const
{
os << from_ascii("<citation>")
<< cleanupWhitespace(getParam("key"))
<< from_ascii("</citation>");
return 0;
}
docstring InsetCitation::xhtml(XHTMLStream & xs, OutputParams const &) const
{
BiblioInfo const & bi = buffer().masterBibInfo();
docstring const & key_list = getParam("key");
if (key_list.empty())
return docstring();
// FIXME We should do a better job outputing different things for the
// different citation styles. For now, we use square brackets for every
// case.
xs << "[";
docstring const & before = getParam("before");
if (!before.empty())
xs << before << " ";
vector<docstring> const keys = getVectorFromString(key_list);
vector<docstring>::const_iterator it = keys.begin();
vector<docstring>::const_iterator const en = keys.end();
bool first = true;
for (; it != en; ++it) {
BiblioInfo::const_iterator const bt = bi.find(*it);
if (bt == bi.end())
continue;
BibTeXInfo const & bibinfo = bt->second;
if (!first) {
xs << ", ";
first = false;
}
docstring const & label = bibinfo.label();
docstring const & target = label.empty() ? *it : label;
string const attr = "href='#" + to_utf8(*it) + "'";
xs << StartTag("a", attr) << target << EndTag("a");
}
docstring const & after = getParam("after");
if (!after.empty())
xs << ", " << after;
xs << "]";
return docstring();
}
void InsetCitation::tocString(odocstream & os) const
{
plaintext(os, OutputParams(0));
}
// Have to overwrite the default InsetCommand method in order to check that
// the \cite command is valid. Eg, the user has natbib enabled, inputs some
// citations and then changes his mind, turning natbib support off. The output
// should revert to \cite[]{}
int InsetCitation::latex(odocstream & os, OutputParams const & runparams) const
{
CiteEngine cite_engine = buffer().params().citeEngine();
// FIXME UNICODE
docstring const cite_str = from_utf8(
asValidLatexCommand(getCmdName(), cite_engine));
if (runparams.inulemcmd)
os << "\\mbox{";
os << "\\" << cite_str;
docstring const & before = getParam("before");
docstring const & after = getParam("after");
if (!before.empty() && cite_engine != ENGINE_BASIC)
os << '[' << before << "][" << after << ']';
else if (!after.empty())
os << '[' << after << ']';
os << '{' << cleanupWhitespace(getParam("key")) << '}';
if (runparams.inulemcmd)
os << "}";
return 0;
}
void InsetCitation::validate(LaTeXFeatures & features) const
{
switch (features.bufferParams().citeEngine()) {
case ENGINE_BASIC:
break;
case ENGINE_NATBIB_AUTHORYEAR:
case ENGINE_NATBIB_NUMERICAL:
features.require("natbib");
break;
case ENGINE_JURABIB:
features.require("jurabib");
break;
}
}
docstring InsetCitation::contextMenu(BufferView const &, int, int) const
{
return from_ascii("context-citation");
}
} // namespace lyx