]> git.lyx.org Git - features.git/blob - src/insets/InsetBibtex.cpp
Followup to e10db6c7: handle InsetCitation too.
[features.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_xhtml.h"
30 #include "OutputParams.h"
31 #include "PDFOptions.h"
32 #include "texstream.h"
33 #include "TextClass.h"
34
35 #include "frontends/alert.h"
36
37 #include "support/convert.h"
38 #include "support/debug.h"
39 #include "support/docstream.h"
40 #include "support/ExceptionMessage.h"
41 #include "support/FileNameList.h"
42 #include "support/filetools.h"
43 #include "support/gettext.h"
44 #include "support/lstrings.h"
45 #include "support/os.h"
46 #include "support/PathChanger.h"
47 #include "support/textutils.h"
48
49 #include <limits>
50
51 using namespace std;
52 using namespace lyx::support;
53
54 namespace lyx {
55
56 namespace Alert = frontend::Alert;
57 namespace os = support::os;
58
59
60 InsetBibtex::InsetBibtex(Buffer * buf, InsetCommandParams const & p)
61         : InsetCommand(buf, p)
62 {
63         buffer().invalidateBibfileCache();
64         buffer().removeBiblioTempFiles();
65 }
66
67
68 InsetBibtex::~InsetBibtex()
69 {
70         if (isBufferLoaded()) {
71                 /* Coverity believes that this may throw an exception, but
72                  * actually this code path is not taken when buffer_ == 0 */
73                 // coverity[exn_spec_violation]
74                 buffer().invalidateBibfileCache();
75                 // coverity[exn_spec_violation]
76                 buffer().removeBiblioTempFiles();
77         }
78 }
79
80
81 ParamInfo const & InsetBibtex::findInfo(string const & /* cmdName */)
82 {
83         static ParamInfo param_info_;
84         if (param_info_.empty()) {
85                 param_info_.add("btprint", ParamInfo::LATEX_OPTIONAL);
86                 param_info_.add("bibfiles", ParamInfo::LATEX_REQUIRED);
87                 param_info_.add("options", ParamInfo::LYX_INTERNAL);
88                 param_info_.add("biblatexopts", ParamInfo::LATEX_OPTIONAL);
89         }
90         return param_info_;
91 }
92
93
94 void InsetBibtex::doDispatch(Cursor & cur, FuncRequest & cmd)
95 {
96         switch (cmd.action()) {
97
98         case LFUN_INSET_EDIT:
99                 editDatabases();
100                 break;
101
102         case LFUN_INSET_MODIFY: {
103                 InsetCommandParams p(BIBTEX_CODE);
104                 try {
105                         if (!InsetCommand::string2params(to_utf8(cmd.argument()), p)) {
106                                 cur.noScreenUpdate();
107                                 break;
108                         }
109                 } catch (ExceptionMessage const & message) {
110                         if (message.type_ == WarningException) {
111                                 Alert::warning(message.title_, message.details_);
112                                 cur.noScreenUpdate();
113                         } else
114                                 throw;
115                         break;
116                 }
117
118                 cur.recordUndo();
119                 setParams(p);
120                 buffer().invalidateBibfileCache();
121                 buffer().removeBiblioTempFiles();
122                 cur.forceBufferUpdate();
123                 break;
124         }
125
126         default:
127                 InsetCommand::doDispatch(cur, cmd);
128                 break;
129         }
130 }
131
132
133 bool InsetBibtex::getStatus(Cursor & cur, FuncRequest const & cmd,
134                 FuncStatus & flag) const
135 {
136         switch (cmd.action()) {
137         case LFUN_INSET_EDIT:
138                 flag.setEnabled(true);
139                 return true;
140
141         default:
142                 return InsetCommand::getStatus(cur, cmd, flag);
143         }
144 }
145
146
147 void InsetBibtex::editDatabases() const
148 {
149         vector<docstring> bibfilelist = getVectorFromString(getParam("bibfiles"));
150
151         if (bibfilelist.empty())
152                 return;
153
154         int nr_databases = bibfilelist.size();
155         if (nr_databases > 1) {
156                         docstring const engine = usingBiblatex() ? _("Biblatex") : _("BibTeX");
157                         docstring message = bformat(_("The %1$s[[BibTeX/Biblatex]] inset includes %2$s databases.\n"
158                                                        "If you proceed, all of them will be opened."),
159                                                         engine, convert<docstring>(nr_databases));
160                         int const ret = Alert::prompt(_("Open Databases?"),
161                                 message, 0, 1, _("&Cancel"), _("&Proceed"));
162
163                         if (ret == 0)
164                                 return;
165         }
166
167         vector<docstring>::const_iterator it = bibfilelist.begin();
168         vector<docstring>::const_iterator en = bibfilelist.end();
169         for (; it != en; ++it) {
170                 FileName const bibfile = getBibTeXPath(*it, buffer());
171                 theFormats().edit(buffer(), bibfile,
172                      theFormats().getFormatFromFile(bibfile));
173         }
174 }
175
176
177 bool InsetBibtex::usingBiblatex() const
178 {
179         return buffer().masterParams().useBiblatex();
180 }
181
182
183 docstring InsetBibtex::screenLabel() const
184 {
185         return usingBiblatex() ? _("Biblatex Generated Bibliography")
186                                : _("BibTeX Generated Bibliography");
187 }
188
189
190 docstring InsetBibtex::toolTip(BufferView const & /*bv*/, int /*x*/, int /*y*/) const
191 {
192         docstring tip = _("Databases:");
193         vector<docstring> bibfilelist = getVectorFromString(getParam("bibfiles"));
194
195         tip += "<ul>";
196         if (bibfilelist.empty())
197                 tip += "<li>" + _("none") + "</li>";
198         else
199                 for (docstring const & bibfile : bibfilelist)
200                         tip += "<li>" + bibfile + "</li>";
201         tip += "</ul>";
202
203         // Style-Options
204         bool toc = false;
205         docstring style = getParam("options"); // maybe empty! and with bibtotoc
206         docstring bibtotoc = from_ascii("bibtotoc");
207         if (prefixIs(style, bibtotoc)) {
208                 toc = true;
209                 if (contains(style, char_type(',')))
210                         style = split(style, bibtotoc, char_type(','));
211         }
212
213         docstring const btprint = getParam("btprint");
214         if (!usingBiblatex()) {
215                 tip += _("Style File:");
216                 tip += "<ul><li>" + (style.empty() ? _("none") : style) + "</li></ul>";
217
218                 tip += _("Lists:") + " ";
219                 if (btprint == "btPrintAll")
220                         tip += _("all references");
221                 else if (btprint == "btPrintNotCited")
222                         tip += _("all uncited references");
223                 else
224                         tip += _("all cited references");
225                 if (toc) {
226                         tip += ", ";
227                         tip += _("included in TOC");
228                 }
229                 if (!buffer().parent()
230                     && buffer().params().multibib == "child") {
231                         tip += "<br />";
232                         tip += _("Note: This bibliography is not output, since bibliographies in the master file "
233                                  "are not allowed with the setting 'Multiple bibliographies per child document'");
234                 }
235         } else {
236                 tip += _("Lists:") + " ";
237                 if (btprint == "bibbysection")
238                         tip += _("all reference units");
239                 else if (btprint == "btPrintAll")
240                         tip += _("all references");
241                 else
242                         tip += _("all cited references");
243                 if (toc) {
244                         tip += ", ";
245                         tip += _("included in TOC");
246                 }
247                 if (!getParam("biblatexopts").empty()) {
248                         if (toc)
249                                 tip += "<br />";
250                         tip += _("Options: ") + getParam("biblatexopts");
251                 }
252         }
253
254         return tip;
255 }
256
257
258 void InsetBibtex::latex(otexstream & os, OutputParams const & runparams) const
259 {
260         // The sequence of the commands:
261         // With normal BibTeX:
262         // 1. \bibliographystyle{style}
263         // 2. \addcontentsline{...} - if option bibtotoc set
264         // 3. \bibliography{database}
265         // With bibtopic:
266         // 1. \bibliographystyle{style}
267         // 2. \begin{btSect}{database}
268         // 3. \btPrint{Cited|NotCited|All}
269         // 4. \end{btSect}
270         // With Biblatex:
271         // \printbibliography[biblatexopts]
272         // or
273         // \bibbysection[biblatexopts] - if btprint is "bibbysection"
274
275         // chapterbib does not allow bibliographies in the master
276         if (!usingBiblatex() && !runparams.is_child
277             && buffer().params().multibib == "child")
278                 return;
279
280         string style = to_utf8(getParam("options")); // maybe empty! and with bibtotoc
281         string bibtotoc;
282         if (prefixIs(style, "bibtotoc")) {
283                 bibtotoc = "bibtotoc";
284                 if (contains(style, ','))
285                         style = split(style, bibtotoc, ',');
286         }
287
288         if (usingBiblatex()) {
289                 // Options
290                 string opts = to_utf8(getParam("biblatexopts"));
291                 // bibtotoc-Option
292                 if (!bibtotoc.empty())
293                         opts = opts.empty() ? "heading=bibintoc" : "heading=bibintoc," + opts;
294                 // The bibliography command
295                 docstring btprint = getParam("btprint");
296                 if (btprint == "btPrintAll")
297                         os << "\\nocite{*}\n";
298                 if (btprint == "bibbysection" && !buffer().masterParams().multibib.empty())
299                         os << "\\bibbysection";
300                 else
301                         os << "\\printbibliography";
302                 if (!opts.empty())
303                         os << "[" << opts << "]";
304                 os << "\n";
305         } else {// using BibTeX
306                 // Database(s)
307                 vector<docstring> const db_out =
308                         buffer().prepareBibFilePaths(runparams, getBibFiles(), false);
309                 // Style options
310                 if (style == "default")
311                         style = buffer().masterParams().defaultBiblioStyle();
312                 if (!style.empty() && !buffer().masterParams().useBibtopic()) {
313                         string base = buffer().masterBuffer()->prepareFileNameForLaTeX(style, ".bst", runparams.nice);
314                         FileName const try_in_file =
315                                 makeAbsPath(base + ".bst", buffer().filePath());
316                         bool const not_from_texmf = try_in_file.isReadableFile();
317                         // If this style does not come from texmf and we are not
318                         // exporting to .tex copy it to the tmp directory.
319                         // This prevents problems with spaces and 8bit characters
320                         // in the file name.
321                         if (!runparams.inComment && !runparams.dryrun && !runparams.nice &&
322                             not_from_texmf) {
323                                 // use new style name
324                                 DocFileName const in_file = DocFileName(try_in_file);
325                                 base = removeExtension(in_file.mangledFileName());
326                                 FileName const out_file = makeAbsPath(base + ".bst",
327                                                 buffer().masterBuffer()->temppath());
328                                 bool const success = in_file.copyTo(out_file);
329                                 if (!success) {
330                                         LYXERR0("Failed to copy '" << in_file
331                                                << "' to '" << out_file << "'");
332                                 }
333                         }
334                         // FIXME UNICODE
335                         os << "\\bibliographystyle{"
336                            << from_utf8(latex_path(buffer().prepareFileNameForLaTeX(base, ".bst", runparams.nice)))
337                            << "}\n";
338                 }
339                 // Warn about spaces in bst path. Warn only once.
340                 static bool warned_about_bst_spaces = false;
341                 if (!warned_about_bst_spaces && runparams.nice && contains(style, ' ')) {
342                         warned_about_bst_spaces = true;
343                         Alert::warning(_("Export Warning!"),
344                                        _("There are spaces in the path to your BibTeX style file.\n"
345                                                       "BibTeX will be unable to find it."));
346                 }
347                 // Handle the bibtopic case
348                 if (!db_out.empty() && buffer().masterParams().useBibtopic()) {
349                         os << "\\begin{btSect}";
350                         if (!style.empty())
351                                 os << "[" << style << "]";
352                         os << "{" << getStringFromVector(db_out) << "}\n";
353                         docstring btprint = getParam("btprint");
354                         if (btprint.empty())
355                                 // default
356                                 btprint = from_ascii("btPrintCited");
357                         os << "\\" << btprint << "\n"
358                            << "\\end{btSect}\n";
359                 }
360                 // bibtotoc option
361                 if (!bibtotoc.empty() && !buffer().masterParams().useBibtopic()) {
362                         // set label for hyperref, see http://www.lyx.org/trac/ticket/6470
363                         if (buffer().masterParams().pdfoptions().use_hyperref)
364                                         os << "\\phantomsection";
365                         if (buffer().masterParams().documentClass().hasLaTeXLayout("chapter"))
366                                 os << "\\addcontentsline{toc}{chapter}{\\bibname}";
367                         else if (buffer().masterParams().documentClass().hasLaTeXLayout("section"))
368                                 os << "\\addcontentsline{toc}{section}{\\refname}";
369                 }
370                 // The bibliography command
371                 if (!db_out.empty() && !buffer().masterParams().useBibtopic()) {
372                         docstring btprint = getParam("btprint");
373                         if (btprint == "btPrintAll") {
374                                 os << "\\nocite{*}\n";
375                         }
376                         os << "\\bibliography{" << getStringFromVector(db_out) << "}\n";
377                 }
378         }
379 }
380
381
382 support::FileNamePairList InsetBibtex::getBibFiles() const
383 {
384         FileName path(buffer().filePath());
385         support::PathChanger p(path);
386
387         // We need to store both the real FileName and the way it is entered
388         // (with full path, rel path or as a single file name).
389         // The latter is needed for biblatex's central bibfile macro.
390         support::FileNamePairList vec;
391
392         vector<docstring> bibfilelist = getVectorFromString(getParam("bibfiles"));
393         vector<docstring>::const_iterator it = bibfilelist.begin();
394         vector<docstring>::const_iterator en = bibfilelist.end();
395         for (; it != en; ++it) {
396                 FileName const file = getBibTeXPath(*it, buffer());
397
398                 if (!file.empty())
399                         vec.push_back(make_pair(*it, file));
400                 else
401                         LYXERR0("Couldn't find " + to_utf8(*it) + " in InsetBibtex::getBibFiles()!");
402         }
403
404         return vec;
405
406 }
407
408 namespace {
409
410         // methods for parsing bibtex files
411
412         typedef map<docstring, docstring> VarMap;
413
414         /// remove whitespace characters, optionally a single comma,
415         /// and further whitespace characters from the stream.
416         /// @return true if a comma was found, false otherwise
417         ///
418         bool removeWSAndComma(ifdocstream & ifs) {
419                 char_type ch;
420
421                 if (!ifs)
422                         return false;
423
424                 // skip whitespace
425                 do {
426                         ifs.get(ch);
427                 } while (ifs && isSpace(ch));
428
429                 if (!ifs)
430                         return false;
431
432                 if (ch != ',') {
433                         ifs.putback(ch);
434                         return false;
435                 }
436
437                 // skip whitespace
438                 do {
439                         ifs.get(ch);
440                 } while (ifs && isSpace(ch));
441
442                 if (ifs) {
443                         ifs.putback(ch);
444                 }
445
446                 return true;
447         }
448
449
450         enum charCase {
451                 makeLowerCase,
452                 keepCase
453         };
454
455         /// remove whitespace characters, read characer sequence
456         /// not containing whitespace characters or characters in
457         /// delimChars, and remove further whitespace characters.
458         ///
459         /// @return true if a string of length > 0 could be read.
460         ///
461         bool readTypeOrKey(docstring & val, ifdocstream & ifs,
462                 docstring const & delimChars, docstring const & illegalChars,
463                 charCase chCase) {
464
465                 char_type ch;
466
467                 val.clear();
468
469                 if (!ifs)
470                         return false;
471
472                 // skip whitespace
473                 do {
474                         ifs.get(ch);
475                 } while (ifs && isSpace(ch));
476
477                 if (!ifs)
478                         return false;
479
480                 // read value
481                 bool legalChar = true;
482                 while (ifs && !isSpace(ch) &&
483                                                  delimChars.find(ch) == docstring::npos &&
484                                                  (legalChar = (illegalChars.find(ch) == docstring::npos))
485                                         )
486                 {
487                         if (chCase == makeLowerCase)
488                                 val += lowercase(ch);
489                         else
490                                 val += ch;
491                         ifs.get(ch);
492                 }
493
494                 if (!legalChar) {
495                         ifs.putback(ch);
496                         return false;
497                 }
498
499                 // skip whitespace
500                 while (ifs && isSpace(ch)) {
501                         ifs.get(ch);
502                 }
503
504                 if (ifs) {
505                         ifs.putback(ch);
506                 }
507
508                 return val.length() > 0;
509         }
510
511         /// read subsequent bibtex values that are delimited with a #-character.
512         /// Concatenate all parts and replace names with the associated string in
513         /// the variable strings.
514         /// @return true if reading was successfull (all single parts were delimited
515         /// correctly)
516         bool readValue(docstring & val, ifdocstream & ifs, const VarMap & strings) {
517
518                 char_type ch;
519
520                 val.clear();
521
522                 if (!ifs)
523                         return false;
524
525                 do {
526                         // skip whitespace
527                         do {
528                                 ifs.get(ch);
529                         } while (ifs && isSpace(ch));
530
531                         if (!ifs)
532                                 return false;
533
534                         // check for field type
535                         if (isDigitASCII(ch)) {
536
537                                 // read integer value
538                                 do {
539                                         val += ch;
540                                         ifs.get(ch);
541                                 } while (ifs && isDigitASCII(ch));
542
543                                 if (!ifs)
544                                         return false;
545
546                         } else if (ch == '"' || ch == '{') {
547                                 // set end delimiter
548                                 char_type delim = ch == '"' ? '"': '}';
549
550                                 // Skip whitespace
551                                 do {
552                                         ifs.get(ch);
553                                 } while (ifs && isSpace(ch));
554
555                                 if (!ifs)
556                                         return false;
557
558                                 // We now have the first non-whitespace character
559                                 // We'll collapse adjacent whitespace.
560                                 bool lastWasWhiteSpace = false;
561
562                                 // inside this delimited text braces must match.
563                                 // Thus we can have a closing delimiter only
564                                 // when nestLevel == 0
565                                 int nestLevel = 0;
566
567                                 while (ifs && (nestLevel > 0 || ch != delim)) {
568                                         if (isSpace(ch)) {
569                                                 lastWasWhiteSpace = true;
570                                                 ifs.get(ch);
571                                                 continue;
572                                         }
573                                         // We output the space only after we stop getting
574                                         // whitespace so as not to output any whitespace
575                                         // at the end of the value.
576                                         if (lastWasWhiteSpace) {
577                                                 lastWasWhiteSpace = false;
578                                                 val += ' ';
579                                         }
580
581                                         val += ch;
582
583                                         // update nesting level
584                                         switch (ch) {
585                                                 case '{':
586                                                         ++nestLevel;
587                                                         break;
588                                                 case '}':
589                                                         --nestLevel;
590                                                         if (nestLevel < 0)
591                                                                 return false;
592                                                         break;
593                                         }
594
595                                         if (ifs)
596                                                 ifs.get(ch);
597                                 }
598
599                                 if (!ifs)
600                                         return false;
601
602                                 // FIXME Why is this here?
603                                 ifs.get(ch);
604
605                                 if (!ifs)
606                                         return false;
607
608                         } else {
609
610                                 // reading a string name
611                                 docstring strName;
612
613                                 while (ifs && !isSpace(ch) && ch != '#' && ch != ',' && ch != '}' && ch != ')') {
614                                         strName += lowercase(ch);
615                                         ifs.get(ch);
616                                 }
617
618                                 if (!ifs)
619                                         return false;
620
621                                 // replace the string with its assigned value or
622                                 // discard it if it's not assigned
623                                 if (strName.length()) {
624                                         VarMap::const_iterator pos = strings.find(strName);
625                                         if (pos != strings.end()) {
626                                                 val += pos->second;
627                                         }
628                                 }
629                         }
630
631                         // skip WS
632                         while (ifs && isSpace(ch)) {
633                                 ifs.get(ch);
634                         }
635
636                         if (!ifs)
637                                 return false;
638
639                         // continue reading next value on concatenate with '#'
640                 } while (ch == '#');
641
642                 ifs.putback(ch);
643
644                 return true;
645         }
646 }
647
648
649 void InsetBibtex::collectBibKeys(InsetIterator const & /*di*/) const
650 {
651         parseBibTeXFiles();
652 }
653
654
655 void InsetBibtex::parseBibTeXFiles() const
656 {
657         // This bibtex parser is a first step to parse bibtex files
658         // more precisely.
659         //
660         // - it reads the whole bibtex entry and does a syntax check
661         //   (matching delimiters, missing commas,...
662         // - it recovers from errors starting with the next @-character
663         // - it reads @string definitions and replaces them in the
664         //   field values.
665         // - it accepts more characters in keys or value names than
666         //   bibtex does.
667         //
668         // Officially bibtex does only support ASCII, but in practice
669         // you can use the encoding of the main document as long as
670         // some elements like keys and names are pure ASCII. Therefore
671         // we convert the file from the buffer encoding.
672         // We don't restrict keys to ASCII in LyX, since our own
673         // InsetBibitem can generate non-ASCII keys, and nonstandard
674         // 8bit clean bibtex forks exist.
675
676         BiblioInfo keylist;
677
678         support::FileNamePairList const files = getBibFiles();
679         support::FileNamePairList::const_iterator it = files.begin();
680         support::FileNamePairList::const_iterator en = files.end();
681         for (; it != en; ++ it) {
682                 ifdocstream ifs(it->second.toFilesystemEncoding().c_str(),
683                         ios_base::in, buffer().masterParams().encoding().iconvName());
684
685                 char_type ch;
686                 VarMap strings;
687
688                 while (ifs) {
689
690                         ifs.get(ch);
691                         if (!ifs)
692                                 break;
693
694                         if (ch != '@')
695                                 continue;
696
697                         docstring entryType;
698
699                         if (!readTypeOrKey(entryType, ifs, from_ascii("{("), docstring(), makeLowerCase)) {
700                                 lyxerr << "BibTeX Parser: Error reading entry type." << std::endl;
701                                 continue;
702                         }
703
704                         if (!ifs) {
705                                 lyxerr << "BibTeX Parser: Unexpected end of file." << std::endl;
706                                 continue;
707                         }
708
709                         if (entryType == from_ascii("comment")) {
710                                 ifs.ignore(numeric_limits<int>::max(), '\n');
711                                 continue;
712                         }
713
714                         ifs.get(ch);
715                         if (!ifs) {
716                                 lyxerr << "BibTeX Parser: Unexpected end of file." << std::endl;
717                                 break;
718                         }
719
720                         if ((ch != '(') && (ch != '{')) {
721                                 lyxerr << "BibTeX Parser: Invalid entry delimiter." << std::endl;
722                                 ifs.putback(ch);
723                                 continue;
724                         }
725
726                         // process the entry
727                         if (entryType == from_ascii("string")) {
728
729                                 // read string and add it to the strings map
730                                 // (or replace it's old value)
731                                 docstring name;
732                                 docstring value;
733
734                                 if (!readTypeOrKey(name, ifs, from_ascii("="), from_ascii("#{}(),"), makeLowerCase)) {
735                                         lyxerr << "BibTeX Parser: Error reading string name." << std::endl;
736                                         continue;
737                                 }
738
739                                 if (!ifs) {
740                                         lyxerr << "BibTeX Parser: Unexpected end of file." << std::endl;
741                                         continue;
742                                 }
743
744                                 // next char must be an equal sign
745                                 ifs.get(ch);
746                                 if (!ifs || ch != '=') {
747                                         lyxerr << "BibTeX Parser: No `=' after string name: " <<
748                                                         name << "." << std::endl;
749                                         continue;
750                                 }
751
752                                 if (!readValue(value, ifs, strings)) {
753                                         lyxerr << "BibTeX Parser: Unable to read value for string: " <<
754                                                         name << "." << std::endl;
755                                         continue;
756                                 }
757
758                                 strings[name] = value;
759
760                         } else if (entryType == from_ascii("preamble")) {
761
762                                 // preamble definitions are discarded.
763                                 // can they be of any use in lyx?
764                                 docstring value;
765
766                                 if (!readValue(value, ifs, strings)) {
767                                         lyxerr << "BibTeX Parser: Unable to read preamble value." << std::endl;
768                                         continue;
769                                 }
770
771                         } else {
772
773                                 // Citation entry. Try to read the key.
774                                 docstring key;
775
776                                 if (!readTypeOrKey(key, ifs, from_ascii(","), from_ascii("}"), keepCase)) {
777                                         lyxerr << "BibTeX Parser: Unable to read key for entry type:" <<
778                                                         entryType << "." << std::endl;
779                                         continue;
780                                 }
781
782                                 if (!ifs) {
783                                         lyxerr << "BibTeX Parser: Unexpected end of file." << std::endl;
784                                         continue;
785                                 }
786
787                                 /////////////////////////////////////////////
788                                 // now we have a key, so we will add an entry
789                                 // (even if it's empty, as bibtex does)
790                                 //
791                                 // we now read the field = value pairs.
792                                 // all items must be separated by a comma. If
793                                 // it is missing the scanning of this entry is
794                                 // stopped and the next is searched.
795                                 docstring name;
796                                 docstring value;
797                                 docstring data;
798                                 BibTeXInfo keyvalmap(key, entryType);
799
800                                 bool readNext = removeWSAndComma(ifs);
801
802                                 while (ifs && readNext) {
803
804                                         // read field name
805                                         if (!readTypeOrKey(name, ifs, from_ascii("="),
806                                                            from_ascii("{}(),"), makeLowerCase) || !ifs)
807                                                 break;
808
809                                         // next char must be an equal sign
810                                         // FIXME Whitespace??
811                                         ifs.get(ch);
812                                         if (!ifs) {
813                                                 lyxerr << "BibTeX Parser: Unexpected end of file." << std::endl;
814                                                 break;
815                                         }
816                                         if (ch != '=') {
817                                                 lyxerr << "BibTeX Parser: Missing `=' after field name: " <<
818                                                                 name << ", for key: " << key << "." << std::endl;
819                                                 ifs.putback(ch);
820                                                 break;
821                                         }
822
823                                         // read field value
824                                         if (!readValue(value, ifs, strings)) {
825                                                 lyxerr << "BibTeX Parser: Unable to read value for field: " <<
826                                                                 name << ", for key: " << key << "." << std::endl;
827                                                 break;
828                                         }
829
830                                         keyvalmap[name] = value;
831                                         data += "\n\n" + value;
832                                         keylist.addFieldName(name);
833                                         readNext = removeWSAndComma(ifs);
834                                 }
835
836                                 // add the new entry
837                                 keylist.addEntryType(entryType);
838                                 keyvalmap.setAllData(data);
839                                 keylist[key] = keyvalmap;
840                         } //< else (citation entry)
841                 } //< searching '@'
842         } //< for loop over files
843
844         buffer().addBiblioInfo(keylist);
845 }
846
847
848 FileName InsetBibtex::getBibTeXPath(docstring const & filename, Buffer const & buf)
849 {
850         string texfile = changeExtension(to_utf8(filename), "bib");
851         // note that, if the filename can be found directly from the path,
852         // findtexfile will just return a FileName object for that path.
853         FileName file(findtexfile(texfile, "bib"));
854         if (file.empty())
855                 file = FileName(makeAbsPath(texfile, buf.filePath()));
856         return file;
857 }
858
859
860 bool InsetBibtex::addDatabase(docstring const & db)
861 {
862         docstring bibfiles = getParam("bibfiles");
863         if (tokenPos(bibfiles, ',', db) != -1)
864                 return false;
865         if (!bibfiles.empty())
866                 bibfiles += ',';
867         setParam("bibfiles", bibfiles + db);
868         return true;
869 }
870
871
872 bool InsetBibtex::delDatabase(docstring const & db)
873 {
874         docstring bibfiles = getParam("bibfiles");
875         if (contains(bibfiles, db)) {
876                 int const n = tokenPos(bibfiles, ',', db);
877                 docstring bd = db;
878                 if (n > 0) {
879                         // this is not the first database
880                         docstring tmp = ',' + bd;
881                         setParam("bibfiles", subst(bibfiles, tmp, docstring()));
882                 } else if (n == 0)
883                         // this is the first (or only) database
884                         setParam("bibfiles", split(bibfiles, bd, ','));
885                 else
886                         return false;
887         }
888         return true;
889 }
890
891
892 void InsetBibtex::validate(LaTeXFeatures & features) const
893 {
894         BufferParams const & mparams = features.buffer().masterParams();
895         if (mparams.useBibtopic())
896                 features.require("bibtopic");
897         else if (!mparams.useBiblatex() && mparams.multibib == "child")
898                 features.require("chapterbib");
899         // FIXME XHTML
900         // It'd be better to be able to get this from an InsetLayout, but at present
901         // InsetLayouts do not seem really to work for things that aren't InsetTexts.
902         if (features.runparams().flavor == OutputParams::HTML)
903                 features.addCSSSnippet("div.bibtexentry { margin-left: 2em; text-indent: -2em; }\n"
904                         "span.bibtexlabel:before{ content: \"[\"; }\n"
905                         "span.bibtexlabel:after{ content: \"] \"; }");
906 }
907
908
909 int InsetBibtex::plaintext(odocstringstream & os,
910        OutputParams const & op, size_t max_length) const
911 {
912         docstring const reflabel = buffer().B_("References");
913
914         // We could output more information here, e.g., what databases are included
915         // and information about options. But I don't necessarily see any reason to
916         // do this right now.
917         if (op.for_tooltip || op.for_toc || op.for_search) {
918                 os << '[' << reflabel << ']' << '\n';
919                 return PLAINTEXT_NEWLINE;
920         }
921
922         BiblioInfo bibinfo = buffer().masterBibInfo();
923         bibinfo.makeCitationLabels(buffer());
924         vector<docstring> const & cites = bibinfo.citedEntries();
925
926         size_t start_size = os.str().size();
927         docstring refoutput;
928         refoutput += reflabel + "\n\n";
929
930         // Tell BiblioInfo our purpose
931         CiteItem ci;
932         ci.context = CiteItem::Export;
933
934         // Now we loop over the entries
935         vector<docstring>::const_iterator vit = cites.begin();
936         vector<docstring>::const_iterator const ven = cites.end();
937         for (; vit != ven; ++vit) {
938                 if (start_size + refoutput.size() >= max_length)
939                         break;
940                 BiblioInfo::const_iterator const biit = bibinfo.find(*vit);
941                 if (biit == bibinfo.end())
942                         continue;
943                 BibTeXInfo const & entry = biit->second;
944                 refoutput += "[" + entry.label() + "] ";
945                 // FIXME Right now, we are calling BibInfo::getInfo on the key,
946                 // which will give us all the cross-referenced info. But for every
947                 // entry, so there's a lot of repitition. This should be fixed.
948                 refoutput += bibinfo.getInfo(entry.key(), buffer(), ci) + "\n\n";
949         }
950         os << refoutput;
951         return refoutput.size();
952 }
953
954
955 // FIXME
956 // docstring InsetBibtex::entriesAsXHTML(vector<docstring> const & entries)
957 // And then here just: entriesAsXHTML(buffer().masterBibInfo().citedEntries())
958 docstring InsetBibtex::xhtml(XHTMLStream & xs, OutputParams const &) const
959 {
960         BiblioInfo const & bibinfo = buffer().masterBibInfo();
961         bool const all_entries = getParam("btprint") == "btPrintAll";
962         vector<docstring> const & cites = 
963             all_entries ? bibinfo.getKeys() : bibinfo.citedEntries();
964
965         docstring const reflabel = buffer().B_("References");
966
967         // tell BiblioInfo our purpose
968         CiteItem ci;
969         ci.context = CiteItem::Export;
970         ci.richtext = true;
971         ci.max_key_size = UINT_MAX;
972
973         xs << html::StartTag("h2", "class='bibtex'")
974                 << reflabel
975                 << html::EndTag("h2")
976                 << html::StartTag("div", "class='bibtex'");
977
978         // Now we loop over the entries
979         vector<docstring>::const_iterator vit = cites.begin();
980         vector<docstring>::const_iterator const ven = cites.end();
981         for (; vit != ven; ++vit) {
982                 BiblioInfo::const_iterator const biit = bibinfo.find(*vit);
983                 if (biit == bibinfo.end())
984                         continue;
985
986                 BibTeXInfo const & entry = biit->second;
987                 string const attr = "class='bibtexentry' id='LyXCite-" 
988                     + to_utf8(html::cleanAttr(entry.key())) + "'";
989                 xs << html::StartTag("div", attr);
990                 
991                 // don't print labels if we're outputting all entries
992                 if (!all_entries) {
993                         xs << html::StartTag("span", "class='bibtexlabel'")
994                                 << entry.label()
995                                 << html::EndTag("span");
996                 }
997                 
998                 // FIXME Right now, we are calling BibInfo::getInfo on the key,
999                 // which will give us all the cross-referenced info. But for every
1000                 // entry, so there's a lot of repitition. This should be fixed.
1001                 xs << html::StartTag("span", "class='bibtexinfo'")
1002                    << XHTMLStream::ESCAPE_AND
1003                    << bibinfo.getInfo(entry.key(), buffer(), ci)
1004                    << html::EndTag("span")
1005                    << html::EndTag("div")
1006                    << html::CR();
1007         }
1008         xs << html::EndTag("div");
1009         return docstring();
1010 }
1011
1012
1013 void InsetBibtex::write(ostream & os) const
1014 {
1015         params().Write(os, &buffer());
1016 }
1017
1018
1019 string InsetBibtex::contextMenuName() const
1020 {
1021         return "context-bibtex";
1022 }
1023
1024
1025 } // namespace lyx