X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2Finsets%2Finsetcite.C;h=a8bf9a068e5a87daedc172ce1e9636476a567b06;hb=e28331ed63062dea10d0a21b9ec12034b4b17b9a;hp=eea8607b70bcdea615fddb0ed59651beb9fb8079;hpb=62e038c59470b3dab4c3fe5206a46ff65463b844;p=lyx.git diff --git a/src/insets/insetcite.C b/src/insets/insetcite.C index eea8607b70..a8bf9a068e 100644 --- a/src/insets/insetcite.C +++ b/src/insets/insetcite.C @@ -1,64 +1,468 @@ -// -*- C++ -*- -/* This file is part of* - * ====================================================== +/** + * \file insetcite.C + * This file is part of LyX, the document processor. + * Licence details can be found in the file COPYING. * - * LyX, The Document Processor - * - * Copyright 2000 The LyX Team. - * - * ====================================================== */ + * \author Angus Leeming + * \author Herbert Voß + * + * Full author contact details are available in file CREDITS. + */ #include -#ifdef __GNUG__ -#pragma implementation -#endif - #include "insetcite.h" -#include "BufferView.h" -#include "LyXView.h" -#include "frontends/Dialogs.h" + +#include "buffer.h" +#include "bufferparams.h" +#include "debug.h" +#include "dispatchresult.h" +#include "funcrequest.h" +#include "LaTeXFeatures.h" + +#include "frontends/controllers/biblio.h" + +#include "support/fs_extras.h" #include "support/lstrings.h" -InsetCitation::InsetCitation(InsetCommandParams const & p) - : InsetCommand(p) -{} +#include + +#include +#include -string const InsetCitation::getScreenLabel() const + +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; + +using std::endl; +using std::replace; +using std::string; +using std::ostream; +using std::vector; +using std::map; + +namespace fs = boost::filesystem; + + +namespace { + +docstring const getNatbibLabel(Buffer const & buffer, + string const & citeType, string const & keyList, + docstring const & before, docstring const & after, + biblio::CiteEngine engine) { - string keys(getContents()); + // Only start the process off after the buffer is loaded from file. + if (!buffer.fully_loaded()) + return docstring(); - // If keys is "too long" then only print out the first few tokens - string label; - if (contains(keys, ",")) { - // Final comma allows while loop to cover all keys - keys = frontStrip(split(keys, label, ',')) + ","; + // Cache the labels + typedef std::map CachedMap; + static CachedMap cached_keys; + + // and cache the timestamp of the bibliography files. + static std::map bibfileStatus; + + biblio::InfoMap infomap; + + 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) { + typedef vector > InfoType; + InfoType bibkeys; + buffer.fillWithBibKeys(bibkeys); + + InfoType::const_iterator bit = bibkeys.begin(); + InfoType::const_iterator bend = bibkeys.end(); + + for (; bit != bend; ++bit) + infomap[bit->first] = bit->second; + + cached_keys[&buffer] = infomap; + } else + // use the cached keys + infomap = cached_keys[&buffer]; + + if (infomap.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/ + + // 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; + } + + docstring after_str; + if (!after.empty()) { + // The "after" key is appended only to the end of the whole. + after_str = ", " + after; + } + + // 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 = ";"; - string::size_type const maxSize = 40; - while (contains( keys, "," )) { - string key; - keys = frontStrip(split(keys, key, ',')); + docstring const op_str(' ' + docstring(1, op)); + docstring const cp_str(docstring(1, cp) + ' '); + docstring const sep_str(from_ascii(sep) + ' '); - string::size_type size = label.size() + 2 + key.size(); - if( size >= maxSize ) { - label += ", ..."; + 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(infomap, *it)); + docstring const year(biblio::getYear(infomap, *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; + } + } + 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; + } + } + + if (!before_str.empty() && (cite_type == "citep" || + cite_type == "citealp" || + cite_type == "citeyearpar")) { + label = before_str + label; + } + + if (cite_type == "citep" || cite_type == "citeyearpar") + label = op + label + cp; + + return label; +} + + +docstring const getBasicLabel(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 { + } else label = keys; + + if (!after.empty()) + label += ", " + after; + + return '[' + label + ']'; +} + +} // anon namespace + + +InsetCitation::InsetCitation(InsetCommandParams const & p) + : InsetCommand(p, "citation") +{} + + +docstring const InsetCitation::generateLabel(Buffer const & buffer) const +{ + docstring const before = getParam("before"); + docstring const after = getParam("after"); + + docstring label; + biblio::CiteEngine const engine = buffer.params().cite_engine; + if (engine != biblio::ENGINE_BASIC) { + // FIXME UNICODE + label = getNatbibLabel(buffer, getCmdName(), to_utf8(getParam("key")), + before, after, engine); + } + + // Fallback to fail-safe + if (label.empty()) { + label = getBasicLabel(getParam("key"), after); + } + + return label; +} + + +docstring const InsetCitation::getScreenLabel(Buffer const & buffer) const +{ + biblio::CiteEngine const engine = biblio::getEngine(buffer); + if (cache.params == params() && cache.engine == engine) + return cache.screen_label; + + // The label has changed, so we have to re-create it. + string const before = getSecOptions(); + string const after = getOptions(); + + docstring const glabel = generateLabel(buffer); + + 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; + + return label; +} + + +int InsetCitation::plaintext(Buffer const & buffer, odocstream & os, + OutputParams const &) const +{ + if (cache.params == params() && + cache.engine == biblio::getEngine(buffer)) + os << cache.generated_label; + else + os << generateLabel(buffer); + return 0; +} + + +namespace { + +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; +} - if (!getOptions().empty()) - label += ", " + getOptions(); +// end anon namyspace +} - return "[" + label + "]"; +int InsetCitation::docbook(Buffer const &, odocstream & os, OutputParams const &) const +{ + os << "" + << cleanupWhitespace(getParam("key")) + << ""; + return 0; } -void InsetCitation::Edit(BufferView * bv, int, int, unsigned int) +int InsetCitation::textString(Buffer const & buf, odocstream & os, + OutputParams const & op) const { - bv->owner()->getDialogs()->showCitation(this); + return plaintext(buf, os, op); } + +// 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 +{ + biblio::CiteEngine const cite_engine = buffer.params().cite_engine; + // FIXME UNICODE + docstring const cite_str = from_utf8( + biblio::asValidLatexCommand(getCmdName(), cite_engine)); + + os << "\\" << cite_str; + + docstring const & before = getParam("before"); + docstring const & after = getParam("after"); + if (!before.empty() && cite_engine != biblio::ENGINE_BASIC) + os << '[' << before << "][" << after << ']'; + else if (!after.empty()) + os << '[' << after << ']'; + + os << '{' << cleanupWhitespace(getParam("key")) << '}'; + + return 0; +} + + +void InsetCitation::validate(LaTeXFeatures & features) const +{ + switch (features.bufferParams().cite_engine) { + 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; + } +} + + +void InsetCitation::replaceContents(string const & from, string const & to) +{ + if (tokenPos(getContents(), ',', from) != -1) { + vector items = getVectorFromString(getContents()); + replace(items.begin(), items.end(), from, to); + setContents(getStringFromVector(items)); + } +} + + +} // namespace lyx