lyx_mirror/src/insets/InsetBibtex.cpp

1007 lines
25 KiB
C++
Raw Normal View History

/**
* \file InsetBibtex.cpp
* This file is part of LyX, the document processor.
* Licence details can be found in the file COPYING.
*
* \author Alejandro Aguilar Sierra
* \author Richard Heck (BibTeX parser improvements)
*
* Full author contact details are available in file CREDITS.
*/
#include <config.h>
#include "InsetBibtex.h"
#include "BiblioInfo.h"
#include "Buffer.h"
#include "BufferParams.h"
#include "Cursor.h"
#include "DispatchResult.h"
#include "Encoding.h"
#include "Format.h"
#include "FuncRequest.h"
#include "FuncStatus.h"
#include "Language.h"
#include "LaTeXFeatures.h"
#include "output_xhtml.h"
#include "OutputParams.h"
#include "PDFOptions.h"
#include "TextClass.h"
#include "frontends/alert.h"
#include "support/convert.h"
#include "support/debug.h"
#include "support/docstream.h"
#include "support/ExceptionMessage.h"
#include "support/FileNameList.h"
#include "support/filetools.h"
#include "support/gettext.h"
#include "support/lstrings.h"
#include "support/os.h"
Rename files in src/support, step one. src/support/package.h src/support/Package.h Package src/support/package.C.in src/support/Package.C.in Package src/support/path.h src/support/Path.h Path src/support/fs_extras.h src/support/fs_extras.h NOCLASSES src/support/RandomAccessList.h src/support/RandomAccessList.h RandomAccessList src/support/lyxmanip.h src/support/lyxmanip.h NOCLASSES src/support/rename.C src/support/rename.cpp NOCLASSES src/support/abort.C src/support/abort.cpp NOCLASSES src/support/lyxlib.h src/support/lyxlib.h NOCLASSES src/support/ExceptionMessage.h src/support/ExceptionMessage.h ExceptionMessage src/support/copy.C src/support/copy.cpp NOCLASSES src/support/limited_stack.h src/support/limited_stack.h limited_stack src/support/filefilterlist.C src/support/FileFilterList.cpp ['FileFilterList', 'Filter'] src/support/cow_ptr.h src/support/cow_ptr.h cow_ptr src/support/os_unix.C src/support/os_unix.cpp NOCLASSES src/support/socktools.h src/support/socktools.h NOCLASSES src/support/forkedcontr.h src/support/ForkedcallsController.h ForkedcallsController src/support/os.h src/support/os.h NOCLASSES src/support/FileMonitor.h src/support/FileMonitor.h FileMonitor src/support/copied_ptr.h src/support/copied_ptr.h copied_ptr src/support/translator.h src/support/Translator.h Translator src/support/filetools.C src/support/filetools.cpp NOCLASSES src/support/unlink.C src/support/unlink.cpp NOCLASSES src/support/os_win32.C src/support/os_win32.cpp GetFolderPath src/support/lstrings.C src/support/lstrings.cpp NOCLASSES src/support/qstring_helpers.C src/support/qstring_helpers.cpp NOCLASSES src/support/getcwd.C src/support/getcwd.cpp NOCLASSES src/support/systemcall.C src/support/Systemcall.cpp Systemcall src/support/lyxalgo.h src/support/lyxalgo.h NOCLASSES src/support/filefilterlist.h src/support/FileFilterList.h ['FileFilterList', 'Filter'] src/support/unicode.C src/support/unicode.cpp IconvProcessor src/support/userinfo.C src/support/userinfo.cpp NOCLASSES src/support/lyxtime.C src/support/lyxtime.cpp NOCLASSES src/support/kill.C src/support/kill.cpp NOCLASSES src/support/docstring.C src/support/docstring.cpp to_local8bit_failure src/support/os_cygwin.C src/support/os_cygwin.cpp NOCLASSES src/support/lyxsum.C src/support/lyxsum.cpp NOCLASSES src/support/environment.C src/support/environment.cpp NOCLASSES src/support/filetools.h src/support/filetools.h NOCLASSES src/support/textutils.C src/support/textutils.cpp NOCLASSES src/support/mkdir.C src/support/mkdir.cpp NOCLASSES src/support/forkedcall.C src/support/Forkedcall.cpp ['ForkedProcess', 'Forkedcall'] src/support/tempname.C src/support/tempname.cpp NOCLASSES src/support/os_win32.h src/support/os_win32.h GetFolderPath src/support/types.h src/support/types.h NOCLASSES src/support/lstrings.h src/support/lstrings.h NOCLASSES src/support/forkedcallqueue.C src/support/ForkedCallQueue.cpp ForkedCallQueue src/support/qstring_helpers.h src/support/qstring_helpers.h NOCLASSES src/support/convert.C src/support/convert.cpp NOCLASSES src/support/filename.C src/support/FileName.cpp ['FileName', 'DocFileName'] src/support/tests/convert.C src/support/tests/convert.cpp NOCLASSES src/support/tests/filetools.C src/support/tests/filetools.cpp NOCLASSES src/support/tests/lstrings.C src/support/tests/lstrings.cpp NOCLASSES src/support/tests/boost.C src/support/tests/boost.cpp NOCLASSES src/support/docstream.C src/support/docstream.cpp ['iconv_codecvt_facet_exception', 'idocfstream', 'odocfstream'] src/support/std_istream.h src/support/std_istream.h NOCLASSES src/support/systemcall.h src/support/Systemcall.h Systemcall src/support/chdir.C src/support/chdir.cpp NOCLASSES src/support/std_ostream.h src/support/std_ostream.h NOCLASSES src/support/unicode.h src/support/unicode.h IconvProcessor src/support/path.C src/support/Path.cpp Path src/support/fs_extras.C src/support/fs_extras.cpp NOCLASSES src/support/userinfo.h src/support/userinfo.h NOCLASSES src/support/lyxtime.h src/support/lyxtime.h NOCLASSES src/support/docstring.h src/support/docstring.h to_local8bit_failure src/support/debugstream.h src/support/debugstream.h basic_debugstream src/support/environment.h src/support/environment.h NOCLASSES src/support/textutils.h src/support/textutils.h NOCLASSES src/support/forkedcall.h src/support/Forkedcall.h ['ForkedProcess', 'Forkedcall'] src/support/socktools.C src/support/socktools.cpp NOCLASSES src/support/forkedcallqueue.h src/support/ForkedCallQueue.h ForkedCallQueue src/support/forkedcontr.C src/support/ForkedcallsController.cpp ForkedcallsController src/support/os.C src/support/os.cpp NOCLASSES src/support/convert.h src/support/convert.h NOCLASSES src/support/filename.h src/support/FileName.h ['FileName', 'DocFileName'] src/support/docstream.h src/support/docstream.h ['iconv_codecvt_facet_exception', 'idocfstream', 'odocfstream'] src/support/FileMonitor.C src/support/FileMonitor.cpp FileMonitor git-svn-id: svn://svn.lyx.org/lyx/lyx-devel/trunk@18024 a592a061-630c-0410-9148-cb99ea01b6c8
2007-04-26 05:12:52 +00:00
#include "support/Path.h"
#include "support/textutils.h"
#include <limits>
using namespace std;
using namespace lyx::support;
namespace lyx {
namespace Alert = frontend::Alert;
namespace os = support::os;
InsetBibtex::InsetBibtex(Buffer * buf, InsetCommandParams const & p)
: InsetCommand(buf, p)
{
buffer().invalidateBibinfoCache();
}
InsetBibtex::~InsetBibtex()
{
if (isBufferLoaded())
buffer().invalidateBibfileCache();
}
ParamInfo const & InsetBibtex::findInfo(string const & /* cmdName */)
{
static ParamInfo param_info_;
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("btprint", ParamInfo::LATEX_OPTIONAL);
param_info_.add("bibfiles", ParamInfo::LATEX_REQUIRED);
param_info_.add("options", ParamInfo::LYX_INTERNAL);
}
return param_info_;
}
void InsetBibtex::doDispatch(Cursor & cur, FuncRequest & cmd)
{
switch (cmd.action()) {
case LFUN_INSET_EDIT:
editDatabases();
break;
case LFUN_INSET_MODIFY: {
InsetCommandParams p(BIBTEX_CODE);
try {
if (!InsetCommand::string2params(to_utf8(cmd.argument()), p)) {
cur.noScreenUpdate();
break;
}
} catch (ExceptionMessage const & message) {
if (message.type_ == WarningException) {
Alert::warning(message.title_, message.details_);
cur.noScreenUpdate();
} else
throw message;
break;
}
cur.recordUndo();
setParams(p);
buffer().invalidateBibfileCache();
cur.forceBufferUpdate();
break;
}
default:
InsetCommand::doDispatch(cur, cmd);
break;
}
}
bool InsetBibtex::getStatus(Cursor & cur, FuncRequest const & cmd,
FuncStatus & flag) const
{
switch (cmd.action()) {
case LFUN_INSET_EDIT:
flag.setEnabled(true);
return true;
default:
return InsetCommand::getStatus(cur, cmd, flag);
}
}
void InsetBibtex::editDatabases() const
{
vector<docstring> bibfilelist = getVectorFromString(getParam("bibfiles"));
if (bibfilelist.empty())
return;
int nr_databases = bibfilelist.size();
if (nr_databases > 1) {
docstring message = bformat(_("The BibTeX inset includes %1$s databases.\n"
"If you proceed, all of them will be opened."),
convert<docstring>(nr_databases));
int const ret = Alert::prompt(_("Open Databases?"),
message, 0, 1, _("&Cancel"), _("&Proceed"));
if (ret == 0)
return;
}
vector<docstring>::const_iterator it = bibfilelist.begin();
vector<docstring>::const_iterator en = bibfilelist.end();
for (; it != en; ++it) {
FileName const bibfile = getBibTeXPath(*it, buffer());
formats.edit(buffer(), bibfile,
formats.getFormatFromFile(bibfile));
}
}
docstring InsetBibtex::screenLabel() const
{
return _("BibTeX Generated Bibliography");
}
docstring InsetBibtex::toolTip(BufferView const & /*bv*/, int /*x*/, int /*y*/) const
{
docstring item = from_ascii("* ");
docstring tip = _("Databases:") + "\n";
vector<docstring> bibfilelist = getVectorFromString(getParam("bibfiles"));
if (bibfilelist.empty()) {
tip += item;
tip += _("none");
} else {
vector<docstring>::const_iterator it = bibfilelist.begin();
vector<docstring>::const_iterator en = bibfilelist.end();
for (; it != en; ++it) {
tip += item;
tip += *it + "\n";
}
}
// Style-Options
bool toc = false;
docstring style = getParam("options"); // maybe empty! and with bibtotoc
docstring bibtotoc = from_ascii("bibtotoc");
if (prefixIs(style, bibtotoc)) {
toc = true;
if (contains(style, char_type(',')))
style = split(style, bibtotoc, char_type(','));
}
tip += _("Style File:") +"\n";
tip += item;
if (!style.empty())
tip += style;
else
tip += _("none");
tip += "\n" + _("Lists:") + " ";
docstring btprint = getParam("btprint");
if (btprint == "btPrintAll")
tip += _("all references");
else if (btprint == "btPrintNotCited")
tip += _("all uncited references");
else
tip += _("all cited references");
if (toc) {
tip += ", ";
tip += _("included in TOC");
}
return tip;
}
static string normalizeName(Buffer const & buffer,
OutputParams const & runparams, string const & name, string const & ext)
{
string const fname = makeAbsPath(name, buffer.filePath()).absFileName();
if (FileName::isAbsolute(name) || !FileName(fname + ext).isReadableFile())
return name;
if (!runparams.nice)
return fname;
// FIXME UNICODE
return to_utf8(makeRelPath(from_utf8(fname),
from_utf8(buffer.masterBuffer()->filePath())));
}
int InsetBibtex::latex(odocstream & os, OutputParams const & runparams) const
{
// the sequence of the commands:
// 1. \bibliographystyle{style}
// 2. \addcontentsline{...} - if option bibtotoc set
// 3. \bibliography{database}
// and with bibtopic:
// 1. \bibliographystyle{style}
// 2. \begin{btSect}{database}
// 3. \btPrint{Cited|NotCited|All}
// 4. \end{btSect}
// Database(s)
// If we are processing the LaTeX file in a temp directory then
// copy the .bib databases to this temp directory, mangling their
// names in the process. Store this mangled name in the list of
// all databases.
// (We need to do all this because BibTeX *really*, *really*
// can't handle "files with spaces" and Windows users tend to
// use such filenames.)
// Otherwise, store the (maybe absolute) path to the original,
// unmangled database name.
vector<docstring> bibfilelist = getVectorFromString(getParam("bibfiles"));
vector<docstring>::const_iterator it = bibfilelist.begin();
vector<docstring>::const_iterator en = bibfilelist.end();
odocstringstream dbs;
bool didone = false;
for (; it != en; ++it) {
string utf8input = to_utf8(*it);
string database =
normalizeName(buffer(), runparams, utf8input, ".bib");
FileName const try_in_file =
makeAbsPath(database + ".bib", buffer().filePath());
bool const not_from_texmf = try_in_file.isReadableFile();
if (!runparams.inComment && !runparams.dryrun && !runparams.nice &&
not_from_texmf) {
// mangledFileName() needs the extension
DocFileName const in_file = DocFileName(try_in_file);
database = removeExtension(in_file.mangledFileName());
FileName const out_file = makeAbsPath(database + ".bib",
buffer().masterBuffer()->temppath());
bool const success = in_file.copyTo(out_file);
if (!success) {
lyxerr << "Failed to copy '" << in_file
<< "' to '" << out_file << "'"
<< endl;
}
} else if (!runparams.inComment && runparams.nice && not_from_texmf) {
if (!isValidLaTeXFileName(database)) {
frontend::Alert::warning(_("Invalid filename"),
_("The following filename will cause troubles "
"when running the exported file through LaTeX: ") +
from_utf8(database));
}
if (!isValidDVIFileName(database)) {
frontend::Alert::warning(_("Problematic filename for DVI"),
_("The following filename can cause troubles "
"when running the exported file through LaTeX "
"and opening the resulting DVI: ") +
from_utf8(database), true);
}
}
if (didone)
dbs << ',';
else
didone = true;
// FIXME UNICODE
dbs << from_utf8(latex_path(database));
}
docstring const db_out = dbs.str();
// Post this warning only once.
static bool warned_about_spaces = false;
if (!warned_about_spaces &&
runparams.nice && db_out.find(' ') != docstring::npos) {
warned_about_spaces = true;
Alert::warning(_("Export Warning!"),
_("There are spaces in the paths to your BibTeX databases.\n"
"BibTeX will be unable to find them."));
}
// Style-Options
string style = to_utf8(getParam("options")); // maybe empty! and with bibtotoc
string bibtotoc;
if (prefixIs(style, "bibtotoc")) {
bibtotoc = "bibtotoc";
if (contains(style, ','))
style = split(style, bibtotoc, ',');
}
// line count
int nlines = 0;
if (!style.empty()) {
string base = normalizeName(buffer(), runparams, style, ".bst");
FileName const try_in_file =
makeAbsPath(base + ".bst", buffer().filePath());
bool const not_from_texmf = try_in_file.isReadableFile();
// If this style does not come from texmf and we are not
// exporting to .tex copy it to the tmp directory.
// This prevents problems with spaces and 8bit charcaters
// in the file name.
if (!runparams.inComment && !runparams.dryrun && !runparams.nice &&
not_from_texmf) {
// use new style name
DocFileName const in_file = DocFileName(try_in_file);
base = removeExtension(in_file.mangledFileName());
FileName const out_file = makeAbsPath(base + ".bst",
buffer().masterBuffer()->temppath());
bool const success = in_file.copyTo(out_file);
if (!success) {
lyxerr << "Failed to copy '" << in_file
<< "' to '" << out_file << "'"
<< endl;
}
}
// FIXME UNICODE
os << "\\bibliographystyle{"
<< from_utf8(latex_path(normalizeName(buffer(), runparams, base, ".bst")))
<< "}\n";
nlines += 1;
}
// Post this warning only once.
static bool warned_about_bst_spaces = false;
if (!warned_about_bst_spaces && runparams.nice && contains(style, ' ')) {
warned_about_bst_spaces = true;
Alert::warning(_("Export Warning!"),
_("There are spaces in the path to your BibTeX style file.\n"
"BibTeX will be unable to find it."));
}
if (!db_out.empty() && buffer().params().use_bibtopic) {
os << "\\begin{btSect}{" << db_out << "}\n";
docstring btprint = getParam("btprint");
if (btprint.empty())
// default
btprint = from_ascii("btPrintCited");
os << "\\" << btprint << "\n"
<< "\\end{btSect}\n";
nlines += 3;
}
// bibtotoc-Option
if (!bibtotoc.empty() && !buffer().params().use_bibtopic) {
// set label for hyperref, see http://www.lyx.org/trac/ticket/6470
if (buffer().params().pdfoptions().use_hyperref)
os << "\\phantomsection";
if (buffer().params().documentClass().hasLaTeXLayout("chapter"))
os << "\\addcontentsline{toc}{chapter}{\\bibname}";
else if (buffer().params().documentClass().hasLaTeXLayout("section"))
os << "\\addcontentsline{toc}{section}{\\refname}";
}
if (!db_out.empty() && !buffer().params().use_bibtopic) {
docstring btprint = getParam("btprint");
if (btprint == "btPrintAll") {
os << "\\nocite{*}\n";
nlines += 1;
}
os << "\\bibliography{" << db_out << "}\n";
nlines += 1;
}
return nlines;
}
support::FileNameList InsetBibtex::getBibFiles() const
{
FileName path(buffer().filePath());
support::PathChanger p(path);
support::FileNameList vec;
vector<docstring> bibfilelist = getVectorFromString(getParam("bibfiles"));
vector<docstring>::const_iterator it = bibfilelist.begin();
vector<docstring>::const_iterator en = bibfilelist.end();
for (; it != en; ++it) {
FileName const file = getBibTeXPath(*it, buffer());
if (!file.empty())
vec.push_back(file);
else
LYXERR0("Couldn't find " + to_utf8(*it) + " in InsetBibtex::getBibFiles()!");
}
return vec;
}
namespace {
// methods for parsing bibtex files
typedef map<docstring, docstring> VarMap;
/// remove whitespace characters, optionally a single comma,
/// and further whitespace characters from the stream.
/// @return true if a comma was found, false otherwise
///
bool removeWSAndComma(ifdocstream & ifs) {
char_type ch;
if (!ifs)
return false;
// skip whitespace
do {
ifs.get(ch);
} while (ifs && isSpace(ch));
if (!ifs)
return false;
if (ch != ',') {
ifs.putback(ch);
return false;
}
// skip whitespace
do {
ifs.get(ch);
} while (ifs && isSpace(ch));
if (ifs) {
ifs.putback(ch);
}
return true;
}
enum charCase {
makeLowerCase,
keepCase
};
/// remove whitespace characters, read characer sequence
/// not containing whitespace characters or characters in
/// delimChars, and remove further whitespace characters.
///
/// @return true if a string of length > 0 could be read.
///
bool readTypeOrKey(docstring & val, ifdocstream & ifs,
docstring const & delimChars, docstring const & illegalChars,
charCase chCase) {
char_type ch;
val.clear();
if (!ifs)
return false;
// skip whitespace
do {
ifs.get(ch);
} while (ifs && isSpace(ch));
if (!ifs)
return false;
// read value
bool legalChar = true;
while (ifs && !isSpace(ch) &&
delimChars.find(ch) == docstring::npos &&
(legalChar = (illegalChars.find(ch) == docstring::npos))
)
{
if (chCase == makeLowerCase)
val += lowercase(ch);
else
val += ch;
ifs.get(ch);
}
if (!legalChar) {
ifs.putback(ch);
return false;
}
// skip whitespace
while (ifs && isSpace(ch)) {
ifs.get(ch);
}
if (ifs) {
ifs.putback(ch);
}
return val.length() > 0;
}
/// read subsequent bibtex values that are delimited with a #-character.
/// Concatenate all parts and replace names with the associated string in
/// the variable strings.
/// @return true if reading was successfull (all single parts were delimited
/// correctly)
bool readValue(docstring & val, ifdocstream & ifs, const VarMap & strings) {
char_type ch;
val.clear();
if (!ifs)
return false;
do {
// skip whitespace
do {
ifs.get(ch);
} while (ifs && isSpace(ch));
if (!ifs)
return false;
// check for field type
if (isDigitASCII(ch)) {
// read integer value
do {
val += ch;
ifs.get(ch);
} while (ifs && isDigitASCII(ch));
if (!ifs)
return false;
} else if (ch == '"' || ch == '{') {
// set end delimiter
char_type delim = ch == '"' ? '"': '}';
// Skip whitespace
do {
ifs.get(ch);
} while (ifs && isSpace(ch));
if (!ifs)
return false;
// We now have the first non-whitespace character
// We'll collapse adjacent whitespace.
bool lastWasWhiteSpace = false;
// inside this delimited text braces must match.
// Thus we can have a closing delimiter only
// when nestLevel == 0
int nestLevel = 0;
while (ifs && (nestLevel > 0 || ch != delim)) {
if (isSpace(ch)) {
lastWasWhiteSpace = true;
ifs.get(ch);
continue;
}
// We output the space only after we stop getting
// whitespace so as not to output any whitespace
// at the end of the value.
if (lastWasWhiteSpace) {
lastWasWhiteSpace = false;
val += ' ';
}
val += ch;
// update nesting level
switch (ch) {
case '{':
++nestLevel;
break;
case '}':
--nestLevel;
if (nestLevel < 0)
return false;
break;
}
if (ifs)
ifs.get(ch);
}
if (!ifs)
return false;
// FIXME Why is this here?
ifs.get(ch);
if (!ifs)
return false;
} else {
// reading a string name
docstring strName;
while (ifs && !isSpace(ch) && ch != '#' && ch != ',' && ch != '}' && ch != ')') {
strName += lowercase(ch);
ifs.get(ch);
}
if (!ifs)
return false;
// replace the string with its assigned value or
// discard it if it's not assigned
if (strName.length()) {
VarMap::const_iterator pos = strings.find(strName);
if (pos != strings.end()) {
val += pos->second;
}
}
}
// skip WS
while (ifs && isSpace(ch)) {
ifs.get(ch);
}
if (!ifs)
return false;
// continue reading next value on concatenate with '#'
} while (ch == '#');
ifs.putback(ch);
return true;
}
}
void InsetBibtex::collectBibKeys(InsetIterator const & /*di*/) const
{
parseBibTeXFiles();
}
void InsetBibtex::parseBibTeXFiles() const
{
// This bibtex parser is a first step to parse bibtex files
// more precisely.
//
// - it reads the whole bibtex entry and does a syntax check
// (matching delimiters, missing commas,...
// - it recovers from errors starting with the next @-character
// - it reads @string definitions and replaces them in the
// field values.
// - it accepts more characters in keys or value names than
// bibtex does.
//
// Officially bibtex does only support ASCII, but in practice
// you can use the encoding of the main document as long as
// some elements like keys and names are pure ASCII. Therefore
// we convert the file from the buffer encoding.
// We don't restrict keys to ASCII in LyX, since our own
// InsetBibitem can generate non-ASCII keys, and nonstandard
// 8bit clean bibtex forks exist.
BiblioInfo keylist;
support::FileNameList const files = getBibFiles();
support::FileNameList::const_iterator it = files.begin();
support::FileNameList::const_iterator en = files.end();
for (; it != en; ++ it) {
ifdocstream ifs(it->toFilesystemEncoding().c_str(),
ios_base::in, buffer().params().encoding().iconvName());
char_type ch;
VarMap strings;
while (ifs) {
ifs.get(ch);
if (!ifs)
break;
if (ch != '@')
continue;
docstring entryType;
if (!readTypeOrKey(entryType, ifs, from_ascii("{("), docstring(), makeLowerCase)) {
lyxerr << "BibTeX Parser: Error reading entry type." << std::endl;
continue;
}
if (!ifs) {
lyxerr << "BibTeX Parser: Unexpected end of file." << std::endl;
continue;
}
if (entryType == from_ascii("comment")) {
ifs.ignore(numeric_limits<int>::max(), '\n');
continue;
}
ifs.get(ch);
if (!ifs) {
lyxerr << "BibTeX Parser: Unexpected end of file." << std::endl;
break;
}
if ((ch != '(') && (ch != '{')) {
lyxerr << "BibTeX Parser: Invalid entry delimiter." << std::endl;
ifs.putback(ch);
continue;
}
// process the entry
if (entryType == from_ascii("string")) {
// read string and add it to the strings map
// (or replace it's old value)
docstring name;
docstring value;
if (!readTypeOrKey(name, ifs, from_ascii("="), from_ascii("#{}(),"), makeLowerCase)) {
lyxerr << "BibTeX Parser: Error reading string name." << std::endl;
continue;
}
if (!ifs) {
lyxerr << "BibTeX Parser: Unexpected end of file." << std::endl;
continue;
}
// next char must be an equal sign
ifs.get(ch);
if (!ifs || ch != '=') {
lyxerr << "BibTeX Parser: No `=' after string name: " <<
name << "." << std::endl;
continue;
}
if (!readValue(value, ifs, strings)) {
lyxerr << "BibTeX Parser: Unable to read value for string: " <<
name << "." << std::endl;
continue;
}
strings[name] = value;
} else if (entryType == from_ascii("preamble")) {
// preamble definitions are discarded.
// can they be of any use in lyx?
docstring value;
if (!readValue(value, ifs, strings)) {
lyxerr << "BibTeX Parser: Unable to read preamble value." << std::endl;
continue;
}
} else {
// Citation entry. Try to read the key.
docstring key;
if (!readTypeOrKey(key, ifs, from_ascii(","), from_ascii("}"), keepCase)) {
lyxerr << "BibTeX Parser: Unable to read key for entry type:" <<
entryType << "." << std::endl;
continue;
}
if (!ifs) {
lyxerr << "BibTeX Parser: Unexpected end of file." << std::endl;
continue;
}
/////////////////////////////////////////////
// now we have a key, so we will add an entry
// (even if it's empty, as bibtex does)
//
// we now read the field = value pairs.
// all items must be separated by a comma. If
// it is missing the scanning of this entry is
// stopped and the next is searched.
docstring name;
docstring value;
docstring data;
BibTeXInfo keyvalmap(key, entryType);
bool readNext = removeWSAndComma(ifs);
while (ifs && readNext) {
// read field name
if (!readTypeOrKey(name, ifs, from_ascii("="),
from_ascii("{}(),"), makeLowerCase) || !ifs)
break;
// next char must be an equal sign
// FIXME Whitespace??
ifs.get(ch);
if (!ifs) {
lyxerr << "BibTeX Parser: Unexpected end of file." << std::endl;
break;
}
if (ch != '=') {
lyxerr << "BibTeX Parser: Missing `=' after field name: " <<
name << ", for key: " << key << "." << std::endl;
ifs.putback(ch);
break;
}
// read field value
if (!readValue(value, ifs, strings)) {
lyxerr << "BibTeX Parser: Unable to read value for field: " <<
name << ", for key: " << key << "." << std::endl;
break;
}
keyvalmap[name] = value;
data += "\n\n" + value;
keylist.addFieldName(name);
readNext = removeWSAndComma(ifs);
}
// add the new entry
keylist.addEntryType(entryType);
keyvalmap.setAllData(data);
keylist[key] = keyvalmap;
} //< else (citation entry)
} //< searching '@'
} //< for loop over files
buffer().addBiblioInfo(keylist);
}
FileName InsetBibtex::getBibTeXPath(docstring const & filename, Buffer const & buf)
{
string texfile = changeExtension(to_utf8(filename), "bib");
// note that, if the filename can be found directly from the path,
// findtexfile will just return a FileName object for that path.
FileName file(findtexfile(texfile, "bib"));
if (file.empty())
file = FileName(makeAbsPath(texfile, buf.filePath()));
return file;
}
bool InsetBibtex::addDatabase(docstring const & db)
{
docstring bibfiles = getParam("bibfiles");
if (tokenPos(bibfiles, ',', db) != -1)
return false;
if (!bibfiles.empty())
bibfiles += ',';
setParam("bibfiles", bibfiles + db);
return true;
}
bool InsetBibtex::delDatabase(docstring const & db)
{
docstring bibfiles = getParam("bibfiles");
if (contains(bibfiles, db)) {
int const n = tokenPos(bibfiles, ',', db);
docstring bd = db;
if (n > 0) {
// this is not the first database
docstring tmp = ',' + bd;
setParam("bibfiles", subst(bibfiles, tmp, docstring()));
} else if (n == 0)
// this is the first (or only) database
setParam("bibfiles", split(bibfiles, bd, ','));
else
return false;
}
return true;
}
void InsetBibtex::validate(LaTeXFeatures & features) const
{
if (features.bufferParams().use_bibtopic)
features.require("bibtopic");
// FIXME XHTML
// It'd be better to be able to get this from an InsetLayout, but at present
// InsetLayouts do not seem really to work for things that aren't InsetTexts.
if (features.runparams().flavor == OutputParams::HTML)
features.addPreambleSnippet("<style type=\"text/css\">\n"
"div.bibtexentry { margin-left: 2em; text-indent: -2em; }\n"
"span.bibtexlabel:before{ content: \"[\"; }\n"
"span.bibtexlabel:after{ content: \"] \"; }\n"
"</style>");
}
// FIXME
// docstring InsetBibtex::entriesAsXHTML(vector<docstring> const & entries)
// And then here just: entriesAsXHTML(buffer().masterBibInfo().citedEntries())
docstring InsetBibtex::xhtml(XHTMLStream & xs, OutputParams const &) const
{
BiblioInfo const & bibinfo = buffer().masterBibInfo();
vector<docstring> const & cites = bibinfo.citedEntries();
CiteEngine const engine = buffer().params().citeEngine();
bool const numbers =
(engine == ENGINE_BASIC || engine == ENGINE_NATBIB_NUMERICAL);
docstring reflabel = from_ascii("References");
Language const * l = buffer().params().language;
if (l)
reflabel = translateIfPossible(reflabel, l->code());
xs << html::StartTag("h2", "class='bibtex'")
<< reflabel
<< html::EndTag("h2")
<< html::StartTag("div", "class='bibtex'");
// Now we loop over the entries
vector<docstring>::const_iterator vit = cites.begin();
vector<docstring>::const_iterator const ven = cites.end();
for (; vit != ven; ++vit) {
BiblioInfo::const_iterator const biit = bibinfo.find(*vit);
if (biit == bibinfo.end())
continue;
BibTeXInfo const & entry = biit->second;
xs << html::StartTag("div", "class='bibtexentry'");
// FIXME XHTML
// The same name/id problem we have elsewhere.
string const attr = "id='" + to_utf8(entry.key()) + "'";
xs << html::CompTag("a", attr);
docstring citekey;
if (numbers)
citekey = entry.citeNumber();
else {
docstring const auth = entry.getAbbreviatedAuthor();
// we do it this way so as to access the xref, if necessary
// note that this also gives us the modifier
docstring const year = bibinfo.getYear(*vit, true);
if (!auth.empty() && !year.empty())
citekey = auth + ' ' + year;
}
if (citekey.empty()) {
citekey = entry.label();
if (citekey.empty())
citekey = entry.key();
}
xs << html::StartTag("span", "class='bibtexlabel'")
<< citekey
<< html::EndTag("span");
// FIXME Right now, we are calling BibInfo::getInfo on the key,
// which will give us all the cross-referenced info. But for every
// entry, so there's a lot of repitition. This should be fixed.
xs << html::StartTag("span", "class='bibtexinfo'")
<< XHTMLStream::ESCAPE_AND
<< bibinfo.getInfo(entry.key(), buffer(), true)
<< html::EndTag("span")
<< html::EndTag("div");
xs.cr();
}
xs << html::EndTag("div");
return docstring();
}
docstring InsetBibtex::contextMenuName() const
{
return from_ascii("context-bibtex");
}
} // namespace lyx