X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2Finsets%2FInsetBibtex.cpp;h=876adea54b1770ad15a140a3a8d50eb2890edff5;hb=48b1e8a0aca2f3f3faa8f1f800568e47792ba9a0;hp=4ead8663492ef6ac631715b5997323210af1052b;hpb=8319b3e9d615adc6b4b49a67308884400d20373f;p=lyx.git diff --git a/src/insets/InsetBibtex.cpp b/src/insets/InsetBibtex.cpp index 4ead866349..876adea54b 100644 --- a/src/insets/InsetBibtex.cpp +++ b/src/insets/InsetBibtex.cpp @@ -26,20 +26,24 @@ #include "FuncRequest.h" #include "FuncStatus.h" #include "LaTeXFeatures.h" -#include "output_xhtml.h" +#include "output_latex.h" +#include "xml.h" #include "OutputParams.h" #include "PDFOptions.h" #include "texstream.h" #include "TextClass.h" +#include "TocBackend.h" #include "frontends/alert.h" #include "support/convert.h" #include "support/debug.h" #include "support/docstream.h" +#include "support/docstring_list.h" #include "support/ExceptionMessage.h" #include "support/FileNameList.h" #include "support/filetools.h" +#include "support/regex.h" #include "support/gettext.h" #include "support/lstrings.h" #include "support/os.h" @@ -47,6 +51,10 @@ #include "support/textutils.h" #include +#include +#include + +#include using namespace std; using namespace lyx::support; @@ -59,19 +67,7 @@ namespace os = support::os; InsetBibtex::InsetBibtex(Buffer * buf, InsetCommandParams const & p) : InsetCommand(buf, p) -{ - buffer().invalidateBibfileCache(); - buffer().removeBiblioTempFiles(); -} - - -InsetBibtex::~InsetBibtex() -{ - if (isBufferLoaded()) { - buffer().invalidateBibfileCache(); - buffer().removeBiblioTempFiles(); - } -} +{} ParamInfo const & InsetBibtex::findInfo(string const & /* cmdName */) @@ -81,6 +77,8 @@ ParamInfo const & InsetBibtex::findInfo(string const & /* cmdName */) param_info_.add("btprint", ParamInfo::LATEX_OPTIONAL); param_info_.add("bibfiles", ParamInfo::LATEX_REQUIRED); param_info_.add("options", ParamInfo::LYX_INTERNAL); + param_info_.add("encoding", ParamInfo::LYX_INTERNAL); + param_info_.add("file_encodings", ParamInfo::LYX_INTERNAL); param_info_.add("biblatexopts", ParamInfo::LATEX_OPTIONAL); } return param_info_; @@ -92,7 +90,7 @@ void InsetBibtex::doDispatch(Cursor & cur, FuncRequest & cmd) switch (cmd.action()) { case LFUN_INSET_EDIT: - editDatabases(); + editDatabases(cmd.argument()); break; case LFUN_INSET_MODIFY: { @@ -113,8 +111,7 @@ void InsetBibtex::doDispatch(Cursor & cur, FuncRequest & cmd) cur.recordUndo(); setParams(p); - buffer().invalidateBibfileCache(); - buffer().removeBiblioTempFiles(); + cur.buffer()->clearBibFileCache(); cur.forceBufferUpdate(); break; } @@ -140,16 +137,16 @@ bool InsetBibtex::getStatus(Cursor & cur, FuncRequest const & cmd, } -void InsetBibtex::editDatabases() const +void InsetBibtex::editDatabases(docstring const & db) const { vector bibfilelist = getVectorFromString(getParam("bibfiles")); if (bibfilelist.empty()) return; - int nr_databases = bibfilelist.size(); - if (nr_databases > 1) { - docstring const engine = usingBiblatex() ? _("Biblatex") : _("BibTex"); + size_t nr_databases = bibfilelist.size(); + if (nr_databases > 1 && db.empty()) { + docstring const engine = usingBiblatex() ? _("Biblatex") : _("BibTeX"); docstring message = bformat(_("The %1$s[[BibTeX/Biblatex]] inset includes %2$s databases.\n" "If you proceed, all of them will be opened."), engine, convert(nr_databases)); @@ -163,16 +160,18 @@ void InsetBibtex::editDatabases() const vector::const_iterator it = bibfilelist.begin(); vector::const_iterator en = bibfilelist.end(); for (; it != en; ++it) { - FileName const bibfile = getBibTeXPath(*it, buffer()); - formats.edit(buffer(), bibfile, - formats.getFormatFromFile(bibfile)); + if (!db.empty() && db != *it) + continue; + FileName const bibfile = buffer().getBibfilePath(*it); + theFormats().edit(buffer(), bibfile, + theFormats().getFormatFromFile(bibfile)); } } bool InsetBibtex::usingBiblatex() const { - return buffer().params().useBiblatex(); + return buffer().masterParams().useBiblatex(); } @@ -206,12 +205,12 @@ docstring InsetBibtex::toolTip(BufferView const & /*bv*/, int /*x*/, int /*y*/) style = split(style, bibtotoc, char_type(',')); } + docstring const btprint = getParam("btprint"); if (!usingBiblatex()) { tip += _("Style File:"); tip += "
  • " + (style.empty() ? _("none") : style) + "
"; tip += _("Lists:") + " "; - docstring btprint = getParam("btprint"); if (btprint == "btPrintAll") tip += _("all references"); else if (btprint == "btPrintNotCited") @@ -222,12 +221,26 @@ docstring InsetBibtex::toolTip(BufferView const & /*bv*/, int /*x*/, int /*y*/) tip += ", "; tip += _("included in TOC"); } + if (!buffer().parent() + && buffer().params().multibib == "child") { + tip += "
"; + tip += _("Note: This bibliography is not output, since bibliographies in the master file " + "are not allowed with the setting 'Multiple bibliographies per child document'"); + } } else { - if (toc) - tip += _("Included in TOC"); + tip += _("Lists:") + " "; + if (btprint == "bibbysection") + tip += _("all reference units"); + else if (btprint == "btPrintAll") + tip += _("all references"); + else + tip += _("all cited references"); + if (toc) { + tip += ", "; + tip += _("included in TOC"); + } if (!getParam("biblatexopts").empty()) { - if (toc) - tip += "
"; + tip += "
"; tip += _("Options: ") + getParam("biblatexopts"); } } @@ -250,6 +263,22 @@ void InsetBibtex::latex(otexstream & os, OutputParams const & runparams) const // 4. \end{btSect} // With Biblatex: // \printbibliography[biblatexopts] + // or + // \bibbysection[biblatexopts] - if btprint is "bibbysection" + + // chapterbib does not allow bibliographies in the master + if (!usingBiblatex() && !runparams.is_child + && buffer().params().multibib == "child") + return; + + if (runparams.inDeletedInset) { + // We cannot strike-out bibligraphies, + // so we just output a note. + os << "\\textbf{" + << buffer().B_("[BIBLIOGRAPHY DELETED!]") + << "}"; + return; + } string style = to_utf8(getParam("options")); // maybe empty! and with bibtotoc string bibtotoc; @@ -269,19 +298,25 @@ void InsetBibtex::latex(otexstream & os, OutputParams const & runparams) const docstring btprint = getParam("btprint"); if (btprint == "btPrintAll") os << "\\nocite{*}\n"; - os << "\\printbibliography"; + if (btprint == "bibbysection" && !buffer().masterParams().multibib.empty()) + os << "\\bibbysection"; + else + os << "\\printbibliography"; if (!opts.empty()) os << "[" << opts << "]"; os << "\n"; } else {// using BibTeX // Database(s) - vector const db_out = + vector> const dbs = buffer().prepareBibFilePaths(runparams, getBibFiles(), false); + vector db_out; + for (pair const & db : dbs) + db_out.push_back(db.first); // Style options if (style == "default") - style = buffer().params().defaultBiblioStyle(); - if (!style.empty() && !buffer().params().use_bibtopic) { - string base = buffer().prepareFileNameForLaTeX(style, ".bst", runparams.nice); + style = buffer().masterParams().defaultBiblioStyle(); + if (!style.empty() && !buffer().masterParams().useBibtopic()) { + string base = buffer().masterBuffer()->prepareFileNameForLaTeX(style, ".bst", runparams.nice); FileName const try_in_file = makeAbsPath(base + ".bst", buffer().filePath()); bool const not_from_texmf = try_in_file.isReadableFile(); @@ -315,8 +350,21 @@ void InsetBibtex::latex(otexstream & os, OutputParams const & runparams) const _("There are spaces in the path to your BibTeX style file.\n" "BibTeX will be unable to find it.")); } + // Encoding + bool encoding_switched = false; + Encoding const * const save_enc = runparams.encoding; + docstring const encoding = getParam("encoding"); + if (!encoding.empty() && encoding != from_ascii("default")) { + Encoding const * const enc = encodings.fromLyXName(to_ascii(encoding)); + if (enc != runparams.encoding) { + os << "\\bgroup"; + switchEncoding(os.os(), buffer().params(), runparams, *enc, true); + runparams.encoding = enc; + encoding_switched = true; + } + } // Handle the bibtopic case - if (!db_out.empty() && buffer().params().use_bibtopic) { + if (!db_out.empty() && buffer().masterParams().useBibtopic()) { os << "\\begin{btSect}"; if (!style.empty()) os << "[" << style << "]"; @@ -329,51 +377,38 @@ void InsetBibtex::latex(otexstream & os, OutputParams const & runparams) const << "\\end{btSect}\n"; } // bibtotoc option - if (!bibtotoc.empty() && !buffer().params().use_bibtopic) { + if (!bibtotoc.empty() && !buffer().masterParams().useBibtopic() + && !buffer().masterParams().documentClass().bibInToc()) { // set label for hyperref, see http://www.lyx.org/trac/ticket/6470 - if (buffer().params().pdfoptions().use_hyperref) + if (buffer().masterParams().pdfoptions().use_hyperref) os << "\\phantomsection"; - if (buffer().params().documentClass().hasLaTeXLayout("chapter")) + if (buffer().masterParams().documentClass().hasLaTeXLayout("chapter")) os << "\\addcontentsline{toc}{chapter}{\\bibname}"; - else if (buffer().params().documentClass().hasLaTeXLayout("section")) + else if (buffer().masterParams().documentClass().hasLaTeXLayout("section")) os << "\\addcontentsline{toc}{section}{\\refname}"; } // The bibliography command - if (!db_out.empty() && !buffer().params().use_bibtopic) { + if (!db_out.empty() && !buffer().masterParams().useBibtopic()) { docstring btprint = getParam("btprint"); if (btprint == "btPrintAll") { os << "\\nocite{*}\n"; } os << "\\bibliography{" << getStringFromVector(db_out) << "}\n"; } + if (encoding_switched){ + // Switch back + switchEncoding(os.os(), buffer().params(), + runparams, *save_enc, true, true); + os << "\\egroup" << breakln; + runparams.encoding = save_enc; + } } } -support::FileNamePairList InsetBibtex::getBibFiles() const +docstring_list InsetBibtex::getBibFiles() const { - FileName path(buffer().filePath()); - support::PathChanger p(path); - - // We need to store both the real FileName and the way it is entered - // (with full path, rel path or as a single file name). - // The latter is needed for biblatex's central bibfile macro. - support::FileNamePairList vec; - - vector bibfilelist = getVectorFromString(getParam("bibfiles")); - vector::const_iterator it = bibfilelist.begin(); - vector::const_iterator en = bibfilelist.end(); - for (; it != en; ++it) { - FileName const file = getBibTeXPath(*it, buffer()); - - if (!file.empty()) - vec.push_back(make_pair(*it, file)); - else - LYXERR0("Couldn't find " + to_utf8(*it) + " in InsetBibtex::getBibFiles()!"); - } - - return vec; - + return getVectorFromString(getParam("bibfiles")); } namespace { @@ -423,7 +458,7 @@ namespace { keepCase }; - /// remove whitespace characters, read characer sequence + /// remove whitespace characters, read character sequence /// not containing whitespace characters or characters in /// delimChars, and remove further whitespace characters. /// @@ -449,11 +484,9 @@ namespace { return false; // read value - bool legalChar = true; while (ifs && !isSpace(ch) && - delimChars.find(ch) == docstring::npos && - (legalChar = (illegalChars.find(ch) == docstring::npos)) - ) + delimChars.find(ch) == docstring::npos && + illegalChars.find(ch) == docstring::npos) { if (chCase == makeLowerCase) val += lowercase(ch); @@ -462,7 +495,7 @@ namespace { ifs.get(ch); } - if (!legalChar) { + if (illegalChars.find(ch) != docstring::npos) { ifs.putback(ch); return false; } @@ -482,7 +515,7 @@ namespace { /// 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 + /// @return true if reading was successful (all single parts were delimited /// correctly) bool readValue(docstring & val, ifdocstream & ifs, const VarMap & strings) { @@ -614,16 +647,16 @@ namespace { return true; } -} +} // namespace -void InsetBibtex::collectBibKeys(InsetIterator const & /*di*/) const +void InsetBibtex::collectBibKeys(InsetIterator const & /*di*/, FileNameList & checkedFiles) const { - parseBibTeXFiles(); + parseBibTeXFiles(checkedFiles); } -void InsetBibtex::parseBibTeXFiles() const +void InsetBibtex::parseBibTeXFiles(FileNameList & checkedFiles) const { // This bibtex parser is a first step to parse bibtex files // more precisely. @@ -637,27 +670,42 @@ void InsetBibtex::parseBibTeXFiles() const // 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. + // you can use any encoding as long as some elements like keys + // and names are pure ASCII. We support specifying an encoding, + // and we convert the file from that (default is 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::FileNamePairList const files = getBibFiles(); - support::FileNamePairList::const_iterator it = files.begin(); - support::FileNamePairList::const_iterator en = files.end(); - for (; it != en; ++ it) { - ifdocstream ifs(it->second.toFilesystemEncoding().c_str(), - ios_base::in, buffer().params().encoding().iconvName()); + docstring_list const files = getBibFiles(); + for (auto const & bf : files) { + FileName const bibfile = buffer().getBibfilePath(bf); + if (bibfile.empty()) { + LYXERR0("Unable to find path for " << bf << "!"); + continue; + } + if (find(checkedFiles.begin(), checkedFiles.end(), bibfile) != checkedFiles.end()) + // already checked this one. Skip. + continue; + else + // record that we check this. + checkedFiles.push_back(bibfile); + string encoding = buffer().masterParams().encoding().iconvName(); + string ienc = buffer().masterParams().bibFileEncoding(to_utf8(bf)); + if (ienc.empty() || ienc == "general") + ienc = to_ascii(params()["encoding"]); + + if (!ienc.empty() && ienc != "auto-legacy-plain" && ienc != "auto-legacy" && encodings.fromLyXName(ienc)) + encoding = encodings.fromLyXName(ienc)->iconvName(); + ifdocstream ifs(bibfile.toFilesystemEncoding().c_str(), + ios_base::in, encoding); char_type ch; VarMap strings; while (ifs) { - ifs.get(ch); if (!ifs) break; @@ -816,18 +864,6 @@ void InsetBibtex::parseBibTeXFiles() const } -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"); @@ -862,8 +898,11 @@ bool InsetBibtex::delDatabase(docstring const & db) void InsetBibtex::validate(LaTeXFeatures & features) const { - if (features.bufferParams().use_bibtopic) + BufferParams const & mparams = features.buffer().masterParams(); + if (mparams.useBibtopic()) features.require("bibtopic"); + else if (!mparams.useBiblatex() && mparams.multibib == "child") + features.require("chapterbib"); // 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. @@ -874,10 +913,72 @@ void InsetBibtex::validate(LaTeXFeatures & features) const } +void InsetBibtex::updateBuffer(ParIterator const &, UpdateType, bool const /*deleted*/) +{ + buffer().registerBibfiles(getBibFiles()); + // record encoding of bib files for biblatex + string const enc = (params()["encoding"] == from_ascii("default")) ? + string() : to_ascii(params()["encoding"]); + bool invalidate = false; + if (buffer().params().bibEncoding() != enc) { + buffer().params().setBibEncoding(enc); + invalidate = true; + } + map encs = getFileEncodings(); + map::const_iterator it = encs.begin(); + for (; it != encs.end(); ++it) { + if (buffer().params().bibFileEncoding(it->first) != it->second) { + buffer().params().setBibFileEncoding(it->first, it->second); + invalidate = true; + } + } + if (invalidate) + buffer().invalidateBibinfoCache(); +} + + +map InsetBibtex::getFileEncodings() const +{ + vector ps = + getVectorFromString(to_utf8(getParam("file_encodings")), "\t"); + std::map res; + for (string const & s: ps) { + string key; + string val = split(s, key, ' '); + res[key] = val; + } + return res; +} + + +docstring InsetBibtex::getRefLabel() const +{ + if (buffer().masterParams().documentClass().hasLaTeXLayout("chapter")) + return buffer().B_("Bibliography"); + return buffer().B_("References"); +} + + +void InsetBibtex::addToToc(DocIterator const & cpit, bool output_active, + UpdateType, TocBackend & backend) const +{ + if (!prefixIs(to_utf8(getParam("options")), "bibtotoc")) + return; + + docstring const str = getRefLabel(); + shared_ptr toc = backend.toc("tableofcontents"); + // Assign to appropriate level + int const item_depth = + (buffer().masterParams().documentClass().hasLaTeXLayout("chapter")) + ? 1 : 2; + toc->push_back(TocItem(cpit, item_depth, str, output_active)); +} + + int InsetBibtex::plaintext(odocstringstream & os, OutputParams const & op, size_t max_length) const { - docstring const reflabel = buffer().B_("References"); + docstring const reflabel = getRefLabel(); // We could output more information here, e.g., what databases are included // and information about options. But I don't necessarily see any reason to @@ -912,22 +1013,22 @@ int InsetBibtex::plaintext(odocstringstream & os, refoutput += "[" + entry.label() + "] "; // 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. + // entry, so there's a lot of repetition. This should be fixed. refoutput += bibinfo.getInfo(entry.key(), buffer(), ci) + "\n\n"; } os << refoutput; - return refoutput.size(); + return int(refoutput.size()); } // FIXME // docstring InsetBibtex::entriesAsXHTML(vector const & entries) // And then here just: entriesAsXHTML(buffer().masterBibInfo().citedEntries()) -docstring InsetBibtex::xhtml(XHTMLStream & xs, OutputParams const &) const +docstring InsetBibtex::xhtml(XMLStream & xs, OutputParams const &) const { BiblioInfo const & bibinfo = buffer().masterBibInfo(); bool const all_entries = getParam("btprint") == "btPrintAll"; - vector const & cites = + vector const & cites = all_entries ? bibinfo.getKeys() : bibinfo.citedEntries(); docstring const reflabel = buffer().B_("References"); @@ -938,10 +1039,10 @@ docstring InsetBibtex::xhtml(XHTMLStream & xs, OutputParams const &) const ci.richtext = true; ci.max_key_size = UINT_MAX; - xs << html::StartTag("h2", "class='bibtex'") + xs << xml::StartTag("h2", "class='bibtex'") << reflabel - << html::EndTag("h2") - << html::StartTag("div", "class='bibtex'"); + << xml::EndTag("h2") + << xml::StartTag("div", "class='bibtex'"); // Now we loop over the entries vector::const_iterator vit = cites.begin(); @@ -952,32 +1053,366 @@ docstring InsetBibtex::xhtml(XHTMLStream & xs, OutputParams const &) const continue; BibTeXInfo const & entry = biit->second; - string const attr = "class='bibtexentry' id='LyXCite-" - + to_utf8(html::cleanAttr(entry.key())) + "'"; - xs << html::StartTag("div", attr); - + string const attr = "class='bibtexentry' id='LyXCite-" + + to_utf8(xml::cleanAttr(entry.key())) + "'"; + xs << xml::StartTag("div", attr); + // don't print labels if we're outputting all entries if (!all_entries) { - xs << html::StartTag("span", "class='bibtexlabel'") + xs << xml::StartTag("span", "class='bibtexlabel'") << entry.label() - << html::EndTag("span"); + << xml::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 + // entry, so there's a lot of repetition. This should be fixed. + xs << xml::StartTag("span", "class='bibtexinfo'") + << XMLStream::ESCAPE_AND << bibinfo.getInfo(entry.key(), buffer(), ci) - << html::EndTag("span") - << html::EndTag("div") - << html::CR(); + << xml::EndTag("span") + << xml::EndTag("div") + << xml::CR(); } - xs << html::EndTag("div"); + xs << xml::EndTag("div"); return docstring(); } +void InsetBibtex::docbook(XMLStream & xs, OutputParams const &) const +{ + BiblioInfo const & bibinfo = buffer().masterBibInfo(); + bool const all_entries = getParam("btprint") == "btPrintAll"; + vector const & cites = + all_entries ? bibinfo.getKeys() : bibinfo.citedEntries(); + + docstring const reflabel = buffer().B_("References"); + + // Tell BiblioInfo our purpose (i.e. generate HTML rich text). + CiteItem ci; + ci.context = CiteItem::Export; + ci.richtext = true; + ci.max_key_size = UINT_MAX; + + // Header for bibliography (title required). + xs << xml::StartTag("bibliography"); + xs << xml::CR(); + xs << xml::StartTag("title"); + xs << reflabel; + xs << xml::EndTag("title") << xml::CR(); + + // Translation between keys in each entry and DocBook tags. + // IDs for publications; list: http://tdg.docbook.org/tdg/5.2/biblioid.html. + vector> biblioId = { // + make_pair("doi", "doi"), + make_pair("isbn", "isbn"), + make_pair("issn", "issn"), + make_pair("isrn", "isrn"), + make_pair("istc", "istc"), + make_pair("lccn", "libraryofcongress"), + make_pair("number", "pubsnumber"), + make_pair("url", "uri") + }; + // Relations between documents. + vector> relations = { // + make_pair("journal", "journal"), + make_pair("booktitle", "book"), + make_pair("series", "series") + }; + // Various things that do not fit DocBook. + vector misc = { "language", "school", "note" }; + + // Store the mapping between BibTeX and DocBook. + map toDocBookTag; + toDocBookTag["fullnames:author"] = "SPECIFIC"; // No direct translation to DocBook: . + toDocBookTag["publisher"] = "SPECIFIC"; // No direct translation to DocBook: . + toDocBookTag["address"] = "SPECIFIC"; // No direct translation to DocBook: . + toDocBookTag["editor"] = "editor"; + toDocBookTag["institution"] = "SPECIFIC"; // No direct translation to DocBook: . + + toDocBookTag["title"] = "title"; + toDocBookTag["volume"] = "volumenum"; + toDocBookTag["edition"] = "edition"; + toDocBookTag["pages"] = "artpagenums"; + + toDocBookTag["abstract"] = "SPECIFIC"; // No direct translation to DocBook: . + toDocBookTag["keywords"] = "SPECIFIC"; // No direct translation to DocBook: . + toDocBookTag["year"] = "SPECIFIC"; // No direct translation to DocBook: . + toDocBookTag["month"] = "SPECIFIC"; // No direct translation to DocBook: . + + toDocBookTag["journal"] = "SPECIFIC"; // No direct translation to DocBook: . + toDocBookTag["booktitle"] = "SPECIFIC"; // No direct translation to DocBook: . + toDocBookTag["series"] = "SPECIFIC"; // No direct translation to DocBook: . + + for (auto const & id: biblioId) + toDocBookTag[id.first] = "SPECIFIC"; // No direct translation to DocBook: . + for (auto const & id: relations) + toDocBookTag[id.first] = "SPECIFIC"; // No direct translation to DocBook: . + for (auto const & id: misc) + toDocBookTag[id] = "SPECIFIC"; // No direct translation to DocBook: . + + // Loop over the entries. If there are no entries, add a comment to say so. + auto vit = cites.begin(); + auto ven = cites.end(); + + if (vit == ven) { + xs << XMLStream::ESCAPE_NONE << ""; + } + + for (; vit != ven; ++vit) { + BiblioInfo::const_iterator const biit = bibinfo.find(*vit); + if (biit == bibinfo.end()) + continue; + + BibTeXInfo const & entry = biit->second; + string const attr = "xml:id=\"" + to_utf8(xml::cleanID(entry.key())) + "\""; + xs << xml::StartTag("biblioentry", attr); + xs << xml::CR(); + + // 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 repetition. This should be fixed. + + // Parse the results of getInfo and emit the corresponding DocBook tags. Interesting pieces have the form + // "STH", the rest of the text may be discarded. + // Could have written a DocBook version of expandFormat (that parses a citation into HTML), but it implements + // some kind of recursion. Still, a (static) conversion step between the citation format and DocBook would have + // been required. All in all, both codes approaches would have been similar, but this parsing allows relying + // on existing building blocks. + + string html = to_utf8(bibinfo.getInfo(entry.key(), buffer(), ci)); + regex tagRegex("([^<]*)"); + smatch match; + auto tagIt = lyx::sregex_iterator(html.cbegin(), html.cend(), tagRegex, regex_constants::match_default); + auto tagEnd = lyx::sregex_iterator(); + map delayedTags; + + // Read all tags from HTML and convert those that have a 1:1 matching. + while (tagIt != tagEnd) { + string tag = tagIt->str(); // regex_match cannot work with temporary strings. + ++tagIt; + + if (regex_match(tag, match, tagRegex)) { + if (toDocBookTag[match[1]] == "SPECIFIC") { + delayedTags[match[1]] = match[2]; + } else { + xs << xml::StartTag(toDocBookTag[match[1]]); + xs << from_utf8(match[2].str()); + xs << xml::EndTag(toDocBookTag[match[1]]); + } + } else { + LYXERR0("The BibTeX field " << match[1].str() << " is unknown."); + xs << XMLStream::ESCAPE_NONE << from_utf8("\n"); + } + } + + // Type of document (book, journal paper, etc.). + xs << xml::StartTag("bibliomisc", "role=\"type\""); + xs << entry.entryType(); + xs << xml::EndTag("bibliomisc"); + xs << xml::CR(); + + // Handle tags that have complex transformations. + if (! delayedTags.empty()) { + unsigned long remainingTags = delayedTags.size(); // Used as a workaround. With GCC 7, when erasing all + // elements one by one, some elements may still pop in later on (even though they were deleted previously). + auto hasTag = [&delayedTags](string key) { return delayedTags.find(key) != delayedTags.end(); }; + auto getTag = [&delayedTags](string key) { return from_utf8(delayedTags[key]); }; + auto eraseTag = [&delayedTags, &remainingTags](string key) { + remainingTags -= 1; + delayedTags.erase(key); + }; + + // Notes on order of checks. + // - address goes with publisher if there is one, so check this first. Otherwise, the address goes with + // the entry without other details. + + // + if (hasTag("publisher")) { + xs << xml::StartTag("publisher"); + xs << xml::CR(); + xs << xml::StartTag("publishername"); + xs << getTag("publisher"); + xs << xml::EndTag("publishername"); + xs << xml::CR(); + + if (hasTag("address")) { + xs << xml::StartTag("address"); + xs << getTag("address"); + xs << xml::EndTag("address"); + eraseTag("address"); + } + + xs << xml::EndTag("publisher"); + xs << xml::CR(); + eraseTag("publisher"); + } + + if (hasTag("address")) { + xs << xml::StartTag("address"); + xs << getTag("address"); + xs << xml::EndTag("address"); + eraseTag("address"); + } + + // + if (hasTag("keywords")) { + // Split the keywords on comma. + docstring keywordSet = getTag("keywords"); + vector keywords; + if (keywordSet.find(from_utf8(",")) == string::npos) { + keywords = { keywordSet }; + } else { + size_t pos = 0; + while ((pos = keywordSet.find(from_utf8(","))) != string::npos) { + keywords.push_back(keywordSet.substr(0, pos)); + keywordSet.erase(0, pos + 1); + } + keywords.push_back(keywordSet); + } + + xs << xml::StartTag("keywordset") << xml::CR(); + for (auto & kw: keywords) { + kw.erase(kw.begin(), std::find_if(kw.begin(), kw.end(), + [](int c) {return !std::isspace(c);})); + xs << xml::StartTag("keyword"); + xs << kw; + xs << xml::EndTag("keyword"); + xs << xml::CR(); + } + xs << xml::EndTag("keywordset") << xml::CR(); + eraseTag("keywords"); + } + + // + // Example: http://tdg.docbook.org/tdg/5.1/biblioset.html + if (hasTag("year")) { + docstring value = getTag("year"); + eraseTag("year"); + + // Follow xsd:gYearMonth format (http://books.xmlschemata.org/relaxng/ch19-77135.html). + if (hasTag("month")) { + value += "-" + getTag("month"); + eraseTag("month"); + } + + xs << xml::StartTag("pubdate"); + xs << value; + xs << xml::EndTag("pubdate"); + xs << xml::CR(); + } + + // + if (hasTag("institution")) { + xs << xml::StartTag("org"); + xs << xml::CR(); + xs << xml::StartTag("orgname"); + xs << getTag("institution"); + xs << xml::EndTag("orgname"); + xs << xml::CR(); + xs << xml::EndTag("org"); + xs << xml::CR(); + eraseTag("institution"); + } + + // + // Example: http://tdg.docbook.org/tdg/5.1/biblioset.html + for (auto const & id: relations) { + if (hasTag(id.first)) { + xs << xml::StartTag("biblioset", "relation=\"" + id.second + "\""); + xs << xml::CR(); + xs << xml::StartTag("title"); + xs << getTag(id.first); + xs << xml::EndTag("title"); + xs << xml::CR(); + xs << xml::EndTag("biblioset"); + xs << xml::CR(); + eraseTag(id.first); + } + } + + // + // Example: http://tdg.docbook.org/tdg/5.1/authorgroup.html + if (hasTag("fullnames:author")) { + // Perform full parsing of the BibTeX string, dealing with the many corner cases that might + // be encountered. + authorsToDocBookAuthorGroup(getTag("fullnames:author"), xs, buffer()); + eraseTag("fullnames:author"); + } + + // + if (hasTag("abstract")) { + // Split the paragraphs on new line. + docstring abstract = getTag("abstract"); + vector paragraphs; + if (abstract.find(from_utf8("\n")) == string::npos) { + paragraphs = { abstract }; + } else { + size_t pos = 0; + while ((pos = abstract.find(from_utf8(","))) != string::npos) { + paragraphs.push_back(abstract.substr(0, pos)); + abstract.erase(0, pos + 1); + } + paragraphs.push_back(abstract); + } + + xs << xml::StartTag("abstract"); + xs << xml::CR(); + for (auto const & para: paragraphs) { + if (para.empty()) + continue; + xs << xml::StartTag("para"); + xs << para; + xs << xml::EndTag("para"); + } + xs << xml::CR(); + xs << xml::EndTag("abstract"); + xs << xml::CR(); + eraseTag("abstract"); + } + + // + for (auto const & id: biblioId) { + if (hasTag(id.first)) { + xs << xml::StartTag("biblioid", "class=\"" + id.second + "\""); + xs << getTag(id.first); + xs << xml::EndTag("biblioid"); + xs << xml::CR(); + eraseTag(id.first); + } + } + + // + for (auto const & id: misc) { + if (hasTag(id)) { + xs << xml::StartTag("bibliomisc", "role=\"" + id + "\""); + xs << getTag(id); + xs << xml::EndTag("bibliomisc"); + xs << xml::CR(); + eraseTag(id); + } + } + + // After all tags are processed, check for errors. + if (remainingTags > 0) { + LYXERR0("Still delayed tags not yet handled."); + xs << XMLStream::ESCAPE_NONE << from_utf8("\n"); + } + } + + xs << xml::EndTag("biblioentry"); + xs << xml::CR(); + } + + // Footer for bibliography. + xs << xml::EndTag("bibliography"); +} + + void InsetBibtex::write(ostream & os) const { params().Write(os, &buffer());