]> git.lyx.org Git - lyx.git/blob - src/insets/InsetBibtex.cpp
ef7d29f0e8aae8fde1f078b4979dd8c48af8d9ae
[lyx.git] / src / insets / InsetBibtex.cpp
1 /**
2  * \file InsetBibtex.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Alejandro Aguilar Sierra
7  * \author Richard Heck (BibTeX parser improvements)
8  * \author Jürgen Spitzmüller
9  *
10  * Full author contact details are available in file CREDITS.
11  */
12
13 #include <config.h>
14
15 #include "InsetBibtex.h"
16
17 #include "BiblioInfo.h"
18 #include "Buffer.h"
19 #include "BufferParams.h"
20 #include "CiteEnginesList.h"
21 #include "Cursor.h"
22 #include "DispatchResult.h"
23 #include "Encoding.h"
24 #include "Exporter.h"
25 #include "Format.h"
26 #include "FuncRequest.h"
27 #include "FuncStatus.h"
28 #include "LaTeXFeatures.h"
29 #include "output_latex.h"
30 #include "xml.h"
31 #include "OutputParams.h"
32 #include "PDFOptions.h"
33 #include "texstream.h"
34 #include "TextClass.h"
35 #include "TocBackend.h"
36
37 #include "frontends/alert.h"
38
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/gettext.h"
47 #include "support/lstrings.h"
48 #include "support/os.h"
49 #include "support/PathChanger.h"
50 #include "support/textutils.h"
51
52 #include <limits>
53 #include <map>
54 #include <regex>
55 #include <utility>
56
57 #include <iostream>
58
59 using namespace std;
60 using namespace lyx::support;
61
62 namespace lyx {
63
64 namespace Alert = frontend::Alert;
65 namespace os = support::os;
66
67
68 InsetBibtex::InsetBibtex(Buffer * buf, InsetCommandParams const & p)
69         : InsetCommand(buf, p)
70 {}
71
72
73 ParamInfo const & InsetBibtex::findInfo(string const & /* cmdName */)
74 {
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);
83         }
84         return param_info_;
85 }
86
87
88 void InsetBibtex::doDispatch(Cursor & cur, FuncRequest & cmd)
89 {
90         switch (cmd.action()) {
91
92         case LFUN_INSET_EDIT:
93                 editDatabases(cmd.argument());
94                 break;
95
96         case LFUN_INSET_MODIFY: {
97                 InsetCommandParams p(BIBTEX_CODE);
98                 try {
99                         if (!InsetCommand::string2params(to_utf8(cmd.argument()), p)) {
100                                 cur.noScreenUpdate();
101                                 break;
102                         }
103                 } catch (ExceptionMessage const & message) {
104                         if (message.type_ == WarningException) {
105                                 Alert::warning(message.title_, message.details_);
106                                 cur.noScreenUpdate();
107                         } else
108                                 throw;
109                         break;
110                 }
111
112                 cur.recordUndo();
113                 setParams(p);
114                 cur.buffer()->clearBibFileCache();
115                 cur.forceBufferUpdate();
116                 break;
117         }
118
119         default:
120                 InsetCommand::doDispatch(cur, cmd);
121                 break;
122         }
123 }
124
125
126 bool InsetBibtex::getStatus(Cursor & cur, FuncRequest const & cmd,
127                 FuncStatus & flag) const
128 {
129         switch (cmd.action()) {
130         case LFUN_INSET_EDIT:
131                 flag.setEnabled(true);
132                 return true;
133
134         default:
135                 return InsetCommand::getStatus(cur, cmd, flag);
136         }
137 }
138
139
140 void InsetBibtex::editDatabases(docstring const & db) const
141 {
142         vector<docstring> bibfilelist = getVectorFromString(getParam("bibfiles"));
143
144         if (bibfilelist.empty())
145                 return;
146
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"));
155
156                         if (ret == 0)
157                                 return;
158         }
159
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)
164                         continue;
165                 FileName const bibfile = buffer().getBibfilePath(*it);
166                 theFormats().edit(buffer(), bibfile,
167                      theFormats().getFormatFromFile(bibfile));
168         }
169 }
170
171
172 bool InsetBibtex::usingBiblatex() const
173 {
174         return buffer().masterParams().useBiblatex();
175 }
176
177
178 docstring InsetBibtex::screenLabel() const
179 {
180         return usingBiblatex() ? _("Biblatex Generated Bibliography")
181                                : _("BibTeX Generated Bibliography");
182 }
183
184
185 docstring InsetBibtex::toolTip(BufferView const & /*bv*/, int /*x*/, int /*y*/) const
186 {
187         docstring tip = _("Databases:");
188         vector<docstring> bibfilelist = getVectorFromString(getParam("bibfiles"));
189
190         tip += "<ul>";
191         if (bibfilelist.empty())
192                 tip += "<li>" + _("none") + "</li>";
193         else
194                 for (docstring const & bibfile : bibfilelist)
195                         tip += "<li>" + bibfile + "</li>";
196         tip += "</ul>";
197
198         // Style-Options
199         bool toc = false;
200         docstring style = getParam("options"); // maybe empty! and with bibtotoc
201         docstring bibtotoc = from_ascii("bibtotoc");
202         if (prefixIs(style, bibtotoc)) {
203                 toc = true;
204                 if (contains(style, char_type(',')))
205                         style = split(style, bibtotoc, char_type(','));
206         }
207
208         docstring const btprint = getParam("btprint");
209         if (!usingBiblatex()) {
210                 tip += _("Style File:");
211                 tip += "<ul><li>" + (style.empty() ? _("none") : style) + "</li></ul>";
212
213                 tip += _("Lists:") + " ";
214                 if (btprint == "btPrintAll")
215                         tip += _("all references");
216                 else if (btprint == "btPrintNotCited")
217                         tip += _("all uncited references");
218                 else
219                         tip += _("all cited references");
220                 if (toc) {
221                         tip += ", ";
222                         tip += _("included in TOC");
223                 }
224                 if (!buffer().parent()
225                     && buffer().params().multibib == "child") {
226                         tip += "<br />";
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'");
229                 }
230         } else {
231                 tip += _("Lists:") + " ";
232                 if (btprint == "bibbysection")
233                         tip += _("all reference units");
234                 else if (btprint == "btPrintAll")
235                         tip += _("all references");
236                 else
237                         tip += _("all cited references");
238                 if (toc) {
239                         tip += ", ";
240                         tip += _("included in TOC");
241                 }
242                 if (!getParam("biblatexopts").empty()) {
243                         tip += "<br />";
244                         tip += _("Options: ") + getParam("biblatexopts");
245                 }
246         }
247
248         return tip;
249 }
250
251
252 void InsetBibtex::latex(otexstream & os, OutputParams const & runparams) const
253 {
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}
259         // With bibtopic:
260         // 1. \bibliographystyle{style}
261         // 2. \begin{btSect}{database}
262         // 3. \btPrint{Cited|NotCited|All}
263         // 4. \end{btSect}
264         // With Biblatex:
265         // \printbibliography[biblatexopts]
266         // or
267         // \bibbysection[biblatexopts] - if btprint is "bibbysection"
268
269         // chapterbib does not allow bibliographies in the master
270         if (!usingBiblatex() && !runparams.is_child
271             && buffer().params().multibib == "child")
272                 return;
273
274         if (runparams.inDeletedInset) {
275                 // We cannot strike-out bibligraphies,
276                 // so we just output a note.
277                 os << "\\textbf{"
278                    << buffer().B_("[BIBLIOGRAPHY DELETED!]")
279                    << "}";
280                 return;
281         }
282
283         string style = to_utf8(getParam("options")); // maybe empty! and with bibtotoc
284         string bibtotoc;
285         if (prefixIs(style, "bibtotoc")) {
286                 bibtotoc = "bibtotoc";
287                 if (contains(style, ','))
288                         style = split(style, bibtotoc, ',');
289         }
290
291         if (usingBiblatex()) {
292                 // Options
293                 string opts = to_utf8(getParam("biblatexopts"));
294                 // bibtotoc-Option
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";
303                 else
304                         os << "\\printbibliography";
305                 if (!opts.empty())
306                         os << "[" << opts << "]";
307                 os << "\n";
308         } else {// using BibTeX
309                 // Database(s)
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);
316                 // Style options
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
327                         // in the file name.
328                         if (!runparams.inComment && !runparams.dryrun && !runparams.nice &&
329                             not_from_texmf) {
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);
336                                 if (!success) {
337                                         LYXERR0("Failed to copy '" << in_file
338                                                << "' to '" << out_file << "'");
339                                 }
340                         }
341                         // FIXME UNICODE
342                         os << "\\bibliographystyle{"
343                            << from_utf8(latex_path(buffer().prepareFileNameForLaTeX(base, ".bst", runparams.nice)))
344                            << "}\n";
345                 }
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."));
353                 }
354                 // Encoding
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) {
361                                 os << "\\bgroup";
362                                 switchEncoding(os.os(), buffer().params(), runparams, *enc, true);
363                                 runparams.encoding = enc;
364                                 encoding_switched = true;
365                         }
366                 }
367                 // Handle the bibtopic case
368                 if (!db_out.empty() && buffer().masterParams().useBibtopic()) {
369                         os << "\\begin{btSect}";
370                         if (!style.empty())
371                                 os << "[" << style << "]";
372                         os << "{" << getStringFromVector(db_out) << "}\n";
373                         docstring btprint = getParam("btprint");
374                         if (btprint.empty())
375                                 // default
376                                 btprint = from_ascii("btPrintCited");
377                         os << "\\" << btprint << "\n"
378                            << "\\end{btSect}\n";
379                 }
380                 // bibtotoc option
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}";
390                 }
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";
396                         }
397                         os << "\\bibliography{" << getStringFromVector(db_out) << "}\n";
398                 }
399                 if (encoding_switched){
400                         // Switch back
401                         switchEncoding(os.os(), buffer().params(),
402                                        runparams, *save_enc, true, true);
403                         os << "\\egroup" << breakln;
404                         runparams.encoding = save_enc;
405                 }
406         }
407 }
408
409
410 docstring_list InsetBibtex::getBibFiles() const
411 {
412         return getVectorFromString(getParam("bibfiles"));
413 }
414
415 namespace {
416
417         // methods for parsing bibtex files
418
419         typedef map<docstring, docstring> VarMap;
420
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
424         ///
425         bool removeWSAndComma(ifdocstream & ifs) {
426                 char_type ch;
427
428                 if (!ifs)
429                         return false;
430
431                 // skip whitespace
432                 do {
433                         ifs.get(ch);
434                 } while (ifs && isSpace(ch));
435
436                 if (!ifs)
437                         return false;
438
439                 if (ch != ',') {
440                         ifs.putback(ch);
441                         return false;
442                 }
443
444                 // skip whitespace
445                 do {
446                         ifs.get(ch);
447                 } while (ifs && isSpace(ch));
448
449                 if (ifs) {
450                         ifs.putback(ch);
451                 }
452
453                 return true;
454         }
455
456
457         enum charCase {
458                 makeLowerCase,
459                 keepCase
460         };
461
462         /// remove whitespace characters, read character sequence
463         /// not containing whitespace characters or characters in
464         /// delimChars, and remove further whitespace characters.
465         ///
466         /// @return true if a string of length > 0 could be read.
467         ///
468         bool readTypeOrKey(docstring & val, ifdocstream & ifs,
469                 docstring const & delimChars, docstring const & illegalChars,
470                 charCase chCase) {
471
472                 char_type ch;
473
474                 val.clear();
475
476                 if (!ifs)
477                         return false;
478
479                 // skip whitespace
480                 do {
481                         ifs.get(ch);
482                 } while (ifs && isSpace(ch));
483
484                 if (!ifs)
485                         return false;
486
487                 // read value
488                 while (ifs && !isSpace(ch) &&
489                        delimChars.find(ch) == docstring::npos &&
490                        illegalChars.find(ch) == docstring::npos)
491                 {
492                         if (chCase == makeLowerCase)
493                                 val += lowercase(ch);
494                         else
495                                 val += ch;
496                         ifs.get(ch);
497                 }
498
499                 if (illegalChars.find(ch) != docstring::npos) {
500                         ifs.putback(ch);
501                         return false;
502                 }
503
504                 // skip whitespace
505                 while (ifs && isSpace(ch)) {
506                         ifs.get(ch);
507                 }
508
509                 if (ifs) {
510                         ifs.putback(ch);
511                 }
512
513                 return val.length() > 0;
514         }
515
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
520         /// correctly)
521         bool readValue(docstring & val, ifdocstream & ifs, const VarMap & strings) {
522
523                 char_type ch;
524
525                 val.clear();
526
527                 if (!ifs)
528                         return false;
529
530                 do {
531                         // skip whitespace
532                         do {
533                                 ifs.get(ch);
534                         } while (ifs && isSpace(ch));
535
536                         if (!ifs)
537                                 return false;
538
539                         // check for field type
540                         if (isDigitASCII(ch)) {
541
542                                 // read integer value
543                                 do {
544                                         val += ch;
545                                         ifs.get(ch);
546                                 } while (ifs && isDigitASCII(ch));
547
548                                 if (!ifs)
549                                         return false;
550
551                         } else if (ch == '"' || ch == '{') {
552                                 // set end delimiter
553                                 char_type delim = ch == '"' ? '"': '}';
554
555                                 // Skip whitespace
556                                 do {
557                                         ifs.get(ch);
558                                 } while (ifs && isSpace(ch));
559
560                                 if (!ifs)
561                                         return false;
562
563                                 // We now have the first non-whitespace character
564                                 // We'll collapse adjacent whitespace.
565                                 bool lastWasWhiteSpace = false;
566
567                                 // inside this delimited text braces must match.
568                                 // Thus we can have a closing delimiter only
569                                 // when nestLevel == 0
570                                 int nestLevel = 0;
571
572                                 while (ifs && (nestLevel > 0 || ch != delim)) {
573                                         if (isSpace(ch)) {
574                                                 lastWasWhiteSpace = true;
575                                                 ifs.get(ch);
576                                                 continue;
577                                         }
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;
583                                                 val += ' ';
584                                         }
585
586                                         val += ch;
587
588                                         // update nesting level
589                                         switch (ch) {
590                                                 case '{':
591                                                         ++nestLevel;
592                                                         break;
593                                                 case '}':
594                                                         --nestLevel;
595                                                         if (nestLevel < 0)
596                                                                 return false;
597                                                         break;
598                                         }
599
600                                         if (ifs)
601                                                 ifs.get(ch);
602                                 }
603
604                                 if (!ifs)
605                                         return false;
606
607                                 // FIXME Why is this here?
608                                 ifs.get(ch);
609
610                                 if (!ifs)
611                                         return false;
612
613                         } else {
614
615                                 // reading a string name
616                                 docstring strName;
617
618                                 while (ifs && !isSpace(ch) && ch != '#' && ch != ',' && ch != '}' && ch != ')') {
619                                         strName += lowercase(ch);
620                                         ifs.get(ch);
621                                 }
622
623                                 if (!ifs)
624                                         return false;
625
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()) {
631                                                 val += pos->second;
632                                         }
633                                 }
634                         }
635
636                         // skip WS
637                         while (ifs && isSpace(ch)) {
638                                 ifs.get(ch);
639                         }
640
641                         if (!ifs)
642                                 return false;
643
644                         // continue reading next value on concatenate with '#'
645                 } while (ch == '#');
646
647                 ifs.putback(ch);
648
649                 return true;
650         }
651 } // namespace
652
653
654 void InsetBibtex::collectBibKeys(InsetIterator const & /*di*/, FileNameList & checkedFiles) const
655 {
656         parseBibTeXFiles(checkedFiles);
657 }
658
659
660 void InsetBibtex::parseBibTeXFiles(FileNameList & checkedFiles) const
661 {
662         // This bibtex parser is a first step to parse bibtex files
663         // more precisely.
664         //
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
669         //   field values.
670         // - it accepts more characters in keys or value names than
671         //   bibtex does.
672         //
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.
680
681         BiblioInfo keylist;
682
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 << "!");
688                         continue;
689                 }
690                 if (find(checkedFiles.begin(), checkedFiles.end(), bibfile) != checkedFiles.end())
691                         // already checked this one. Skip.
692                         continue;
693                 else
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"]);
700
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);
705
706                 char_type ch;
707                 VarMap strings;
708
709                 while (ifs) {
710                         ifs.get(ch);
711                         if (!ifs)
712                                 break;
713
714                         if (ch != '@')
715                                 continue;
716
717                         docstring entryType;
718
719                         if (!readTypeOrKey(entryType, ifs, from_ascii("{("), docstring(), makeLowerCase)) {
720                                 lyxerr << "BibTeX Parser: Error reading entry type." << std::endl;
721                                 continue;
722                         }
723
724                         if (!ifs) {
725                                 lyxerr << "BibTeX Parser: Unexpected end of file." << std::endl;
726                                 continue;
727                         }
728
729                         if (entryType == from_ascii("comment")) {
730                                 ifs.ignore(numeric_limits<int>::max(), '\n');
731                                 continue;
732                         }
733
734                         ifs.get(ch);
735                         if (!ifs) {
736                                 lyxerr << "BibTeX Parser: Unexpected end of file." << std::endl;
737                                 break;
738                         }
739
740                         if ((ch != '(') && (ch != '{')) {
741                                 lyxerr << "BibTeX Parser: Invalid entry delimiter." << std::endl;
742                                 ifs.putback(ch);
743                                 continue;
744                         }
745
746                         // process the entry
747                         if (entryType == from_ascii("string")) {
748
749                                 // read string and add it to the strings map
750                                 // (or replace it's old value)
751                                 docstring name;
752                                 docstring value;
753
754                                 if (!readTypeOrKey(name, ifs, from_ascii("="), from_ascii("#{}(),"), makeLowerCase)) {
755                                         lyxerr << "BibTeX Parser: Error reading string name." << std::endl;
756                                         continue;
757                                 }
758
759                                 if (!ifs) {
760                                         lyxerr << "BibTeX Parser: Unexpected end of file." << std::endl;
761                                         continue;
762                                 }
763
764                                 // next char must be an equal sign
765                                 ifs.get(ch);
766                                 if (!ifs || ch != '=') {
767                                         lyxerr << "BibTeX Parser: No `=' after string name: " <<
768                                                         name << "." << std::endl;
769                                         continue;
770                                 }
771
772                                 if (!readValue(value, ifs, strings)) {
773                                         lyxerr << "BibTeX Parser: Unable to read value for string: " <<
774                                                         name << "." << std::endl;
775                                         continue;
776                                 }
777
778                                 strings[name] = value;
779
780                         } else if (entryType == from_ascii("preamble")) {
781
782                                 // preamble definitions are discarded.
783                                 // can they be of any use in lyx?
784                                 docstring value;
785
786                                 if (!readValue(value, ifs, strings)) {
787                                         lyxerr << "BibTeX Parser: Unable to read preamble value." << std::endl;
788                                         continue;
789                                 }
790
791                         } else {
792
793                                 // Citation entry. Try to read the key.
794                                 docstring key;
795
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;
799                                         continue;
800                                 }
801
802                                 if (!ifs) {
803                                         lyxerr << "BibTeX Parser: Unexpected end of file." << std::endl;
804                                         continue;
805                                 }
806
807                                 /////////////////////////////////////////////
808                                 // now we have a key, so we will add an entry
809                                 // (even if it's empty, as bibtex does)
810                                 //
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.
815                                 docstring name;
816                                 docstring value;
817                                 docstring data;
818                                 BibTeXInfo keyvalmap(key, entryType);
819
820                                 bool readNext = removeWSAndComma(ifs);
821
822                                 while (ifs && readNext) {
823
824                                         // read field name
825                                         if (!readTypeOrKey(name, ifs, from_ascii("="),
826                                                            from_ascii("{}(),"), makeLowerCase) || !ifs)
827                                                 break;
828
829                                         // next char must be an equal sign
830                                         // FIXME Whitespace??
831                                         ifs.get(ch);
832                                         if (!ifs) {
833                                                 lyxerr << "BibTeX Parser: Unexpected end of file." << std::endl;
834                                                 break;
835                                         }
836                                         if (ch != '=') {
837                                                 lyxerr << "BibTeX Parser: Missing `=' after field name: " <<
838                                                                 name << ", for key: " << key << "." << std::endl;
839                                                 ifs.putback(ch);
840                                                 break;
841                                         }
842
843                                         // read field value
844                                         if (!readValue(value, ifs, strings)) {
845                                                 lyxerr << "BibTeX Parser: Unable to read value for field: " <<
846                                                                 name << ", for key: " << key << "." << std::endl;
847                                                 break;
848                                         }
849
850                                         keyvalmap[name] = value;
851                                         data += "\n\n" + value;
852                                         keylist.addFieldName(name);
853                                         readNext = removeWSAndComma(ifs);
854                                 }
855
856                                 // add the new entry
857                                 keylist.addEntryType(entryType);
858                                 keyvalmap.setAllData(data);
859                                 keylist[key] = keyvalmap;
860                         } //< else (citation entry)
861                 } //< searching '@'
862         } //< for loop over files
863
864         buffer().addBiblioInfo(keylist);
865 }
866
867
868 bool InsetBibtex::addDatabase(docstring const & db)
869 {
870         docstring bibfiles = getParam("bibfiles");
871         if (tokenPos(bibfiles, ',', db) != -1)
872                 return false;
873         if (!bibfiles.empty())
874                 bibfiles += ',';
875         setParam("bibfiles", bibfiles + db);
876         return true;
877 }
878
879
880 bool InsetBibtex::delDatabase(docstring const & db)
881 {
882         docstring bibfiles = getParam("bibfiles");
883         if (contains(bibfiles, db)) {
884                 int const n = tokenPos(bibfiles, ',', db);
885                 docstring bd = db;
886                 if (n > 0) {
887                         // this is not the first database
888                         docstring tmp = ',' + bd;
889                         setParam("bibfiles", subst(bibfiles, tmp, docstring()));
890                 } else if (n == 0)
891                         // this is the first (or only) database
892                         setParam("bibfiles", split(bibfiles, bd, ','));
893                 else
894                         return false;
895         }
896         return true;
897 }
898
899
900 void InsetBibtex::validate(LaTeXFeatures & features) const
901 {
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");
907         // FIXME XHTML
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: \"] \"; }");
914 }
915
916
917 void InsetBibtex::updateBuffer(ParIterator const &, UpdateType, bool const /*deleted*/)
918 {
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);
926                 invalidate = true;
927         }
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);
933                         invalidate = true;
934                 }
935         }
936         if (invalidate)
937                 buffer().invalidateBibinfoCache();
938 }
939
940
941 map<string, string> InsetBibtex::getFileEncodings() const
942 {
943         vector<string> ps =
944                 getVectorFromString(to_utf8(getParam("file_encodings")), "\t");
945         std::map<string, string> res;
946         for (string const & s: ps) {
947                 string key;
948                 string val = split(s, key, ' ');
949                 res[key] = val;
950         }
951         return res;
952 }
953
954
955 docstring InsetBibtex::getRefLabel() const
956 {
957         if (buffer().masterParams().documentClass().hasLaTeXLayout("chapter"))
958                 return buffer().B_("Bibliography");
959         return buffer().B_("References");
960 }
961
962
963 void InsetBibtex::addToToc(DocIterator const & cpit, bool output_active,
964                            UpdateType, TocBackend & backend) const
965 {
966         if (!prefixIs(to_utf8(getParam("options")), "bibtotoc"))
967                 return;
968
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")) 
974                         ? 1 : 2;
975         toc->push_back(TocItem(cpit, item_depth, str, output_active));
976 }
977
978
979 int InsetBibtex::plaintext(odocstringstream & os,
980        OutputParams const & op, size_t max_length) const
981 {
982         docstring const reflabel = getRefLabel();
983
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;
990         }
991
992         BiblioInfo bibinfo = buffer().masterBibInfo();
993         bibinfo.makeCitationLabels(buffer());
994         vector<docstring> const & cites = bibinfo.citedEntries();
995
996         size_t start_size = os.str().size();
997         docstring refoutput;
998         refoutput += reflabel + "\n\n";
999
1000         // Tell BiblioInfo our purpose
1001         CiteItem ci;
1002         ci.context = CiteItem::Export;
1003
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)
1009                         break;
1010                 BiblioInfo::const_iterator const biit = bibinfo.find(*vit);
1011                 if (biit == bibinfo.end())
1012                         continue;
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";
1019         }
1020         os << refoutput;
1021         return int(refoutput.size());
1022 }
1023
1024
1025 // FIXME
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
1029 {
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();
1034
1035         docstring const reflabel = buffer().B_("References");
1036
1037         // tell BiblioInfo our purpose
1038         CiteItem ci;
1039         ci.context = CiteItem::Export;
1040         ci.richtext = true;
1041         ci.max_key_size = UINT_MAX;
1042
1043         xs << xml::StartTag("h2", "class='bibtex'")
1044                 << reflabel
1045                 << xml::EndTag("h2")
1046                 << xml::StartTag("div", "class='bibtex'");
1047
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())
1054                         continue;
1055
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);
1060
1061                 // don't print labels if we're outputting all entries
1062                 if (!all_entries) {
1063                         xs << xml::StartTag("span", "class='bibtexlabel'")
1064                                 << entry.label()
1065                                 << xml::EndTag("span");
1066                 }
1067
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")
1076                    << xml::CR();
1077         }
1078         xs << xml::EndTag("div");
1079         return docstring();
1080 }
1081
1082
1083 void InsetBibtex::docbook(XMLStream & xs, OutputParams const &) const
1084 {
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();
1089
1090         docstring const reflabel = buffer().B_("References");
1091
1092         // Check that the bibliography is not empty, to ensure that the document is valid.
1093         if (cites.empty()) {
1094                 xs << XMLStream::ESCAPE_NONE << "<!-- The bibliography is empty! -->";
1095                 xs << xml::CR();
1096                 return;
1097         }
1098
1099         // Tell BiblioInfo our purpose (i.e. generate HTML rich text).
1100         CiteItem ci;
1101         ci.context = CiteItem::Export;
1102         ci.richtext = true;
1103         ci.max_key_size = UINT_MAX;
1104
1105         // Header for bibliography (title required).
1106         xs << xml::StartTag("bibliography");
1107         xs << xml::CR();
1108         xs << xml::StartTag("title");
1109         xs << reflabel;
1110         xs << xml::EndTag("title");
1111         xs << xml::CR();
1112
1113         // Translation between keys in each entry and DocBook tags.
1114         // IDs for publications; list: http://tdg.docbook.org/tdg/5.2/biblioid.html.
1115         vector<pair<string, string>> biblioId = { // <bibtex, docbook>
1116                 make_pair("doi", "doi"),
1117                 make_pair("isbn", "isbn"),
1118                 make_pair("issn", "issn"),
1119                 make_pair("isrn", "isrn"),
1120                 make_pair("istc", "istc"),
1121                 make_pair("lccn", "libraryofcongress"),
1122                 make_pair("number", "pubsnumber"),
1123                 make_pair("url", "uri")
1124         };
1125         // Relations between documents.
1126         vector<pair<string, string>> relations = { // <bibtex, docbook biblioset relation>
1127                 make_pair("journal", "journal"),
1128                 make_pair("booktitle", "book"),
1129                 make_pair("series", "series")
1130         };
1131         // Various things that do not fit DocBook.
1132         vector<string> misc = { "language", "school", "note" };
1133
1134         // Store the mapping between BibTeX and DocBook.
1135         map<string, string> toDocBookTag;
1136         toDocBookTag["fullnames:author"] = "SPECIFIC"; // No direct translation to DocBook: <authorgroup>.
1137         toDocBookTag["publisher"] = "SPECIFIC"; // No direct translation to DocBook: <publisher>.
1138         toDocBookTag["address"] = "SPECIFIC"; // No direct translation to DocBook: <publisher>.
1139         toDocBookTag["editor"] = "editor";
1140         toDocBookTag["institution"] = "SPECIFIC"; // No direct translation to DocBook: <org>.
1141
1142         toDocBookTag["title"] = "title";
1143         toDocBookTag["volume"] = "volumenum";
1144         toDocBookTag["edition"] = "edition";
1145         toDocBookTag["pages"] = "artpagenums";
1146
1147         toDocBookTag["abstract"] = "SPECIFIC"; // No direct translation to DocBook: <abstract>.
1148         toDocBookTag["keywords"] = "SPECIFIC"; // No direct translation to DocBook: <keywordset>.
1149         toDocBookTag["year"] = "SPECIFIC"; // No direct translation to DocBook: <pubdate>.
1150         toDocBookTag["month"] = "SPECIFIC"; // No direct translation to DocBook: <pubdate>.
1151
1152         toDocBookTag["journal"] = "SPECIFIC"; // No direct translation to DocBook: <biblioset>.
1153         toDocBookTag["booktitle"] = "SPECIFIC"; // No direct translation to DocBook: <biblioset>.
1154         toDocBookTag["series"] = "SPECIFIC"; // No direct translation to DocBook: <biblioset>.
1155
1156         for (auto const & id: biblioId)
1157             toDocBookTag[id.first] = "SPECIFIC"; // No direct translation to DocBook: <biblioid>.
1158         for (auto const & id: relations)
1159             toDocBookTag[id.first] = "SPECIFIC"; // No direct translation to DocBook: <biblioset>.
1160         for (auto const & id: misc)
1161             toDocBookTag[id] = "SPECIFIC"; // No direct translation to DocBook: <bibliomisc>.
1162
1163         // Loop over the entries. If there are no entries, add a comment to say so.
1164         auto vit = cites.begin();
1165         auto ven = cites.end();
1166
1167         for (; vit != ven; ++vit) {
1168                 auto const biit = bibinfo.find(*vit);
1169                 if (biit == bibinfo.end())
1170                         continue;
1171
1172                 BibTeXInfo const & entry = biit->second;
1173                 string const attr = "xml:id=\"" + to_utf8(xml::cleanID(entry.key())) + "\"";
1174                 xs << xml::StartTag("biblioentry", attr);
1175                 xs << xml::CR();
1176
1177                 // FIXME Right now, we are calling BibInfo::getInfo on the key,
1178                 // which will give us all the cross-referenced info. But for every
1179                 // entry, so there's a lot of repetition. This should be fixed.
1180
1181                 // Parse the results of getInfo and emit the corresponding DocBook tags. Interesting pieces have the form
1182                 // "<span class="bib-STH">STH</span>", the rest of the text may be discarded.
1183                 // Could have written a DocBook version of expandFormat (that parses a citation into HTML), but it implements
1184                 // some kind of recursion. Still, a (static) conversion step between the citation format and DocBook would have
1185                 // been required. All in all, both codes approaches would have been similar, but this parsing allows relying
1186                 // on existing building blocks.
1187
1188                 string html = to_utf8(bibinfo.getInfo(entry.key(), buffer(), ci));
1189                 regex tagRegex("<span class=\"bib-([^\"]*)\">([^<]*)</span>");
1190                 smatch match;
1191                 auto tagIt = sregex_iterator(html.cbegin(), html.cend(), tagRegex, regex_constants::match_default);
1192                 auto tagEnd = sregex_iterator();
1193                 map<string, string> delayedTags;
1194
1195                 // Read all tags from HTML and convert those that have a 1:1 matching.
1196                 while (tagIt != tagEnd) {
1197                         string tag = tagIt->str(); // regex_match cannot work with temporary strings.
1198                         ++tagIt;
1199
1200                         if (regex_match(tag, match, tagRegex)) {
1201                                 if (toDocBookTag[match[1]] == "SPECIFIC") {
1202                                         delayedTags[match[1]] = match[2];
1203                                 } else {
1204                                         xs << xml::StartTag(toDocBookTag[match[1]]);
1205                                         xs << from_utf8(match[2].str());
1206                                         xs << xml::EndTag(toDocBookTag[match[1]]);
1207                                         xs << xml::CR();
1208                                 }
1209                         } else {
1210                                 LYXERR0("The BibTeX field " << match[1].str() << " is unknown.");
1211                                 xs << XMLStream::ESCAPE_NONE << from_utf8("<!-- Output Error: The BibTeX field " + match[1].str() + " is unknown -->\n");
1212                         }
1213                 }
1214
1215                 // Type of document (book, journal paper, etc.).
1216                 xs << xml::StartTag("bibliomisc", "role=\"type\"");
1217                 xs << entry.entryType();
1218                 xs << xml::EndTag("bibliomisc");
1219                 xs << xml::CR();
1220
1221                 // Handle tags that have complex transformations.
1222                 if (! delayedTags.empty()) {
1223                         unsigned long remainingTags = delayedTags.size(); // Used as a workaround. With GCC 7, when erasing all
1224                         // elements one by one, some elements may still pop in later on (even though they were deleted previously).
1225                         auto hasTag = [&delayedTags](const string & key) { return delayedTags.find(key) != delayedTags.end(); };
1226                         auto getTag = [&delayedTags](const string & key) { return from_utf8(delayedTags[key]); };
1227                         auto eraseTag = [&delayedTags, &remainingTags](const string & key) {
1228                                 remainingTags -= 1;
1229                                 delayedTags.erase(key);
1230                         };
1231
1232                         // Notes on order of checks.
1233                         // - address goes with publisher if there is one, so check this first. Otherwise, the address goes with
1234                         //   the entry without other details.
1235
1236                         // <publisher>
1237                         if (hasTag("publisher")) {
1238                                 xs << xml::StartTag("publisher");
1239                                 xs << xml::CR();
1240                                 xs << xml::StartTag("publishername");
1241                                 xs << getTag("publisher");
1242                                 xs << xml::EndTag("publishername");
1243                                 xs << xml::CR();
1244
1245                                 if (hasTag("address")) {
1246                                         xs << xml::StartTag("address");
1247                                         xs << getTag("address");
1248                                         xs << xml::EndTag("address");
1249                                         eraseTag("address");
1250                                 }
1251
1252                                 xs << xml::EndTag("publisher");
1253                                 xs << xml::CR();
1254                                 eraseTag("publisher");
1255                         }
1256
1257                         if (hasTag("address")) {
1258                                 xs << xml::StartTag("address");
1259                                 xs << getTag("address");
1260                                 xs << xml::EndTag("address");
1261                                 eraseTag("address");
1262                         }
1263
1264                         // <keywordset>
1265                         if (hasTag("keywords")) {
1266                                 // Split the keywords on comma.
1267                                 docstring keywordSet = getTag("keywords");
1268                                 vector<docstring> keywords;
1269                                 if (keywordSet.find(from_utf8(",")) == string::npos) {
1270                                         keywords = { keywordSet };
1271                                 } else {
1272                                         size_t pos = 0;
1273                                         while ((pos = keywordSet.find(from_utf8(","))) != string::npos) {
1274                                                 keywords.push_back(keywordSet.substr(0, pos));
1275                                                 keywordSet.erase(0, pos + 1);
1276                                         }
1277                                         keywords.push_back(keywordSet);
1278                                 }
1279
1280                                 xs << xml::StartTag("keywordset") << xml::CR();
1281                                 for (auto & kw: keywords) {
1282                                         kw.erase(kw.begin(), std::find_if(kw.begin(), kw.end(),
1283                                                                           [](char_type c) {return !lyx::isSpace(c);}));
1284                                         xs << xml::StartTag("keyword");
1285                                         xs << kw;
1286                                         xs << xml::EndTag("keyword");
1287                                         xs << xml::CR();
1288                                 }
1289                                 xs << xml::EndTag("keywordset") << xml::CR();
1290                                 eraseTag("keywords");
1291                         }
1292
1293                         // <copyright>
1294                         // Example: http://tdg.docbook.org/tdg/5.1/biblioset.html
1295                         if (hasTag("year")) {
1296                                 docstring value = getTag("year");
1297                                 eraseTag("year");
1298
1299                                 // Follow xsd:gYearMonth format (http://books.xmlschemata.org/relaxng/ch19-77135.html).
1300                                 if (hasTag("month")) {
1301                                         value += "-" + getTag("month");
1302                                         eraseTag("month");
1303                                 }
1304
1305                                 xs << xml::StartTag("pubdate");
1306                                 xs << value;
1307                                 xs << xml::EndTag("pubdate");
1308                                 xs << xml::CR();
1309                         }
1310
1311                         // <institution>
1312                         if (hasTag("institution")) {
1313                                 xs << xml::StartTag("org");
1314                                 xs << xml::CR();
1315                                 xs << xml::StartTag("orgname");
1316                                 xs << getTag("institution");
1317                                 xs << xml::EndTag("orgname");
1318                                 xs << xml::CR();
1319                                 xs << xml::EndTag("org");
1320                                 xs << xml::CR();
1321                                 eraseTag("institution");
1322                         }
1323
1324                         // <biblioset>
1325                         // Example: http://tdg.docbook.org/tdg/5.1/biblioset.html
1326                         for (auto const & id: relations) {
1327                                 if (hasTag(id.first)) {
1328                                         xs << xml::StartTag("biblioset", "relation=\"" + id.second + "\"");
1329                                         xs << xml::CR();
1330                                         xs << xml::StartTag("title");
1331                                         xs << getTag(id.first);
1332                                         xs << xml::EndTag("title");
1333                                         xs << xml::CR();
1334                                         xs << xml::EndTag("biblioset");
1335                                         xs << xml::CR();
1336                                         eraseTag(id.first);
1337                                 }
1338                         }
1339
1340                         // <authorgroup>
1341                         // Example: http://tdg.docbook.org/tdg/5.1/authorgroup.html
1342                         if (hasTag("fullnames:author")) {
1343                                 // Perform full parsing of the BibTeX string, dealing with the many corner cases that might
1344                                 // be encountered.
1345                                 authorsToDocBookAuthorGroup(getTag("fullnames:author"), xs, buffer());
1346                                 eraseTag("fullnames:author");
1347                         }
1348
1349                         // <abstract>
1350                         if (hasTag("abstract")) {
1351                                 // Split the paragraphs on new line.
1352                                 docstring abstract = getTag("abstract");
1353                                 vector<docstring> paragraphs;
1354                                 if (abstract.find(from_utf8("\n")) == string::npos) {
1355                                         paragraphs = { abstract };
1356                                 } else {
1357                                         size_t pos = 0;
1358                                         while ((pos = abstract.find(from_utf8(","))) != string::npos) {
1359                                                 paragraphs.push_back(abstract.substr(0, pos));
1360                                                 abstract.erase(0, pos + 1);
1361                                         }
1362                                         paragraphs.push_back(abstract);
1363                                 }
1364
1365                                 xs << xml::StartTag("abstract");
1366                                 xs << xml::CR();
1367                                 for (auto const & para: paragraphs) {
1368                                         if (para.empty())
1369                                                 continue;
1370                                         xs << xml::StartTag("para");
1371                                         xs << para;
1372                                         xs << xml::EndTag("para");
1373                                 }
1374                                 xs << xml::CR();
1375                                 xs << xml::EndTag("abstract");
1376                                 xs << xml::CR();
1377                                 eraseTag("abstract");
1378                         }
1379
1380                         // <biblioid>
1381                         for (auto const & id: biblioId) {
1382                                 if (hasTag(id.first)) {
1383                                         xs << xml::StartTag("biblioid", "class=\"" + id.second + "\"");
1384                                         xs << getTag(id.first);
1385                                         xs << xml::EndTag("biblioid");
1386                                         xs << xml::CR();
1387                                         eraseTag(id.first);
1388                                 }
1389                         }
1390
1391                         // <bibliomisc>
1392                         for (auto const & id: misc) {
1393                                 if (hasTag(id)) {
1394                                         xs << xml::StartTag("bibliomisc", "role=\"" + id + "\"");
1395                                         xs << getTag(id);
1396                                         xs << xml::EndTag("bibliomisc");
1397                                         xs << xml::CR();
1398                                         eraseTag(id);
1399                                 }
1400                         }
1401
1402                         // After all tags are processed, check for errors.
1403                         if (remainingTags > 0) {
1404                                 LYXERR0("Still delayed tags not yet handled.");
1405                                 xs << XMLStream::ESCAPE_NONE << from_utf8("<!-- Output Error: still delayed tags not yet handled.\n");
1406                                 for (auto const & item: delayedTags) {
1407                                         xs << from_utf8(" " + item.first + ": " + item.second + "\n");
1408                                 }
1409                                 xs << XMLStream::ESCAPE_NONE << from_utf8(" -->\n");
1410                         }
1411                 }
1412
1413                 xs << xml::EndTag("biblioentry");
1414                 xs << xml::CR();
1415         }
1416
1417         // Footer for bibliography.
1418         xs << xml::EndTag("bibliography");
1419         xs << xml::CR();
1420 }
1421
1422
1423 void InsetBibtex::write(ostream & os) const
1424 {
1425         params().Write(os, &buffer());
1426 }
1427
1428
1429 string InsetBibtex::contextMenuName() const
1430 {
1431         return "context-bibtex";
1432 }
1433
1434
1435 } // namespace lyx