X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2Finsets%2Finsetcite.C;h=8bd95bfdbea5f73c6cad53c8bd0767386521126c;hb=6ce86e2bfe0a403e0e811b66fdddb2d56cfe0f83;hp=13ec3a2d0e5ce08a122a35185ecb021b1ef15efb;hpb=89885b5fa1d24ee5275b4744ed3344bccf51df04;p=lyx.git diff --git a/src/insets/insetcite.C b/src/insets/insetcite.C index 13ec3a2d0e..8bd95bfdbe 100644 --- a/src/insets/insetcite.C +++ b/src/insets/insetcite.C @@ -1,35 +1,405 @@ -// -*- 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 Voss + * + * Full author contact details are available in file CREDITS + */ #include -#ifdef __GNUG__ -#pragma implementation -#endif - #include "insetcite.h" +#include "buffer.h" +#include "BufferView.h" +#include "LaTeXFeatures.h" + +#include "frontends/controllers/biblio.h" + +#include "support/lstrings.h" + +#include + +using std::ostream; +using std::vector; +using std::map; -InsetCitation::InsetCitation(string const & key, string const & note) - : InsetCommand("cite", key, note) +namespace { + +// An optimisation. We assume that until the first InsetCitation::edit is +// called, we're loading the buffer and that, therefore, we don't need to +// reload the bibkey list +std::map loading_buffer; + +string const getNatbibLabel(Buffer const * buffer, + string const & citeType, string const & keyList, + string const & before, string const & after, + bool numerical) { + typedef std::map CachedMap; + static CachedMap cached_keys; + + // Only load the bibkeys once if we're loading up the buffer, + // else load them afresh each time. + map::iterator lit = loading_buffer.find(buffer); + if (lit == loading_buffer.end()) + loading_buffer[buffer] = true; + + bool loadkeys = !loading_buffer[buffer]; + if (!loadkeys) { + CachedMap::iterator kit = cached_keys.find(buffer); + loadkeys = kit == cached_keys.end(); + } + + if (loadkeys) { + // build the keylist + typedef vector > InfoType; + InfoType bibkeys; + buffer->fillWithBibKeys(bibkeys); + + InfoType::const_iterator bit = bibkeys.begin(); + InfoType::const_iterator bend = bibkeys.end(); + + biblio::InfoMap infomap; + for (; bit != bend; ++bit) { + infomap[bit->first] = bit->second; + } + if (infomap.empty()) + return string(); + + cached_keys[buffer] = infomap; + } + + biblio::InfoMap infomap = cached_keys[buffer]; + + // the natbib citation-styles + // CITET: author (year) + // CITEP: (author,year) + // CITEALT: author year + // CITEALP: author, year + // CITEAUTHOR: author + // CITEYEAR: year + // CITEYEARPAR: (year) + + // We don't currently use the full or forceUCase fields. + // bool const forceUCase = citeType[0] == 'C'; + bool const full = citeType[citeType.size() - 1] == '*'; + + string const cite_type = full ? + ascii_lowercase(citeType.substr(0, citeType.size() - 1)) : + ascii_lowercase(citeType); + + string 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 + ' '; + } + + string 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 const op_str(' ' + string(1, op)); + string const cp_str(string(1, cp) + ' '); + string const sep_str(string(sep) + ' '); + + string 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 + string const author(biblio::getAbbreviatedAuthor(infomap, *it)); + string const year(biblio::getYear(infomap, *it)); + + // Something isn't right. Fail safely. + if (author.empty() || year.empty()) + return string(); + + // (authors1 ( year); ... ; + // authors_last ( year, ) + if (cite_type == "citet") { + string const tmp = numerical ? '#' + *it : year; + label += author + op_str + before_str + tmp + + cp + sep_str; + + // author, year; author, year; ... + } else if (cite_type == "citep" || + cite_type == "citealp") { + if (numerical) { + label += *it + sep_str; + } else { + label += author + ", " + year + sep_str; + } + + // (authors1 year; + // authors_last year, ) + } else if (cite_type == "citealt") { + string const tmp = numerical ? '#' + *it : year; + label += author + ' ' + before_str + tmp + sep_str; + + // 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 = !(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 = string(1, op) + label + string(1, cp); + + return label; } -string InsetCitation::getScreenLabel() const + +string const getBasicLabel(string const & keyList, string const & after) { - string temp("["); + string keys(keyList); + string label; - temp += getContents(); + if (contains(keys, ",")) { + // Final comma allows while loop to cover all keys + keys = ltrim(split(keys, label, ',')) + ','; + while (contains(keys, ",")) { + string key; + keys = ltrim(split(keys, key, ',')); + label += ", " + key; + } + } else + label = keys; + + if (!after.empty()) + label += ", " + after; + + return '[' + label + ']'; +} + +} // anon namespace + + +InsetCitation::InsetCitation(InsetCommandParams const & p, bool) + : InsetCommand(p) +{} + + +InsetCitation::~InsetCitation() +{ + InsetCommandMailer mailer("citation", *this); + mailer.hideDialog(); +} + + +string const InsetCitation::generateLabel(Buffer const * buffer) const +{ + string const before = string(); + string const after = getOptions(); - if( !getOptions().empty() ) { - temp += ", " + getOptions(); + string label; + if (buffer->params.use_natbib) { + string cmd = getCmdName(); + if (cmd == "cite") { + // We may be "upgrading" from an older LyX version. + // If, however, we use "cite" because the necessary + // author/year info is not present in the biblio + // database, then getNatbibLabel will exit gracefully + // and we'll call getBasicLabel. + if (buffer->params.use_numerical_citations) + cmd = "citep"; + else + cmd = "citet"; + } + label = getNatbibLabel(buffer, cmd, getContents(), + before, after, + buffer->params.use_numerical_citations); } - return temp + ']'; + // Fallback to fail-safe + if (label.empty()) { + label = getBasicLabel(getContents(), after); + } + + return label; +} + + +InsetCitation::Cache::Style InsetCitation::getStyle(Buffer const * buffer) const +{ + Cache::Style style = Cache::BASIC; + + if (buffer->params.use_natbib) { + if (buffer->params.use_numerical_citations) { + style = Cache::NATBIB_NUM; + } else { + style = Cache::NATBIB_AY; + } + } + + return style; +} + + +string const InsetCitation::getScreenLabel(Buffer const * buffer) const +{ + Cache::Style const style = getStyle(buffer); + if (cache.params == params() && cache.style == style) + return cache.screen_label; + + // The label has changed, so we have to re-create it. + string const before = string(); + string const after = getOptions(); + + string const glabel = generateLabel(buffer); + + unsigned int const maxLabelChars = 45; + + string label = glabel; + if (label.size() > maxLabelChars) { + label.erase(maxLabelChars-3); + label += "..."; + } + + cache.style = style; + cache.params = params(); + cache.generated_label = glabel; + cache.screen_label = label; + + return label; +} + + +void InsetCitation::setLoadingBuffer(Buffer const * buffer, bool state) const +{ + // Doesn't matter if there is no bv->buffer() entry in the map. + loading_buffer[buffer] = state; +} + + +void InsetCitation::edit(BufferView * bv, int, int, mouse_button::state) +{ + // A call to edit() indicates that we're no longer loading the + // buffer but doing some real work. + setLoadingBuffer(bv->buffer(), false); + + InsetCommandMailer mailer("citation", *this); + mailer.showDialog(bv); +} + + +void InsetCitation::edit(BufferView * bv, bool) +{ + edit(bv, 0, 0, mouse_button::none); +} + + +int InsetCitation::ascii(Buffer const * buffer, ostream & os, int) const +{ + string label; + + if (cache.params == params() && cache.style == getStyle(buffer)) + label = cache.generated_label; + else + label = generateLabel(buffer); + + os << label; + return 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(Buffer const * buffer, ostream & os, + bool /*fragile*/, bool/*fs*/) const +{ + os << "\\"; + if (buffer->params.use_natbib) + os << getCmdName(); + else + os << "cite"; + +#warning What is this code supposed to do? (Lgb) +// my guess is that this is just waiting for when we support before, +// so it's a oneliner. But this is very silly ! - jbl + +#if 1 + // The current strange code + + string const before = string(); + string const after = getOptions(); + if (!before.empty() && buffer->params.use_natbib) + os << '[' << before << "][" << after << ']'; + else if (!after.empty()) + os << '[' << after << ']'; +#else + // and the cleaned up equvalent, should it just be changed? (Lgb) + string const after = getOptions(); + if (!after.empty()) + os << '[' << after << ']'; +#endif + string::const_iterator it = getContents().begin(); + string::const_iterator end = getContents().end(); + // Paranoia check: make sure that there is no whitespace in here + string content; + char last = ','; + for (; it != end; ++it) { + if (*it != ' ') + last = *it; + if (*it != ' ' || last != ',') + content += *it; + } + + os << '{' << content << '}'; + + return 0; +} + + +void InsetCitation::validate(LaTeXFeatures & features) const +{ + if (features.bufferParams().use_natbib) + features.require("natbib"); }