2 * \file InsetBibtex.cpp
3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Alejandro Aguilar Sierra
7 * \author Richard Heck (BibTeX parser improvements)
8 * \author Jürgen Spitzmüller
10 * Full author contact details are available in file CREDITS.
15 #include "InsetBibtex.h"
17 #include "BiblioInfo.h"
19 #include "BufferParams.h"
20 #include "CiteEnginesList.h"
22 #include "DispatchResult.h"
26 #include "FuncRequest.h"
27 #include "FuncStatus.h"
28 #include "LaTeXFeatures.h"
29 #include "output_latex.h"
31 #include "OutputParams.h"
32 #include "PDFOptions.h"
33 #include "texstream.h"
34 #include "TextClass.h"
35 #include "TocBackend.h"
37 #include "frontends/alert.h"
39 #include "support/convert.h"
40 #include "support/debug.h"
41 #include "support/docstream.h"
42 #include "support/docstring_list.h"
43 #include "support/ExceptionMessage.h"
44 #include "support/FileNameList.h"
45 #include "support/filetools.h"
46 #include "support/regex.h"
47 #include "support/gettext.h"
48 #include "support/lstrings.h"
49 #include "support/os.h"
50 #include "support/PathChanger.h"
51 #include "support/textutils.h"
60 using namespace lyx::support;
64 namespace Alert = frontend::Alert;
65 namespace os = support::os;
68 InsetBibtex::InsetBibtex(Buffer * buf, InsetCommandParams const & p)
69 : InsetCommand(buf, p)
73 ParamInfo const & InsetBibtex::findInfo(string const & /* cmdName */)
75 static ParamInfo param_info_;
76 if (param_info_.empty()) {
77 param_info_.add("btprint", ParamInfo::LATEX_OPTIONAL);
78 param_info_.add("bibfiles", ParamInfo::LATEX_REQUIRED);
79 param_info_.add("options", ParamInfo::LYX_INTERNAL);
80 param_info_.add("encoding", ParamInfo::LYX_INTERNAL);
81 param_info_.add("file_encodings", ParamInfo::LYX_INTERNAL);
82 param_info_.add("biblatexopts", ParamInfo::LATEX_OPTIONAL);
88 void InsetBibtex::doDispatch(Cursor & cur, FuncRequest & cmd)
90 switch (cmd.action()) {
93 editDatabases(cmd.argument());
96 case LFUN_INSET_MODIFY: {
97 InsetCommandParams p(BIBTEX_CODE);
99 if (!InsetCommand::string2params(to_utf8(cmd.argument()), p)) {
100 cur.noScreenUpdate();
103 } catch (ExceptionMessage const & message) {
104 if (message.type_ == WarningException) {
105 Alert::warning(message.title_, message.details_);
106 cur.noScreenUpdate();
114 cur.buffer()->clearBibFileCache();
115 cur.forceBufferUpdate();
120 InsetCommand::doDispatch(cur, cmd);
126 bool InsetBibtex::getStatus(Cursor & cur, FuncRequest const & cmd,
127 FuncStatus & flag) const
129 switch (cmd.action()) {
130 case LFUN_INSET_EDIT:
131 flag.setEnabled(true);
135 return InsetCommand::getStatus(cur, cmd, flag);
140 void InsetBibtex::editDatabases(docstring const & db) const
142 vector<docstring> bibfilelist = getVectorFromString(getParam("bibfiles"));
144 if (bibfilelist.empty())
147 size_t nr_databases = bibfilelist.size();
148 if (nr_databases > 1 && db.empty()) {
149 docstring const engine = usingBiblatex() ? _("Biblatex") : _("BibTeX");
150 docstring message = bformat(_("The %1$s[[BibTeX/Biblatex]] inset includes %2$s databases.\n"
151 "If you proceed, all of them will be opened."),
152 engine, convert<docstring>(nr_databases));
153 int const ret = Alert::prompt(_("Open Databases?"),
154 message, 0, 1, _("&Cancel"), _("&Proceed"));
160 vector<docstring>::const_iterator it = bibfilelist.begin();
161 vector<docstring>::const_iterator en = bibfilelist.end();
162 for (; it != en; ++it) {
163 if (!db.empty() && db != *it)
165 FileName const bibfile = buffer().getBibfilePath(*it);
166 theFormats().edit(buffer(), bibfile,
167 theFormats().getFormatFromFile(bibfile));
172 bool InsetBibtex::usingBiblatex() const
174 return buffer().masterParams().useBiblatex();
178 docstring InsetBibtex::screenLabel() const
180 return usingBiblatex() ? _("Biblatex Generated Bibliography")
181 : _("BibTeX Generated Bibliography");
185 docstring InsetBibtex::toolTip(BufferView const & /*bv*/, int /*x*/, int /*y*/) const
187 docstring tip = _("Databases:");
188 vector<docstring> bibfilelist = getVectorFromString(getParam("bibfiles"));
191 if (bibfilelist.empty())
192 tip += "<li>" + _("none") + "</li>";
194 for (docstring const & bibfile : bibfilelist)
195 tip += "<li>" + bibfile + "</li>";
200 docstring style = getParam("options"); // maybe empty! and with bibtotoc
201 docstring bibtotoc = from_ascii("bibtotoc");
202 if (prefixIs(style, bibtotoc)) {
204 if (contains(style, char_type(',')))
205 style = split(style, bibtotoc, char_type(','));
208 docstring const btprint = getParam("btprint");
209 if (!usingBiblatex()) {
210 tip += _("Style File:");
211 tip += "<ul><li>" + (style.empty() ? _("none") : style) + "</li></ul>";
213 tip += _("Lists:") + " ";
214 if (btprint == "btPrintAll")
215 tip += _("all references");
216 else if (btprint == "btPrintNotCited")
217 tip += _("all uncited references");
219 tip += _("all cited references");
222 tip += _("included in TOC");
224 if (!buffer().parent()
225 && buffer().params().multibib == "child") {
227 tip += _("Note: This bibliography is not output, since bibliographies in the master file "
228 "are not allowed with the setting 'Multiple bibliographies per child document'");
231 tip += _("Lists:") + " ";
232 if (btprint == "bibbysection")
233 tip += _("all reference units");
234 else if (btprint == "btPrintAll")
235 tip += _("all references");
237 tip += _("all cited references");
240 tip += _("included in TOC");
242 if (!getParam("biblatexopts").empty()) {
244 tip += _("Options: ") + getParam("biblatexopts");
252 void InsetBibtex::latex(otexstream & os, OutputParams const & runparams) const
254 // The sequence of the commands:
255 // With normal BibTeX:
256 // 1. \bibliographystyle{style}
257 // 2. \addcontentsline{...} - if option bibtotoc set
258 // 3. \bibliography{database}
260 // 1. \bibliographystyle{style}
261 // 2. \begin{btSect}{database}
262 // 3. \btPrint{Cited|NotCited|All}
265 // \printbibliography[biblatexopts]
267 // \bibbysection[biblatexopts] - if btprint is "bibbysection"
269 // chapterbib does not allow bibliographies in the master
270 if (!usingBiblatex() && !runparams.is_child
271 && buffer().params().multibib == "child")
274 if (runparams.inDeletedInset) {
275 // We cannot strike-out bibligraphies,
276 // so we just output a note.
278 << buffer().B_("[BIBLIOGRAPHY DELETED!]")
283 string style = to_utf8(getParam("options")); // maybe empty! and with bibtotoc
285 if (prefixIs(style, "bibtotoc")) {
286 bibtotoc = "bibtotoc";
287 if (contains(style, ','))
288 style = split(style, bibtotoc, ',');
291 if (usingBiblatex()) {
293 string opts = to_utf8(getParam("biblatexopts"));
295 if (!bibtotoc.empty())
296 opts = opts.empty() ? "heading=bibintoc" : "heading=bibintoc," + opts;
297 // The bibliography command
298 docstring btprint = getParam("btprint");
299 if (btprint == "btPrintAll")
300 os << "\\nocite{*}\n";
301 if (btprint == "bibbysection" && !buffer().masterParams().multibib.empty())
302 os << "\\bibbysection";
304 os << "\\printbibliography";
306 os << "[" << opts << "]";
308 } else {// using BibTeX
310 vector<pair<docstring, string>> const dbs =
311 buffer().prepareBibFilePaths(runparams, getBibFiles(), false);
312 vector<docstring> db_out;
313 db_out.reserve(dbs.size());
314 for (pair<docstring, string> const & db : dbs)
315 db_out.push_back(db.first);
317 if (style == "default")
318 style = buffer().masterParams().defaultBiblioStyle();
319 if (!style.empty() && !buffer().masterParams().useBibtopic()) {
320 string base = buffer().masterBuffer()->prepareFileNameForLaTeX(style, ".bst", runparams.nice);
321 FileName const try_in_file =
322 makeAbsPath(base + ".bst", buffer().filePath());
323 bool const not_from_texmf = try_in_file.isReadableFile();
324 // If this style does not come from texmf and we are not
325 // exporting to .tex copy it to the tmp directory.
326 // This prevents problems with spaces and 8bit characters
328 if (!runparams.inComment && !runparams.dryrun && !runparams.nice &&
330 // use new style name
331 DocFileName const in_file = DocFileName(try_in_file);
332 base = removeExtension(in_file.mangledFileName());
333 FileName const out_file = makeAbsPath(base + ".bst",
334 buffer().masterBuffer()->temppath());
335 bool const success = in_file.copyTo(out_file);
337 LYXERR0("Failed to copy '" << in_file
338 << "' to '" << out_file << "'");
342 os << "\\bibliographystyle{"
343 << from_utf8(latex_path(buffer().prepareFileNameForLaTeX(base, ".bst", runparams.nice)))
346 // Warn about spaces in bst path. Warn only once.
347 static bool warned_about_bst_spaces = false;
348 if (!warned_about_bst_spaces && runparams.nice && contains(style, ' ')) {
349 warned_about_bst_spaces = true;
350 Alert::warning(_("Export Warning!"),
351 _("There are spaces in the path to your BibTeX style file.\n"
352 "BibTeX will be unable to find it."));
355 bool encoding_switched = false;
356 Encoding const * const save_enc = runparams.encoding;
357 docstring const encoding = getParam("encoding");
358 if (!encoding.empty() && encoding != from_ascii("default")) {
359 Encoding const * const enc = encodings.fromLyXName(to_ascii(encoding));
360 if (enc != runparams.encoding) {
362 switchEncoding(os.os(), buffer().params(), runparams, *enc, true);
363 runparams.encoding = enc;
364 encoding_switched = true;
367 // Handle the bibtopic case
368 if (!db_out.empty() && buffer().masterParams().useBibtopic()) {
369 os << "\\begin{btSect}";
371 os << "[" << style << "]";
372 os << "{" << getStringFromVector(db_out) << "}\n";
373 docstring btprint = getParam("btprint");
376 btprint = from_ascii("btPrintCited");
377 os << "\\" << btprint << "\n"
378 << "\\end{btSect}\n";
381 if (!bibtotoc.empty() && !buffer().masterParams().useBibtopic()
382 && !buffer().masterParams().documentClass().bibInToc()) {
383 // set label for hyperref, see http://www.lyx.org/trac/ticket/6470
384 if (buffer().masterParams().pdfoptions().use_hyperref)
385 os << "\\phantomsection";
386 if (buffer().masterParams().documentClass().hasLaTeXLayout("chapter"))
387 os << "\\addcontentsline{toc}{chapter}{\\bibname}";
388 else if (buffer().masterParams().documentClass().hasLaTeXLayout("section"))
389 os << "\\addcontentsline{toc}{section}{\\refname}";
391 // The bibliography command
392 if (!db_out.empty() && !buffer().masterParams().useBibtopic()) {
393 docstring btprint = getParam("btprint");
394 if (btprint == "btPrintAll") {
395 os << "\\nocite{*}\n";
397 os << "\\bibliography{" << getStringFromVector(db_out) << "}\n";
399 if (encoding_switched){
401 switchEncoding(os.os(), buffer().params(),
402 runparams, *save_enc, true, true);
403 os << "\\egroup" << breakln;
404 runparams.encoding = save_enc;
410 docstring_list InsetBibtex::getBibFiles() const
412 return getVectorFromString(getParam("bibfiles"));
417 // methods for parsing bibtex files
419 typedef map<docstring, docstring> VarMap;
421 /// remove whitespace characters, optionally a single comma,
422 /// and further whitespace characters from the stream.
423 /// @return true if a comma was found, false otherwise
425 bool removeWSAndComma(ifdocstream & ifs) {
434 } while (ifs && isSpace(ch));
447 } while (ifs && isSpace(ch));
462 /// remove whitespace characters, read character sequence
463 /// not containing whitespace characters or characters in
464 /// delimChars, and remove further whitespace characters.
466 /// @return true if a string of length > 0 could be read.
468 bool readTypeOrKey(docstring & val, ifdocstream & ifs,
469 docstring const & delimChars, docstring const & illegalChars,
482 } while (ifs && isSpace(ch));
488 while (ifs && !isSpace(ch) &&
489 delimChars.find(ch) == docstring::npos &&
490 illegalChars.find(ch) == docstring::npos)
492 if (chCase == makeLowerCase)
493 val += lowercase(ch);
499 if (illegalChars.find(ch) != docstring::npos) {
505 while (ifs && isSpace(ch)) {
513 return val.length() > 0;
516 /// read subsequent bibtex values that are delimited with a #-character.
517 /// Concatenate all parts and replace names with the associated string in
518 /// the variable strings.
519 /// @return true if reading was successful (all single parts were delimited
521 bool readValue(docstring & val, ifdocstream & ifs, const VarMap & strings) {
534 } while (ifs && isSpace(ch));
539 // check for field type
540 if (isDigitASCII(ch)) {
542 // read integer value
546 } while (ifs && isDigitASCII(ch));
551 } else if (ch == '"' || ch == '{') {
553 char_type delim = ch == '"' ? '"': '}';
558 } while (ifs && isSpace(ch));
563 // We now have the first non-whitespace character
564 // We'll collapse adjacent whitespace.
565 bool lastWasWhiteSpace = false;
567 // inside this delimited text braces must match.
568 // Thus we can have a closing delimiter only
569 // when nestLevel == 0
572 while (ifs && (nestLevel > 0 || ch != delim)) {
574 lastWasWhiteSpace = true;
578 // We output the space only after we stop getting
579 // whitespace so as not to output any whitespace
580 // at the end of the value.
581 if (lastWasWhiteSpace) {
582 lastWasWhiteSpace = false;
588 // update nesting level
607 // FIXME Why is this here?
615 // reading a string name
618 while (ifs && !isSpace(ch) && ch != '#' && ch != ',' && ch != '}' && ch != ')') {
619 strName += lowercase(ch);
626 // replace the string with its assigned value or
627 // discard it if it's not assigned
628 if (strName.length()) {
629 VarMap::const_iterator pos = strings.find(strName);
630 if (pos != strings.end()) {
637 while (ifs && isSpace(ch)) {
644 // continue reading next value on concatenate with '#'
654 void InsetBibtex::collectBibKeys(InsetIterator const & /*di*/, FileNameList & checkedFiles) const
656 parseBibTeXFiles(checkedFiles);
660 void InsetBibtex::parseBibTeXFiles(FileNameList & checkedFiles) const
662 // This bibtex parser is a first step to parse bibtex files
665 // - it reads the whole bibtex entry and does a syntax check
666 // (matching delimiters, missing commas,...
667 // - it recovers from errors starting with the next @-character
668 // - it reads @string definitions and replaces them in the
670 // - it accepts more characters in keys or value names than
673 // Officially bibtex does only support ASCII, but in practice
674 // you can use any encoding as long as some elements like keys
675 // and names are pure ASCII. We support specifying an encoding,
676 // and we convert the file from that (default is buffer encoding).
677 // We don't restrict keys to ASCII in LyX, since our own
678 // InsetBibitem can generate non-ASCII keys, and nonstandard
679 // 8bit clean bibtex forks exist.
683 docstring_list const files = getBibFiles();
684 for (auto const & bf : files) {
685 FileName const bibfile = buffer().getBibfilePath(bf);
686 if (bibfile.empty()) {
687 LYXERR0("Unable to find path for " << bf << "!");
690 if (find(checkedFiles.begin(), checkedFiles.end(), bibfile) != checkedFiles.end())
691 // already checked this one. Skip.
694 // record that we check this.
695 checkedFiles.push_back(bibfile);
696 string encoding = buffer().masterParams().encoding().iconvName();
697 string ienc = buffer().masterParams().bibFileEncoding(to_utf8(bf));
698 if (ienc.empty() || ienc == "general")
699 ienc = to_ascii(params()["encoding"]);
701 if (!ienc.empty() && ienc != "auto-legacy-plain" && ienc != "auto-legacy" && encodings.fromLyXName(ienc))
702 encoding = encodings.fromLyXName(ienc)->iconvName();
703 ifdocstream ifs(bibfile.toFilesystemEncoding().c_str(),
704 ios_base::in, encoding);
719 if (!readTypeOrKey(entryType, ifs, from_ascii("{("), docstring(), makeLowerCase)) {
720 lyxerr << "BibTeX Parser: Error reading entry type." << std::endl;
725 lyxerr << "BibTeX Parser: Unexpected end of file." << std::endl;
729 if (entryType == from_ascii("comment")) {
730 ifs.ignore(numeric_limits<int>::max(), '\n');
736 lyxerr << "BibTeX Parser: Unexpected end of file." << std::endl;
740 if ((ch != '(') && (ch != '{')) {
741 lyxerr << "BibTeX Parser: Invalid entry delimiter." << std::endl;
747 if (entryType == from_ascii("string")) {
749 // read string and add it to the strings map
750 // (or replace it's old value)
754 if (!readTypeOrKey(name, ifs, from_ascii("="), from_ascii("#{}(),"), makeLowerCase)) {
755 lyxerr << "BibTeX Parser: Error reading string name." << std::endl;
760 lyxerr << "BibTeX Parser: Unexpected end of file." << std::endl;
764 // next char must be an equal sign
766 if (!ifs || ch != '=') {
767 lyxerr << "BibTeX Parser: No `=' after string name: " <<
768 name << "." << std::endl;
772 if (!readValue(value, ifs, strings)) {
773 lyxerr << "BibTeX Parser: Unable to read value for string: " <<
774 name << "." << std::endl;
778 strings[name] = value;
780 } else if (entryType == from_ascii("preamble")) {
782 // preamble definitions are discarded.
783 // can they be of any use in lyx?
786 if (!readValue(value, ifs, strings)) {
787 lyxerr << "BibTeX Parser: Unable to read preamble value." << std::endl;
793 // Citation entry. Try to read the key.
796 if (!readTypeOrKey(key, ifs, from_ascii(","), from_ascii("}"), keepCase)) {
797 lyxerr << "BibTeX Parser: Unable to read key for entry type:" <<
798 entryType << "." << std::endl;
803 lyxerr << "BibTeX Parser: Unexpected end of file." << std::endl;
807 /////////////////////////////////////////////
808 // now we have a key, so we will add an entry
809 // (even if it's empty, as bibtex does)
811 // we now read the field = value pairs.
812 // all items must be separated by a comma. If
813 // it is missing the scanning of this entry is
814 // stopped and the next is searched.
818 BibTeXInfo keyvalmap(key, entryType);
820 bool readNext = removeWSAndComma(ifs);
822 while (ifs && readNext) {
825 if (!readTypeOrKey(name, ifs, from_ascii("="),
826 from_ascii("{}(),"), makeLowerCase) || !ifs)
829 // next char must be an equal sign
830 // FIXME Whitespace??
833 lyxerr << "BibTeX Parser: Unexpected end of file." << std::endl;
837 lyxerr << "BibTeX Parser: Missing `=' after field name: " <<
838 name << ", for key: " << key << "." << std::endl;
844 if (!readValue(value, ifs, strings)) {
845 lyxerr << "BibTeX Parser: Unable to read value for field: " <<
846 name << ", for key: " << key << "." << std::endl;
850 keyvalmap[name] = value;
851 data += "\n\n" + value;
852 keylist.addFieldName(name);
853 readNext = removeWSAndComma(ifs);
857 keylist.addEntryType(entryType);
858 keyvalmap.setAllData(data);
859 keylist[key] = keyvalmap;
860 } //< else (citation entry)
862 } //< for loop over files
864 buffer().addBiblioInfo(keylist);
868 bool InsetBibtex::addDatabase(docstring const & db)
870 docstring bibfiles = getParam("bibfiles");
871 if (tokenPos(bibfiles, ',', db) != -1)
873 if (!bibfiles.empty())
875 setParam("bibfiles", bibfiles + db);
880 bool InsetBibtex::delDatabase(docstring const & db)
882 docstring bibfiles = getParam("bibfiles");
883 if (contains(bibfiles, db)) {
884 int const n = tokenPos(bibfiles, ',', db);
887 // this is not the first database
888 docstring tmp = ',' + bd;
889 setParam("bibfiles", subst(bibfiles, tmp, docstring()));
891 // this is the first (or only) database
892 setParam("bibfiles", split(bibfiles, bd, ','));
900 void InsetBibtex::validate(LaTeXFeatures & features) const
902 BufferParams const & mparams = features.buffer().masterParams();
903 if (mparams.useBibtopic())
904 features.require("bibtopic");
905 else if (!mparams.useBiblatex() && mparams.multibib == "child")
906 features.require("chapterbib");
908 // It'd be better to be able to get this from an InsetLayout, but at present
909 // InsetLayouts do not seem really to work for things that aren't InsetTexts.
910 if (features.runparams().flavor == OutputParams::HTML)
911 features.addCSSSnippet("div.bibtexentry { margin-left: 2em; text-indent: -2em; }\n"
912 "span.bibtexlabel:before{ content: \"[\"; }\n"
913 "span.bibtexlabel:after{ content: \"] \"; }");
917 void InsetBibtex::updateBuffer(ParIterator const &, UpdateType, bool const /*deleted*/)
919 buffer().registerBibfiles(getBibFiles());
920 // record encoding of bib files for biblatex
921 string const enc = (params()["encoding"] == from_ascii("default")) ?
922 string() : to_ascii(params()["encoding"]);
923 bool invalidate = false;
924 if (buffer().params().bibEncoding() != enc) {
925 buffer().params().setBibEncoding(enc);
928 map<string, string> encs = getFileEncodings();
929 map<string, string>::const_iterator it = encs.begin();
930 for (; it != encs.end(); ++it) {
931 if (buffer().params().bibFileEncoding(it->first) != it->second) {
932 buffer().params().setBibFileEncoding(it->first, it->second);
937 buffer().invalidateBibinfoCache();
941 map<string, string> InsetBibtex::getFileEncodings() const
944 getVectorFromString(to_utf8(getParam("file_encodings")), "\t");
945 std::map<string, string> res;
946 for (string const & s: ps) {
948 string val = split(s, key, ' ');
955 docstring InsetBibtex::getRefLabel() const
957 if (buffer().masterParams().documentClass().hasLaTeXLayout("chapter"))
958 return buffer().B_("Bibliography");
959 return buffer().B_("References");
963 void InsetBibtex::addToToc(DocIterator const & cpit, bool output_active,
964 UpdateType, TocBackend & backend) const
966 if (!prefixIs(to_utf8(getParam("options")), "bibtotoc"))
969 docstring const str = getRefLabel();
970 shared_ptr<Toc> toc = backend.toc("tableofcontents");
971 // Assign to appropriate level
972 int const item_depth =
973 (buffer().masterParams().documentClass().hasLaTeXLayout("chapter"))
975 toc->push_back(TocItem(cpit, item_depth, str, output_active));
979 int InsetBibtex::plaintext(odocstringstream & os,
980 OutputParams const & op, size_t max_length) const
982 docstring const reflabel = getRefLabel();
984 // We could output more information here, e.g., what databases are included
985 // and information about options. But I don't necessarily see any reason to
986 // do this right now.
987 if (op.for_tooltip || op.for_toc || op.for_search) {
988 os << '[' << reflabel << ']' << '\n';
989 return PLAINTEXT_NEWLINE;
992 BiblioInfo bibinfo = buffer().masterBibInfo();
993 bibinfo.makeCitationLabels(buffer());
994 vector<docstring> const & cites = bibinfo.citedEntries();
996 size_t start_size = os.str().size();
998 refoutput += reflabel + "\n\n";
1000 // Tell BiblioInfo our purpose
1002 ci.context = CiteItem::Export;
1004 // Now we loop over the entries
1005 vector<docstring>::const_iterator vit = cites.begin();
1006 vector<docstring>::const_iterator const ven = cites.end();
1007 for (; vit != ven; ++vit) {
1008 if (start_size + refoutput.size() >= max_length)
1010 BiblioInfo::const_iterator const biit = bibinfo.find(*vit);
1011 if (biit == bibinfo.end())
1013 BibTeXInfo const & entry = biit->second;
1014 refoutput += "[" + entry.label() + "] ";
1015 // FIXME Right now, we are calling BibInfo::getInfo on the key,
1016 // which will give us all the cross-referenced info. But for every
1017 // entry, so there's a lot of repetition. This should be fixed.
1018 refoutput += bibinfo.getInfo(entry.key(), buffer(), ci) + "\n\n";
1021 return int(refoutput.size());
1026 // docstring InsetBibtex::entriesAsXHTML(vector<docstring> const & entries)
1027 // And then here just: entriesAsXHTML(buffer().masterBibInfo().citedEntries())
1028 docstring InsetBibtex::xhtml(XMLStream & xs, OutputParams const &) const
1030 BiblioInfo const & bibinfo = buffer().masterBibInfo();
1031 bool const all_entries = getParam("btprint") == "btPrintAll";
1032 vector<docstring> const & cites =
1033 all_entries ? bibinfo.getKeys() : bibinfo.citedEntries();
1035 docstring const reflabel = buffer().B_("References");
1037 // tell BiblioInfo our purpose
1039 ci.context = CiteItem::Export;
1041 ci.max_key_size = UINT_MAX;
1043 xs << xml::StartTag("h2", "class='bibtex'")
1045 << xml::EndTag("h2")
1046 << xml::StartTag("div", "class='bibtex'");
1048 // Now we loop over the entries
1049 vector<docstring>::const_iterator vit = cites.begin();
1050 vector<docstring>::const_iterator const ven = cites.end();
1051 for (; vit != ven; ++vit) {
1052 BiblioInfo::const_iterator const biit = bibinfo.find(*vit);
1053 if (biit == bibinfo.end())
1056 BibTeXInfo const & entry = biit->second;
1057 string const attr = "class='bibtexentry' id='LyXCite-"
1058 + to_utf8(xml::cleanAttr(entry.key())) + "'";
1059 xs << xml::StartTag("div", attr);
1061 // don't print labels if we're outputting all entries
1063 xs << xml::StartTag("span", "class='bibtexlabel'")
1065 << xml::EndTag("span");
1068 // FIXME Right now, we are calling BibInfo::getInfo on the key,
1069 // which will give us all the cross-referenced info. But for every
1070 // entry, so there's a lot of repetition. This should be fixed.
1071 xs << xml::StartTag("span", "class='bibtexinfo'")
1072 << XMLStream::ESCAPE_AND
1073 << bibinfo.getInfo(entry.key(), buffer(), ci)
1074 << xml::EndTag("span")
1075 << xml::EndTag("div")
1078 xs << xml::EndTag("div");
1083 void InsetBibtex::docbook(XMLStream & xs, OutputParams const &) const
1085 BiblioInfo const & bibinfo = buffer().masterBibInfo();
1086 bool const all_entries = getParam("btprint") == "btPrintAll";
1087 vector<docstring> const & cites =
1088 all_entries ? bibinfo.getKeys() : bibinfo.citedEntries();
1090 docstring const reflabel = buffer().B_("References");
1092 // Tell BiblioInfo our purpose (i.e. generate HTML rich text).
1094 ci.context = CiteItem::Export;
1096 ci.max_key_size = UINT_MAX;
1098 // Header for bibliography (title required).
1099 xs << xml::StartTag("bibliography");
1101 xs << xml::StartTag("title");
1103 xs << xml::EndTag("title") << xml::CR();
1105 // Translation between keys in each entry and DocBook tags.
1106 // IDs for publications; list: http://tdg.docbook.org/tdg/5.2/biblioid.html.
1107 vector<pair<string, string>> biblioId = { // <bibtex, docbook>
1108 make_pair("doi", "doi"),
1109 make_pair("isbn", "isbn"),
1110 make_pair("issn", "issn"),
1111 make_pair("isrn", "isrn"),
1112 make_pair("istc", "istc"),
1113 make_pair("lccn", "libraryofcongress"),
1114 make_pair("number", "pubsnumber"),
1115 make_pair("url", "uri")
1117 // Relations between documents.
1118 vector<pair<string, string>> relations = { // <bibtex, docbook biblioset relation>
1119 make_pair("journal", "journal"),
1120 make_pair("booktitle", "book"),
1121 make_pair("series", "series")
1123 // Various things that do not fit DocBook.
1124 vector<string> misc = { "language", "school", "note" };
1126 // Store the mapping between BibTeX and DocBook.
1127 map<string, string> toDocBookTag;
1128 toDocBookTag["fullnames:author"] = "SPECIFIC"; // No direct translation to DocBook: <authorgroup>.
1129 toDocBookTag["publisher"] = "SPECIFIC"; // No direct translation to DocBook: <publisher>.
1130 toDocBookTag["address"] = "SPECIFIC"; // No direct translation to DocBook: <publisher>.
1131 toDocBookTag["editor"] = "editor";
1132 toDocBookTag["institution"] = "SPECIFIC"; // No direct translation to DocBook: <org>.
1134 toDocBookTag["title"] = "title";
1135 toDocBookTag["volume"] = "volumenum";
1136 toDocBookTag["edition"] = "edition";
1137 toDocBookTag["pages"] = "artpagenums";
1139 toDocBookTag["abstract"] = "SPECIFIC"; // No direct translation to DocBook: <abstract>.
1140 toDocBookTag["keywords"] = "SPECIFIC"; // No direct translation to DocBook: <keywordset>.
1141 toDocBookTag["year"] = "SPECIFIC"; // No direct translation to DocBook: <pubdate>.
1142 toDocBookTag["month"] = "SPECIFIC"; // No direct translation to DocBook: <pubdate>.
1144 toDocBookTag["journal"] = "SPECIFIC"; // No direct translation to DocBook: <biblioset>.
1145 toDocBookTag["booktitle"] = "SPECIFIC"; // No direct translation to DocBook: <biblioset>.
1146 toDocBookTag["series"] = "SPECIFIC"; // No direct translation to DocBook: <biblioset>.
1148 for (auto const & id: biblioId)
1149 toDocBookTag[id.first] = "SPECIFIC"; // No direct translation to DocBook: <biblioid>.
1150 for (auto const & id: relations)
1151 toDocBookTag[id.first] = "SPECIFIC"; // No direct translation to DocBook: <biblioset>.
1152 for (auto const & id: misc)
1153 toDocBookTag[id] = "SPECIFIC"; // No direct translation to DocBook: <bibliomisc>.
1155 // Loop over the entries. If there are no entries, add a comment to say so.
1156 auto vit = cites.begin();
1157 auto ven = cites.end();
1160 xs << XMLStream::ESCAPE_NONE << "<!-- No entry in the bibliography. -->";
1164 for (; vit != ven; ++vit) {
1165 auto const biit = bibinfo.find(*vit);
1166 if (biit == bibinfo.end())
1169 BibTeXInfo const & entry = biit->second;
1170 string const attr = "xml:id=\"" + to_utf8(xml::cleanID(entry.key())) + "\"";
1171 xs << xml::StartTag("biblioentry", attr);
1174 // FIXME Right now, we are calling BibInfo::getInfo on the key,
1175 // which will give us all the cross-referenced info. But for every
1176 // entry, so there's a lot of repetition. This should be fixed.
1178 // Parse the results of getInfo and emit the corresponding DocBook tags. Interesting pieces have the form
1179 // "<span class="bib-STH">STH</span>", the rest of the text may be discarded.
1180 // Could have written a DocBook version of expandFormat (that parses a citation into HTML), but it implements
1181 // some kind of recursion. Still, a (static) conversion step between the citation format and DocBook would have
1182 // been required. All in all, both codes approaches would have been similar, but this parsing allows relying
1183 // on existing building blocks.
1185 string html = to_utf8(bibinfo.getInfo(entry.key(), buffer(), ci));
1186 regex tagRegex("<span class=\"bib-([^\"]*)\">([^<]*)</span>");
1188 auto tagIt = lyx::sregex_iterator(html.cbegin(), html.cend(), tagRegex, regex_constants::match_default);
1189 auto tagEnd = lyx::sregex_iterator();
1190 map<string, string> delayedTags;
1192 // Read all tags from HTML and convert those that have a 1:1 matching.
1193 while (tagIt != tagEnd) {
1194 string tag = tagIt->str(); // regex_match cannot work with temporary strings.
1197 if (regex_match(tag, match, tagRegex)) {
1198 if (toDocBookTag[match[1]] == "SPECIFIC") {
1199 delayedTags[match[1]] = match[2];
1201 xs << xml::StartTag(toDocBookTag[match[1]]);
1202 xs << from_utf8(match[2].str());
1203 xs << xml::EndTag(toDocBookTag[match[1]]);
1207 LYXERR0("The BibTeX field " << match[1].str() << " is unknown.");
1208 xs << XMLStream::ESCAPE_NONE << from_utf8("<!-- Output Error: The BibTeX field " + match[1].str() + " is unknown -->\n");
1212 // Type of document (book, journal paper, etc.).
1213 xs << xml::StartTag("bibliomisc", "role=\"type\"");
1214 xs << entry.entryType();
1215 xs << xml::EndTag("bibliomisc");
1218 // Handle tags that have complex transformations.
1219 if (! delayedTags.empty()) {
1220 unsigned long remainingTags = delayedTags.size(); // Used as a workaround. With GCC 7, when erasing all
1221 // elements one by one, some elements may still pop in later on (even though they were deleted previously).
1222 auto hasTag = [&delayedTags](const string & key) { return delayedTags.find(key) != delayedTags.end(); };
1223 auto getTag = [&delayedTags](const string & key) { return from_utf8(delayedTags[key]); };
1224 auto eraseTag = [&delayedTags, &remainingTags](const string & key) {
1226 delayedTags.erase(key);
1229 // Notes on order of checks.
1230 // - address goes with publisher if there is one, so check this first. Otherwise, the address goes with
1231 // the entry without other details.
1234 if (hasTag("publisher")) {
1235 xs << xml::StartTag("publisher");
1237 xs << xml::StartTag("publishername");
1238 xs << getTag("publisher");
1239 xs << xml::EndTag("publishername");
1242 if (hasTag("address")) {
1243 xs << xml::StartTag("address");
1244 xs << getTag("address");
1245 xs << xml::EndTag("address");
1246 eraseTag("address");
1249 xs << xml::EndTag("publisher");
1251 eraseTag("publisher");
1254 if (hasTag("address")) {
1255 xs << xml::StartTag("address");
1256 xs << getTag("address");
1257 xs << xml::EndTag("address");
1258 eraseTag("address");
1262 if (hasTag("keywords")) {
1263 // Split the keywords on comma.
1264 docstring keywordSet = getTag("keywords");
1265 vector<docstring> keywords;
1266 if (keywordSet.find(from_utf8(",")) == string::npos) {
1267 keywords = { keywordSet };
1270 while ((pos = keywordSet.find(from_utf8(","))) != string::npos) {
1271 keywords.push_back(keywordSet.substr(0, pos));
1272 keywordSet.erase(0, pos + 1);
1274 keywords.push_back(keywordSet);
1277 xs << xml::StartTag("keywordset") << xml::CR();
1278 for (auto & kw: keywords) {
1279 kw.erase(kw.begin(), std::find_if(kw.begin(), kw.end(),
1280 [](char_type c) {return !lyx::isSpace(c);}));
1281 xs << xml::StartTag("keyword");
1283 xs << xml::EndTag("keyword");
1286 xs << xml::EndTag("keywordset") << xml::CR();
1287 eraseTag("keywords");
1291 // Example: http://tdg.docbook.org/tdg/5.1/biblioset.html
1292 if (hasTag("year")) {
1293 docstring value = getTag("year");
1296 // Follow xsd:gYearMonth format (http://books.xmlschemata.org/relaxng/ch19-77135.html).
1297 if (hasTag("month")) {
1298 value += "-" + getTag("month");
1302 xs << xml::StartTag("pubdate");
1304 xs << xml::EndTag("pubdate");
1309 if (hasTag("institution")) {
1310 xs << xml::StartTag("org");
1312 xs << xml::StartTag("orgname");
1313 xs << getTag("institution");
1314 xs << xml::EndTag("orgname");
1316 xs << xml::EndTag("org");
1318 eraseTag("institution");
1322 // Example: http://tdg.docbook.org/tdg/5.1/biblioset.html
1323 for (auto const & id: relations) {
1324 if (hasTag(id.first)) {
1325 xs << xml::StartTag("biblioset", "relation=\"" + id.second + "\"");
1327 xs << xml::StartTag("title");
1328 xs << getTag(id.first);
1329 xs << xml::EndTag("title");
1331 xs << xml::EndTag("biblioset");
1338 // Example: http://tdg.docbook.org/tdg/5.1/authorgroup.html
1339 if (hasTag("fullnames:author")) {
1340 // Perform full parsing of the BibTeX string, dealing with the many corner cases that might
1342 authorsToDocBookAuthorGroup(getTag("fullnames:author"), xs, buffer());
1343 eraseTag("fullnames:author");
1347 if (hasTag("abstract")) {
1348 // Split the paragraphs on new line.
1349 docstring abstract = getTag("abstract");
1350 vector<docstring> paragraphs;
1351 if (abstract.find(from_utf8("\n")) == string::npos) {
1352 paragraphs = { abstract };
1355 while ((pos = abstract.find(from_utf8(","))) != string::npos) {
1356 paragraphs.push_back(abstract.substr(0, pos));
1357 abstract.erase(0, pos + 1);
1359 paragraphs.push_back(abstract);
1362 xs << xml::StartTag("abstract");
1364 for (auto const & para: paragraphs) {
1367 xs << xml::StartTag("para");
1369 xs << xml::EndTag("para");
1372 xs << xml::EndTag("abstract");
1374 eraseTag("abstract");
1378 for (auto const & id: biblioId) {
1379 if (hasTag(id.first)) {
1380 xs << xml::StartTag("biblioid", "class=\"" + id.second + "\"");
1381 xs << getTag(id.first);
1382 xs << xml::EndTag("biblioid");
1389 for (auto const & id: misc) {
1391 xs << xml::StartTag("bibliomisc", "role=\"" + id + "\"");
1393 xs << xml::EndTag("bibliomisc");
1399 // After all tags are processed, check for errors.
1400 if (remainingTags > 0) {
1401 LYXERR0("Still delayed tags not yet handled.");
1402 xs << XMLStream::ESCAPE_NONE << from_utf8("<!-- Output Error: still delayed tags not yet handled.\n");
1403 for (auto const & item: delayedTags) {
1404 xs << from_utf8(" " + item.first + ": " + item.second + "\n");
1406 xs << XMLStream::ESCAPE_NONE << from_utf8(" -->\n");
1410 xs << xml::EndTag("biblioentry");
1414 // Footer for bibliography.
1415 xs << xml::EndTag("bibliography");
1420 void InsetBibtex::write(ostream & os) const
1422 params().Write(os, &buffer());
1426 string InsetBibtex::contextMenuName() const
1428 return "context-bibtex";