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