X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2Finsets%2FInsetCitation.cpp;h=8f697993b143c94d43d011c2a6fbd50bdf5bfa80;hb=9c55af4a223ce4db29d643251109e245665344bd;hp=6541e3ceb679d6a59b69695546705a1a5a128d76;hpb=5bb22d9498da67b2cc279b9c07271fb4c033ca39;p=lyx.git diff --git a/src/insets/InsetCitation.cpp b/src/insets/InsetCitation.cpp index 6541e3ceb6..8f697993b1 100644 --- a/src/insets/InsetCitation.cpp +++ b/src/insets/InsetCitation.cpp @@ -4,7 +4,7 @@ * Licence details can be found in the file COPYING. * * \author Angus Leeming - * \author Herbert Voß + * \author Herbert Voß * * Full author contact details are available in file CREDITS. */ @@ -13,362 +13,351 @@ #include "InsetCitation.h" +#include "BiblioInfo.h" #include "Buffer.h" +#include "buffer_funcs.h" #include "BufferParams.h" -#include "debug.h" +#include "BufferView.h" #include "DispatchResult.h" +#include "FuncCode.h" #include "FuncRequest.h" #include "LaTeXFeatures.h" - -#include "frontends/controllers/frontend_helpers.h" - -#include "support/fs_extras.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 +#include -#include -#include - +using namespace std; +using namespace lyx::support; namespace lyx { -using support::ascii_lowercase; -using support::contains; -using support::FileName; -using support::getStringFromVector; -using support::getVectorFromString; -using support::ltrim; -using support::rtrim; -using support::split; -using support::tokenPos; +ParamInfo InsetCitation::param_info_; + + +InsetCitation::InsetCitation(Buffer * buf, InsetCommandParams const & p) + : InsetCommand(buf, p) +{ + buffer().removeBiblioTempFiles(); +} + -using std::endl; -using std::replace; -using std::string; -using std::ostream; -using std::vector; -using std::map; +InsetCitation::~InsetCitation() +{ + if (isBufferLoaded()) + buffer().removeBiblioTempFiles(); +} -namespace fs = boost::filesystem; + +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()) { + param_info_.add("after", ParamInfo::LATEX_OPTIONAL); + param_info_.add("before", ParamInfo::LATEX_OPTIONAL); + param_info_.add("key", ParamInfo::LATEX_REQUIRED); + } + return param_info_; +} namespace { -docstring const getNatbibLabel(Buffer const & buffer, - string const & citeType, string const & keyList, - docstring const & before, docstring const & after, - biblio::CiteEngine engine) +vector const init_possible_cite_commands() { - // Only start the process off after the buffer is loaded from file. - if (!buffer.fully_loaded()) - return docstring(); + 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(possible, possible + size_possible); +} - // Cache the labels - typedef std::map CachedMap; - static CachedMap cached_keys; - - // and cache the timestamp of the bibliography files. - static std::map bibfileStatus; - - biblio::BibKeyList keylist; - - vector const & bibfilesCache = buffer.getBibfilesCache(); - // compare the cached timestamps with the actual ones. - bool changed = false; - for (vector::const_iterator it = bibfilesCache.begin(); - it != bibfilesCache.end(); ++ it) { - FileName const f = *it; - try { - std::time_t lastw = fs::last_write_time(f.toFilesystemEncoding()); - if (lastw != bibfileStatus[f]) { - changed = true; - bibfileStatus[f] = lastw; - } - } - catch (fs::filesystem_error & fserr) { - changed = true; - lyxerr << "Couldn't find or read bibtex file " - << f << endl; - LYXERR(Debug::DEBUG) << "Fs error: " - << fserr.what() << endl; - } - } - // build the keylist only if the bibfiles have been changed - if (cached_keys[&buffer].empty() || bibfileStatus.empty() || changed) { - buffer.fillWithBibKeys(keylist); - cached_keys[&buffer] = keylist; - } else - // use the cached keys - keylist = cached_keys[&buffer]; +vector const & possibleCiteCommands() +{ + static vector const possible = init_possible_cite_commands(); + return possible; +} - if (keylist.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/ +} // anon namespace - // We don't currently use the full or forceUCase fields. - string cite_type = biblio::asValidLatexCommand(citeType, engine); - if (cite_type[0] == 'C') - cite_type = string(1, 'c') + cite_type.substr(1); - if (cite_type[cite_type.size() - 1] == '*') - 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; - } +// FIXME: use the citeCommands provided by the TextClass +// instead of possibleCiteCommands defined in this file. +bool InsetCitation::isCompatibleCommand(string const & cmd) +{ + vector const & possibles = possibleCiteCommands(); + vector::const_iterator const end = possibles.end(); + return find(possibles.begin(), end, cmd) != end; +} + - docstring after_str; - if (!after.empty()) { - // The "after" key is appended only to the end of the whole. - after_str = ", " + after; +void InsetCitation::doDispatch(Cursor & cur, FuncRequest & cmd) +{ + if (cmd.action() == LFUN_INSET_MODIFY) { + buffer().removeBiblioTempFiles(); + cache.recalculate = true; } + InsetCommand::doDispatch(cur, cmd); +} - // One day, these might be tunable (as they are in BibTeX). - char const op = '('; // opening parenthesis. - char const cp = ')'; // closing parenthesis. - // puctuation mark separating citation entries. - char const * const sep = ";"; - docstring const op_str(' ' + docstring(1, op)); - docstring const cp_str(docstring(1, cp) + ' '); - docstring const sep_str(from_ascii(sep) + ' '); +bool InsetCitation::addKey(string const & key) +{ + docstring const ukey = from_utf8(key); + docstring const & curkeys = getParam("key"); + if (curkeys.empty()) { + setParam("key", ukey); + cache.recalculate = true; + return true; + } - docstring label; - vector keys = getVectorFromString(keyList); - vector::const_iterator it = keys.begin(); - vector::const_iterator end = keys.end(); - for (; it != end; ++it) { - // get the bibdata corresponding to the key - docstring const author(biblio::getAbbreviatedAuthor(keylist, *it)); - docstring const year(biblio::getYear(keylist, *it)); - - // Something isn't right. Fail safely. - if (author.empty() || year.empty()) - return docstring(); - - // authors1/; ... ; - // authors_last, - if (cite_type == "cite" && engine == biblio::ENGINE_JURABIB) { - if (it == keys.begin()) - label += author + before_str + sep_str; - else - label += author + sep_str; - - // (authors1 ( year); ... ; - // authors_last ( year, ) - } else if (cite_type == "citet") { - switch (engine) { - case biblio::ENGINE_NATBIB_AUTHORYEAR: - label += author + op_str + before_str + - year + cp + sep_str; - break; - case biblio::ENGINE_NATBIB_NUMERICAL: - // FIXME UNICODE - label += author + op_str + before_str + - '#' + from_utf8(*it) + cp + sep_str; - break; - case biblio::ENGINE_JURABIB: - label += before_str + author + op_str + - year + cp + sep_str; - break; - case biblio::ENGINE_BASIC: - break; - } - - // author, year; author, year; ... - } else if (cite_type == "citep" || - cite_type == "citealp") { - if (engine == biblio::ENGINE_NATBIB_NUMERICAL) { - // FIXME UNICODE - label += from_utf8(*it) + sep_str; - } else { - label += author + ", " + year + sep_str; - } - - // (authors1 year; - // authors_last year, ) - } else if (cite_type == "citealt") { - switch (engine) { - case biblio::ENGINE_NATBIB_AUTHORYEAR: - label += author + ' ' + before_str + - year + sep_str; - break; - case biblio::ENGINE_NATBIB_NUMERICAL: - // FIXME UNICODE - label += author + ' ' + before_str + - '#' + from_utf8(*it) + sep_str; - break; - case biblio::ENGINE_JURABIB: - label += before_str + author + ' ' + - year + sep_str; - break; - case biblio::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; + vector keys = getVectorFromString(curkeys); + vector::const_iterator it = keys.begin(); + vector::const_iterator en = keys.end(); + for (; it != en; ++it) { + if (*it == ukey) { + LYXERR0("Key " << key << " already present."); + return false; } } - 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 == biblio::ENGINE_NATBIB_NUMERICAL && - (cite_type == "citeauthor" || - cite_type == "citeyear")); - if (add) - label += after_str; - } + keys.push_back(ukey); + setParam("key", getStringFromVector(keys)); + cache.recalculate = true; + return true; +} + + +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 keys = getVectorFromString(key); + vector::const_iterator it = keys.begin(); + vector::const_iterator en = keys.end(); + docstring tip; + for (; it != en; ++it) { + docstring const key_info = bi.getInfo(*it, buffer()); + if (key_info.empty()) + continue; + if (!tip.empty()) + tip += "\n"; + tip += wrap(key_info, -4); } + return tip; +} - if (!before_str.empty() && (cite_type == "citep" || - cite_type == "citealp" || - cite_type == "citeyearpar")) { - label = before_str + label; + +namespace { + + +CitationStyle asValidLatexCommand(string const & input, vector const valid_styles) +{ + CitationStyle cs = valid_styles[0]; + cs.forceUpperCase = false; + cs.fullAuthorList = false; + if (!InsetCitation::isCompatibleCommand(input)) + return cs; + + string normalized_input = input; + string::size_type const n = input.size() - 1; + if (input[0] == 'C') + normalized_input[0] = 'c'; + if (input[n] == '*') + normalized_input = normalized_input.substr(0, n); + + vector::const_iterator it = valid_styles.begin(); + vector::const_iterator end = valid_styles.end(); + for (; it != end; ++it) { + CitationStyle this_cs = *it; + if (this_cs.cmd == normalized_input) { + cs = *it; + break; + } } - if (cite_type == "citep" || cite_type == "citeyearpar") - label = op + label + cp; + cs.forceUpperCase &= input[0] == 'C'; + cs.fullAuthorList &= input[n] == '*'; - return label; + return cs; } -docstring const getBasicLabel(docstring const & keyList, docstring const & after) +inline docstring wrapCitation(docstring const & key, + docstring const & content, bool for_xhtml) { - docstring keys(keyList); - docstring label; + if (!for_xhtml) + return content; + // we have to do the escaping here, because we will ultimately + // write this as a raw string, so as not to escape the tags. + return "" + + html::htmlize(content, XHTMLStream::ESCAPE_ALL) + ""; +} - 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; +} // anonymous namespace - if (!after.empty()) - label += ", " + after; +docstring InsetCitation::generateLabel(bool for_xhtml) const +{ + docstring label; + label = complexLabel(for_xhtml); - return '[' + label + ']'; + // Fallback to fail-safe + if (label.empty()) + label = basicLabel(for_xhtml); + + return label; } -} // anon namespace +docstring InsetCitation::complexLabel(bool for_xhtml) const +{ + Buffer const & buf = buffer(); + // Only start the process off after the buffer is loaded from file. + if (!buf.isFullyLoaded()) + return docstring(); -InsetCitation::InsetCitation(InsetCommandParams const & p) - : InsetCommand(p, "citation") -{} + BiblioInfo const & biblist = buf.masterBibInfo(); + if (biblist.empty()) + return docstring(); + docstring const & key = getParam("key"); + if (key.empty()) + return _("No citations selected!"); -docstring const InsetCitation::generateLabel(Buffer const & buffer) const -{ - docstring const before = getParam("before"); - docstring const after = getParam("after"); + // We don't currently use the full or forceUCase fields. + string cite_type = getCmdName(); + 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 const & before = getParam("before"); + docstring const & after = getParam("after"); + + // FIXME: allow to add cite macros + /* + buffer().params().documentClass().addCiteMacro("!textbefore", to_utf8(before)); + buffer().params().documentClass().addCiteMacro("!textafter", to_utf8(after)); + */ docstring label; - biblio::CiteEngine const engine = buffer.params().getEngine(); - if (engine != biblio::ENGINE_BASIC) { - // FIXME UNICODE - label = getNatbibLabel(buffer, getCmdName(), to_utf8(getParam("key")), - before, after, engine); - } + vector keys = getVectorFromString(key); + label = biblist.getLabel(keys, buffer(), cite_type, for_xhtml, UINT_MAX, before, after); + return label; +} - // Fallback to fail-safe - if (label.empty()) { - label = getBasicLabel(getParam("key"), after); - } - return label; +docstring InsetCitation::basicLabel(bool for_xhtml) const +{ + docstring keys = getParam("key"); + docstring label; + + docstring key; + do { + // if there is no comma, then everything goes into key + // and keys will be empty. + keys = trim(split(keys, key, ',')); + key = trim(key); + if (!label.empty()) + label += ", "; + label += wrapCitation(key, key, for_xhtml); + } while (!keys.empty()); + + docstring const & after = getParam("after"); + if (!after.empty()) + label += ", " + after; + + return '[' + label + ']'; +} + +docstring InsetCitation::screenLabel() const +{ + return cache.screen_label; } -docstring const InsetCitation::getScreenLabel(Buffer const & buffer) const +void InsetCitation::updateBuffer(ParIterator const &, UpdateType) { - biblio::CiteEngine const engine = buffer.params().getEngine(); - if (cache.params == params() && cache.engine == engine) - return cache.screen_label; + if (!cache.recalculate && buffer().citeLabelsValid()) + return; - // The label has changed, so we have to re-create it. - docstring const glabel = generateLabel(buffer); + // The label may have 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.erase(maxLabelChars - 3); label += "..."; } - cache.engine = engine; - cache.params = params(); + cache.recalculate = false; cache.generated_label = glabel; cache.screen_label = label; - - return label; } -int InsetCitation::plaintext(Buffer const & buffer, odocstream & os, - OutputParams const &) const +void InsetCitation::addToToc(DocIterator const & cpit, bool output_active) const { - docstring str; + // NOTE + // BiblioInfo::collectCitedEntries() uses the TOC to collect the citations + // from the document. It is used indirectly, via BiblioInfo::makeCitationLables, + // by both XHTML and plaintext output. So, if we change what goes into the TOC, + // then we will also need to change that routine. + docstring const tocitem = getParam("key"); + Toc & toc = buffer().tocBackend().toc("citation"); + toc.push_back(TocItem(cpit, 0, tocitem, output_active)); +} - if (cache.params == params() && - cache.engine == buffer.params().getEngine()) - str = cache.generated_label; - else - str = generateLabel(buffer); - os << str; - return str.size(); -} +int InsetCitation::plaintext(odocstringstream & os, + OutputParams const &, size_t) const +{ + string const & cmd = getCmdName(); + if (cmd == "nocite") + return 0; + docstring const label = generateLabel(false); + os << label; + return label.size(); +} -namespace { -docstring const cleanupWhitespace(docstring const & citelist) +static docstring const cleanupWhitespace(docstring const & citelist) { docstring::const_iterator it = citelist.begin(); docstring::const_iterator end = citelist.end(); @@ -385,76 +374,82 @@ docstring const cleanupWhitespace(docstring const & citelist) return result; } -// end anon namyspace -} -int InsetCitation::docbook(Buffer const &, odocstream & os, - OutputParams const &) const +int InsetCitation::docbook(odocstream & os, OutputParams const &) const { - os << "" + os << from_ascii("") << cleanupWhitespace(getParam("key")) - << ""; + << from_ascii(""); return 0; } -int InsetCitation::textString(Buffer const & buf, odocstream & os, - OutputParams const & op) const +docstring InsetCitation::xhtml(XHTMLStream & xs, OutputParams const &) const { - return plaintext(buf, os, op); + string const & cmd = getCmdName(); + if (cmd == "nocite") + return docstring(); + + // have to output this raw, because generateLabel() will include tags + xs << XHTMLStream::ESCAPE_NONE << generateLabel(true); + + return docstring(); +} + + +void InsetCitation::toString(odocstream & os) const +{ + odocstringstream ods; + plaintext(ods, OutputParams(0)); + os << ods.str(); +} + + +void InsetCitation::forOutliner(docstring & os, size_t) const +{ + os += screenLabel(); } // 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(Buffer const & buffer, odocstream & os, - OutputParams const &) const +// should revert to the default citation command as provided by the citation +// engine, e.g. \cite[]{} for the basic engine. +void InsetCitation::latex(otexstream & os, OutputParams const & runparams) const { - biblio::CiteEngine cite_engine = buffer.params().getEngine(); + vector citation_styles = buffer().params().citeStyles(); + CitationStyle cs = asValidLatexCommand(getCmdName(), citation_styles); + BiblioInfo const & bi = buffer().masterBibInfo(); // FIXME UNICODE - docstring const cite_str = from_utf8( - biblio::asValidLatexCommand(getCmdName(), cite_engine)); + docstring const cite_str = from_utf8(citationStyleToString(cs)); + + if (runparams.inulemcmd > 0) + os << "\\mbox{"; os << "\\" << cite_str; docstring const & before = getParam("before"); docstring const & after = getParam("after"); - if (!before.empty() && cite_engine != biblio::ENGINE_BASIC) + if (!before.empty() && cs.textBefore) os << '[' << before << "][" << after << ']'; - else if (!after.empty()) + else if (!after.empty() && cs.textAfter) os << '[' << after << ']'; - os << '{' << cleanupWhitespace(getParam("key")) << '}'; - - return 0; -} - + if (!bi.isBibtex(getParam("key"))) + // escape chars with bibitems + os << '{' << escape(cleanupWhitespace(getParam("key"))) << '}'; + else + os << '{' << cleanupWhitespace(getParam("key")) << '}'; -void InsetCitation::validate(LaTeXFeatures & features) const -{ - switch (features.bufferParams().getEngine()) { - case biblio::ENGINE_BASIC: - break; - case biblio::ENGINE_NATBIB_AUTHORYEAR: - case biblio::ENGINE_NATBIB_NUMERICAL: - features.require("natbib"); - break; - case biblio::ENGINE_JURABIB: - features.require("jurabib"); - break; - } + if (runparams.inulemcmd) + os << "}"; } -void InsetCitation::replaceContents(string const & from, string const & to) +string InsetCitation::contextMenuName() const { - if (tokenPos(getContents(), ',', from) != -1) { - vector items = getVectorFromString(getContents()); - replace(items.begin(), items.end(), from, to); - setContents(getStringFromVector(items)); - } + return "context-citation"; }