From 206ca457517a7d6a2c92c23cf59f9e59241fb698 Mon Sep 17 00:00:00 2001 From: Richard Heck Date: Sat, 27 Mar 2010 12:58:26 +0000 Subject: [PATCH] This is the first step towards allowing customization of how BibTeX entries are represented, both in LyX and in XHTML output. Try looking at the InsetCitation dialog and, in particular, at entries that have crossref fields and you will see the benefit there. The next step is to allow this stuff to be read from a file. git-svn-id: svn://svn.lyx.org/lyx/lyx-devel/trunk@33879 a592a061-630c-0410-9148-cb99ea01b6c8 --- src/BiblioInfo.cpp | 266 ++++++++++++++++++++++++++++++++++++--------- src/BiblioInfo.h | 8 ++ 2 files changed, 223 insertions(+), 51 deletions(-) diff --git a/src/BiblioInfo.cpp b/src/BiblioInfo.cpp index 9cb3b4458e..99e3bd91ac 100644 --- a/src/BiblioInfo.cpp +++ b/src/BiblioInfo.cpp @@ -27,6 +27,7 @@ #include "insets/InsetInclude.h" #include "support/convert.h" +#include "support/debug.h" #include "support/docstream.h" #include "support/gettext.h" #include "support/lassert.h" @@ -232,6 +233,9 @@ docstring const BibTeXInfo::getAbbreviatedAuthor() const return bib_key_; } + // FIXME Move this to a separate routine that can + // be called from elsewhere. + // // OK, we've got some names. Let's format them. // Try to split the author list on " and " vector const authors = @@ -276,6 +280,204 @@ docstring const BibTeXInfo::getXRef() const } +namespace { + docstring parseOptions(docstring const & format, docstring & optkey, + docstring & ifpart, docstring & elsepart); + + /// Calls parseOptions to deal with an embedded option, such as: + /// {%number%[[, no.~%number%]]} + /// which must appear at the start of format. ifelsepart gets the + /// whole of the option, and we return what's left after the option. + /// we return format if there is an error. + docstring parseEmbeddedOption(docstring const & format, + docstring & ifelsepart) + { + LASSERT(format[0] == '{' && format[1] == '%', return format); + docstring optkey; + docstring ifpart; + docstring elsepart; + docstring const rest = parseOptions(format, optkey, ifpart, elsepart); + if (format == rest) { // parse error + LYXERR0("ERROR! Couldn't parse `" << format <<"'."); + return format; + } + LASSERT(rest.size() <= format.size(), /* */); + ifelsepart = format.substr(0, format.size() - rest.size()); + return rest; + } + + + // Gets a "clause" from a format string, where the clause is + // delimited by '[[' and ']]'. Returns what is left after the + // clause is removed, and returns format if there is an error. + docstring getClause(docstring const & format, docstring & clause) + { + docstring fmt = format; + // remove '[[' + fmt = fmt.substr(2); + // we'll remove characters from the front of fmt as we + // deal with them + while (fmt.size()) { + if (fmt[0] == ']' && fmt.size() > 1 && fmt[1] == ']') { + // that's the end + fmt = fmt.substr(2); + break; + } + // check for an embedded option + if (fmt[0] == '{' && fmt.size() > 1 && fmt[1] == '%') { + docstring part; + docstring const rest = parseEmbeddedOption(fmt, part); + if (fmt == rest) { + LYXERR0("ERROR! Couldn't parse `" << format <<"'."); + return format; + } + clause += part; + fmt = rest; + } else { // it's just a normal character + clause += fmt[0]; + fmt = fmt.substr(1); + } + } + return fmt; + } + + + /// parse an options string, which must appear at the start of the + /// format parameter. puts the parsed bits in optkey, ifpart, and + /// elsepart and returns what's left after the option is removed. + /// if there's an error, it returns format itself. + docstring parseOptions(docstring const & format, docstring & optkey, + docstring & ifpart, docstring & elsepart) + { + LASSERT(format[0] == '{' && format[1] == '%', return format); + // strip '{%' + docstring fmt = format.substr(2); + size_t pos = fmt.find('%'); // end of key + if (pos == string::npos) { + LYXERR0("Error parsing `" << format <<"'. Can't find end of key."); + return format; + } + optkey = fmt.substr(0,pos); + fmt = fmt.substr(pos + 1); + // [[format]] should be next + if (fmt[0] != '[' || fmt[1] != '[') { + LYXERR0("Error parsing `" << format <<"'. Can't find '[[' after key."); + return format; + } + + docstring curfmt = fmt; + fmt = getClause(curfmt, ifpart); + if (fmt == curfmt) { + LYXERR0("Error parsing `" << format <<"'. Couldn't get if clause."); + return format; + } + + if (fmt[0] == '}') // we're done, no else clause + return fmt.substr(1); + + // else part should follow + if (fmt[0] != '[' || fmt[1] != '[') { + LYXERR0("Error parsing `" << format <<"'. Can't find else clause."); + return format; + } + + curfmt = fmt; + fmt = getClause(curfmt, elsepart); + // we should be done + if (fmt == curfmt || fmt[0] != '}') { + LYXERR0("Error parsing `" << format <<"'. Can't find end of option."); + return format; + } + return fmt.substr(1); +} + +} // anon namespace + + +docstring BibTeXInfo::expandFormat(docstring const & format, + BibTeXInfo const * const xref) const +{ + // return value + docstring ret; + docstring key; + bool scanning_key = false; + + docstring fmt = format; + // we'll remove characters from the front of fmt as we + // deal with them + while (fmt.size()) { + char_type thischar = fmt[0]; + if (thischar == '%') { + // beginning or end of key + if (scanning_key) { + // end of key + scanning_key = false; + // so we replace the key with its value, which may be empty + docstring const val = getValueForKey(to_utf8(key), xref); + key.clear(); + ret += val; + } else { + // beginning of key + scanning_key = true; + } + } + else if (thischar == '{') { + // beginning of option? + if (scanning_key) { + LYXERR0("ERROR: Found `{' when scanning key in `" << format << "'."); + return _("ERROR!"); + } + if (fmt.size() > 1 && fmt[1] == '%') { + // it is the beginning of an optional format + docstring optkey; + docstring ifpart; + docstring elsepart; + docstring const newfmt = + parseOptions(fmt, optkey, ifpart, elsepart); + if (newfmt == fmt) // parse error + return _("ERROR!"); + fmt = newfmt; + docstring const val = getValueForKey(to_utf8(optkey), xref); + if (!val.empty()) + ret += expandFormat(ifpart, xref); + else if (!elsepart.empty()) + ret += expandFormat(elsepart, xref); + // fmt will have been shortened for us already + continue; + } + ret += thischar; + } + else if (scanning_key) + key += thischar; + else + ret += thischar; + fmt = fmt.substr(1); + } // for loop + if (scanning_key) { + LYXERR0("Never found end of key in `" << format << "'!"); + return _("ERROR!"); + } + return ret; +} + + +namespace { + +// FIXME These would be better read from a file, so that they +// could be customized. + + static docstring articleFormat = from_ascii("%author%, \"%title%\", %journal% {%volume%[[ %volume%{%number%[[, %number%]]}]]} (%year%){%pages%[[, pp. %pages%]]}.{%note%[[ %note%]]}"); + +static docstring bookFormat = from_ascii("{%author%[[%author%]][[%editor%, ed.]]}, %title%{%volume%[[ vol. %volume%]][[{%number%[[no. %number%]]}]]}{%edition%[[%edition%]]} ({%address%[[%address%: ]]}%publisher%, %year%).{%note%[[ %note%]]}"); + +static docstring inSomething = from_ascii("%author%, \"%title%\", in{%editor%[[ %editor%, ed.,]]} %booktitle%{%volume%[[ vol. %volume%]][[{%number%[[no. %number%]]}]]}{%edition%[[%edition%]]} ({%address%[[%address%: ]]}%publisher%, %year%){%pages%[[, pp. %pages%]]}.{%note%[[ %note%]]}"); + +static docstring thesis = from_ascii("%author%, %title% ({%address%[[%address%: ]]}%school%, %year%).{%note%[[ %note%]]}"); + +static docstring defaultFormat = from_ascii("{%author%[[%author%, ]][[{%editor%[[%editor%, ed., ]]}]]}%title%{%journal%[[, %journal%]][[{%publisher%[[, %publisher%]][[{%institution%[[, %institution%]]}]]}]]}{%year%[[ (%year%)]]}{%pages%[[, %pages%]]}."); + +} + docstring const & BibTeXInfo::getInfo(BibTeXInfo const * const xref) const { if (!info_.empty()) @@ -286,59 +488,21 @@ docstring const & BibTeXInfo::getInfo(BibTeXInfo const * const xref) const info_ = it->second; return info_; } - - // FIXME - // This could be made a lot better using the entry_type_ - // field to customize the output based upon entry type. - - // Search for all possible "required" fields - docstring author = getValueForKey("author", xref); - if (author.empty()) - author = getValueForKey("editor", xref); - - docstring year = getValueForKey("year", xref); - docstring title = getValueForKey("title", xref); - docstring docLoc = getValueForKey("pages", xref); - if (docLoc.empty()) { - docLoc = getValueForKey("chapter", xref); - if (!docLoc.empty()) - docLoc = _("Ch. ") + docLoc; - } else { - docLoc = _("pp. ") + docLoc; - } - docstring media = getValueForKey("journal", xref); - if (media.empty()) { - media = getValueForKey("publisher", xref); - if (media.empty()) { - media = getValueForKey("school", xref); - if (media.empty()) - media = getValueForKey("institution"); - } - } - docstring volume = getValueForKey("volume", xref); - - odocstringstream result; - if (!author.empty()) - result << author << ", "; - if (!title.empty()) - result << title; - if (!media.empty()) - result << ", " << media; - if (!year.empty()) - result << " (" << year << ")"; - if (!docLoc.empty()) - result << ", " << docLoc; - - docstring const result_str = rtrim(result.str()); - if (!result_str.empty()) { - info_ = convertLaTeXCommands(result_str); - return info_; - } + if (entry_type_ == "article") + info_ = expandFormat(articleFormat, xref); + else if (entry_type_ == "book") + info_ = expandFormat(bookFormat, xref); + else if (entry_type_.substr(0,2) == "in") + info_ = expandFormat(inSomething, xref); + else if (entry_type_ == "phdthesis" || entry_type_ == "mastersthesis") + info_ = expandFormat(thesis, xref); + else + info_ = expandFormat(defaultFormat, xref); - // This should never happen (or at least be very unusual!) - static docstring e = docstring(); - return e; + if (!info_.empty()) + info_ = convertLaTeXCommands(info_); + return info_; } diff --git a/src/BiblioInfo.h b/src/BiblioInfo.h index c9a9150d51..24fa2caa27 100644 --- a/src/BiblioInfo.h +++ b/src/BiblioInfo.h @@ -106,6 +106,14 @@ private: /// be the one referenced in the crossref field. docstring getValueForKey(std::string const & key, BibTeXInfo const * const xref = 0) const; + /// replace %keys% in a format string with their values + /// called from getInfo() + /// format strings may contain: + /// %key%, which represents a key + /// {%key%[[format]]}, which prints format if key is non-empty + /// the latter may optionally contain an `else' clause as well: + /// {%key%[[if format]][[else format]]} + docstring expandFormat(docstring const & fmt, BibTeXInfo const * const xref) const; /// true if from BibTeX; false if from bibliography environment bool is_bibtex_; /// the BibTeX key for this entry -- 2.39.5