]> git.lyx.org Git - lyx.git/blob - src/insets/InsetBibtex.cpp
4394992e9687749b9c62a61923190e014fef9558
[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  *
9  * Full author contact details are available in file CREDITS.
10  */
11
12 #include <config.h>
13
14 #include "InsetBibtex.h"
15
16 #include "Buffer.h"
17 #include "BufferParams.h"
18 #include "DispatchResult.h"
19 #include "Encoding.h"
20 #include "FuncRequest.h"
21 #include "LaTeXFeatures.h"
22 #include "MetricsInfo.h"
23 #include "OutputParams.h"
24 #include "TextClass.h"
25
26 #include "frontends/alert.h"
27
28 #include "support/debug.h"
29 #include "support/docstream.h"
30 #include "support/ExceptionMessage.h"
31 #include "support/filetools.h"
32 #include "support/gettext.h"
33 #include "support/lstrings.h"
34 #include "support/os.h"
35 #include "support/Path.h"
36 #include "support/textutils.h"
37
38 #include <limits>
39
40 using namespace std;
41 using namespace lyx::support;
42
43 namespace lyx {
44
45 namespace Alert = frontend::Alert;
46 namespace os = support::os;
47
48
49 InsetBibtex::InsetBibtex(InsetCommandParams const & p)
50         : InsetCommand(p, "bibtex")
51 {}
52
53
54 ParamInfo const & InsetBibtex::findInfo(string const & /* cmdName */)
55 {
56         static ParamInfo param_info_;
57         if (param_info_.empty()) {
58                 param_info_.add("btprint", ParamInfo::LATEX_OPTIONAL);
59                 param_info_.add("bibfiles", ParamInfo::LATEX_REQUIRED);
60                 param_info_.add("options", ParamInfo::LYX_INTERNAL);
61         }
62         return param_info_;
63 }
64
65
66 void InsetBibtex::doDispatch(Cursor & cur, FuncRequest & cmd)
67 {
68         switch (cmd.action) {
69
70         case LFUN_INSET_MODIFY: {
71                 InsetCommandParams p(BIBTEX_CODE);
72                 try {
73                         if (!InsetCommand::string2params("bibtex", 
74                                         to_utf8(cmd.argument()), p)) {
75                                 cur.noUpdate();
76                                 break;
77                         }
78                 } catch (ExceptionMessage const & message) {
79                         if (message.type_ == WarningException) {
80                                 Alert::warning(message.title_, message.details_);
81                                 cur.noUpdate();
82                         } else 
83                                 throw message;
84                         break;
85                 }
86                 //
87                 setParams(p);
88                 buffer().updateBibfilesCache();
89                 break;
90         }
91
92         default:
93                 InsetCommand::doDispatch(cur, cmd);
94                 break;
95         }
96 }
97
98
99 docstring InsetBibtex::screenLabel() const
100 {
101         return _("BibTeX Generated Bibliography");
102 }
103
104
105 docstring InsetBibtex::toolTip(BufferView const & /*bv*/, int /*x*/, int /*y*/) const
106 {
107         docstring item = from_ascii("* ");
108         docstring tip = _("Databases:\n");
109         vector<docstring> bibfilelist = getVectorFromString(getParam("bibfiles"));
110
111         if (bibfilelist.empty()) {
112                 tip += item;
113                 tip += _("none");
114         } else {
115                 vector<docstring>::const_iterator it = bibfilelist.begin();
116                 vector<docstring>::const_iterator en = bibfilelist.end();
117                 for (; it != en; ++it) {
118                         tip += item;
119                         tip += *it + "\n";
120                 }
121         }
122
123         // Style-Options
124         bool toc = false;
125         docstring style = getParam("options"); // maybe empty! and with bibtotoc
126         docstring bibtotoc = from_ascii("bibtotoc");
127         if (prefixIs(style, bibtotoc)) {
128                 toc = true;
129                 if (contains(style, char_type(',')))
130                         style = split(style, bibtotoc, char_type(','));
131         }
132
133         tip += _("Style File:\n");
134         tip += item;
135         if (!style.empty())
136                 tip += style;
137         else
138                 tip += _("none");
139
140         tip += _("\nLists: ");
141         docstring btprint = getParam("btprint");
142                 if (btprint == "btPrintAll")
143                         tip += _("all references");
144                 else if (btprint == "btPrintNotCited")
145                         tip += _("all uncited references");
146                 else
147                         tip += _("all cited references");
148         
149         if (toc) {
150                 tip += ", ";
151                 tip += _("included in TOC");
152         }
153
154         return tip;
155 }
156
157
158 static string normalizeName(Buffer const & buffer,
159         OutputParams const & runparams, string const & name, string const & ext)
160 {
161         string const fname = makeAbsPath(name, buffer.filePath()).absFilename();
162         if (FileName(name).isAbsolute() || !FileName(fname + ext).isReadableFile())
163                 return name;
164         if (!runparams.nice)
165                 return fname;
166
167         // FIXME UNICODE
168         return to_utf8(makeRelPath(from_utf8(fname),
169                                          from_utf8(buffer.masterBuffer()->filePath())));
170 }
171
172
173 int InsetBibtex::latex(odocstream & os, OutputParams const & runparams) const
174 {
175         // the sequence of the commands:
176         // 1. \bibliographystyle{style}
177         // 2. \addcontentsline{...} - if option bibtotoc set
178         // 3. \bibliography{database}
179         // and with bibtopic:
180         // 1. \bibliographystyle{style}
181         // 2. \begin{btSect}{database}
182         // 3. \btPrint{Cited|NotCited|All}
183         // 4. \end{btSect}
184
185         // Database(s)
186         // If we are processing the LaTeX file in a temp directory then
187         // copy the .bib databases to this temp directory, mangling their
188         // names in the process. Store this mangled name in the list of
189         // all databases.
190         // (We need to do all this because BibTeX *really*, *really*
191         // can't handle "files with spaces" and Windows users tend to
192         // use such filenames.)
193         // Otherwise, store the (maybe absolute) path to the original,
194         // unmangled database name.
195         vector<docstring> bibfilelist = getVectorFromString(getParam("bibfiles"));
196         vector<docstring>::const_iterator it = bibfilelist.begin();
197         vector<docstring>::const_iterator en = bibfilelist.end();
198         odocstringstream dbs;
199         bool didone = false;
200
201         for (; it != en; ++it) {
202                 string utf8input = to_utf8(*it);
203                 string database =
204                         normalizeName(buffer(), runparams, utf8input, ".bib");
205                 FileName const try_in_file =
206                         makeAbsPath(database + ".bib", buffer().filePath());
207                 bool const not_from_texmf = try_in_file.isReadableFile();
208
209                 if (!runparams.inComment && !runparams.dryrun && !runparams.nice &&
210                     not_from_texmf) {
211
212                         // mangledFilename() needs the extension
213                         DocFileName const in_file = DocFileName(try_in_file);
214                         database = removeExtension(in_file.mangledFilename());
215                         FileName const out_file = makeAbsPath(database + ".bib",
216                                         buffer().masterBuffer()->temppath());
217
218                         bool const success = in_file.copyTo(out_file);
219                         if (!success) {
220                                 lyxerr << "Failed to copy '" << in_file
221                                        << "' to '" << out_file << "'"
222                                        << endl;
223                         }
224                 } else if (!runparams.inComment && runparams.nice && not_from_texmf &&
225                            !isValidLaTeXFilename(database)) {
226                                 frontend::Alert::warning(_("Invalid filename"),
227                                                          _("The following filename is likely to cause trouble "
228                                                            "when running the exported file through LaTeX: ") +
229                                                             from_utf8(database));
230                 }
231
232                 if (didone)
233                         dbs << ',';
234                 else 
235                         didone = true;
236                 // FIXME UNICODE
237                 dbs << from_utf8(latex_path(database));
238         }
239         docstring const db_out = dbs.str();
240
241         // Post this warning only once.
242         static bool warned_about_spaces = false;
243         if (!warned_about_spaces &&
244             runparams.nice && db_out.find(' ') != docstring::npos) {
245                 warned_about_spaces = true;
246
247                 Alert::warning(_("Export Warning!"),
248                                _("There are spaces in the paths to your BibTeX databases.\n"
249                                               "BibTeX will be unable to find them."));
250         }
251         // Style-Options
252         string style = to_utf8(getParam("options")); // maybe empty! and with bibtotoc
253         string bibtotoc;
254         if (prefixIs(style, "bibtotoc")) {
255                 bibtotoc = "bibtotoc";
256                 if (contains(style, ','))
257                         style = split(style, bibtotoc, ',');
258         }
259
260
261         // line count
262         int nlines = 0;
263
264         if (!style.empty()) {
265                 string base = normalizeName(buffer(), runparams, style, ".bst");
266                 FileName const try_in_file = 
267                         makeAbsPath(base + ".bst", buffer().filePath());
268                 bool const not_from_texmf = try_in_file.isReadableFile();
269                 // If this style does not come from texmf and we are not
270                 // exporting to .tex copy it to the tmp directory.
271                 // This prevents problems with spaces and 8bit charcaters
272                 // in the file name.
273                 if (!runparams.inComment && !runparams.dryrun && !runparams.nice &&
274                     not_from_texmf) {
275                         // use new style name
276                         DocFileName const in_file = DocFileName(try_in_file);
277                         base = removeExtension(in_file.mangledFilename());
278                         FileName const out_file = makeAbsPath(base + ".bst",
279                                         buffer().masterBuffer()->temppath());
280                         bool const success = in_file.copyTo(out_file);
281                         if (!success) {
282                                 lyxerr << "Failed to copy '" << in_file
283                                        << "' to '" << out_file << "'"
284                                        << endl;
285                         }
286                 }
287                 // FIXME UNICODE
288                 os << "\\bibliographystyle{"
289                    << from_utf8(latex_path(normalizeName(buffer(), runparams, base, ".bst")))
290                    << "}\n";
291                 nlines += 1;
292         }
293
294         // Post this warning only once.
295         static bool warned_about_bst_spaces = false;
296         if (!warned_about_bst_spaces && runparams.nice && contains(style, ' ')) {
297                 warned_about_bst_spaces = true;
298                 Alert::warning(_("Export Warning!"),
299                                _("There are spaces in the path to your BibTeX style file.\n"
300                                               "BibTeX will be unable to find it."));
301         }
302
303         if (!db_out.empty() && buffer().params().use_bibtopic) {
304                 os << "\\begin{btSect}{" << db_out << "}\n";
305                 docstring btprint = getParam("btprint");
306                 if (btprint.empty())
307                         // default
308                         btprint = from_ascii("btPrintCited");
309                 os << "\\" << btprint << "\n"
310                    << "\\end{btSect}\n";
311                 nlines += 3;
312         }
313
314         // bibtotoc-Option
315         if (!bibtotoc.empty() && !buffer().params().use_bibtopic) {
316                 if (buffer().params().documentClass().hasLaTeXLayout("chapter")) {
317                         if (buffer().params().sides == OneSide) {
318                                 // oneside
319                                 os << "\\clearpage";
320                         } else {
321                                 // twoside
322                                 os << "\\cleardoublepage";
323                         }
324                         os << "\\addcontentsline{toc}{chapter}{\\bibname}";
325                 } else if (buffer().params().documentClass().hasLaTeXLayout("section"))
326                         os << "\\addcontentsline{toc}{section}{\\refname}";
327         }
328
329         if (!db_out.empty() && !buffer().params().use_bibtopic) {
330                 docstring btprint = getParam("btprint");
331                 if (btprint == "btPrintAll") {
332                         os << "\\nocite{*}\n";
333                         nlines += 1;
334                 }
335                 os << "\\bibliography{" << db_out << "}\n";
336                 nlines += 1;
337         }
338
339         return nlines;
340 }
341
342
343 support::FileNameList InsetBibtex::getBibFiles() const
344 {
345         FileName path(buffer().filePath());
346         support::PathChanger p(path);
347         
348         support::FileNameList vec;
349         
350         vector<docstring> bibfilelist = getVectorFromString(getParam("bibfiles"));
351         vector<docstring>::const_iterator it = bibfilelist.begin();
352         vector<docstring>::const_iterator en = bibfilelist.end();
353         for (; it != en; ++it) {
354                 FileName const file = 
355                         findtexfile(changeExtension(to_utf8(*it), "bib"), "bib");
356                 
357                 // If we didn't find a matching file name just fail silently
358                 if (!file.empty())
359                         vec.push_back(file);
360         }
361         
362         return vec;
363
364 }
365
366 namespace {
367
368         // methods for parsing bibtex files
369
370         typedef map<docstring, docstring> VarMap;
371
372         /// remove whitespace characters, optionally a single comma,
373         /// and further whitespace characters from the stream.
374         /// @return true if a comma was found, false otherwise
375         ///
376         bool removeWSAndComma(idocfstream & ifs) {
377                 char_type ch;
378
379                 if (!ifs)
380                         return false;
381
382                 // skip whitespace
383                 do {
384                         ifs.get(ch);
385                 } while (ifs && isSpace(ch));
386
387                 if (!ifs)
388                         return false;
389
390                 if (ch != ',') {
391                         ifs.putback(ch);
392                         return false;
393                 }
394
395                 // skip whitespace
396                 do {
397                         ifs.get(ch);
398                 } while (ifs && isSpace(ch));
399
400                 if (ifs) {
401                         ifs.putback(ch);
402                 }
403
404                 return true;
405         }
406
407
408         enum charCase {
409                 makeLowerCase,
410                 keepCase
411         };
412
413         /// remove whitespace characters, read characer sequence
414         /// not containing whitespace characters or characters in
415         /// delimChars, and remove further whitespace characters.
416         ///
417         /// @return true if a string of length > 0 could be read.
418         ///
419         bool readTypeOrKey(docstring & val, idocfstream & ifs,
420                 docstring const & delimChars, docstring const &illegalChars, 
421                 charCase chCase) {
422
423                 char_type ch;
424
425                 val.clear();
426
427                 if (!ifs)
428                         return false;
429
430                 // skip whitespace
431                 do {
432                         ifs.get(ch);
433                 } while (ifs && isSpace(ch));
434
435                 if (!ifs)
436                         return false;
437
438                 // read value
439                 bool legalChar = true;
440                 while (ifs && !isSpace(ch) && 
441                                                  delimChars.find(ch) == docstring::npos &&
442                                                  (legalChar = (illegalChars.find(ch) == docstring::npos))
443                                         ) 
444                 {
445                         if (chCase == makeLowerCase)
446                                 val += lowercase(ch);
447                         else
448                                 val += ch;
449                         ifs.get(ch);
450                 }
451                 
452                 if (!legalChar) {
453                         ifs.putback(ch);
454                         return false;
455                 }
456
457                 // skip whitespace
458                 while (ifs && isSpace(ch)) {
459                         ifs.get(ch);
460                 }
461
462                 if (ifs) {
463                         ifs.putback(ch);
464                 }
465
466                 return val.length() > 0;
467         }
468
469         /// read subsequent bibtex values that are delimited with a #-character.
470         /// Concatenate all parts and replace names with the associated string in
471         /// the variable strings.
472         /// @return true if reading was successfull (all single parts were delimited
473         /// correctly)
474         bool readValue(docstring & val, idocfstream & ifs, const VarMap & strings) {
475
476                 char_type ch;
477
478                 val.clear();
479
480                 if (!ifs)
481                         return false;
482
483                 do {
484                         // skip whitespace
485                         do {
486                                 ifs.get(ch);
487                         } while (ifs && isSpace(ch));
488
489                         if (!ifs)
490                                 return false;
491
492                         // check for field type
493                         if (isDigit(ch)) {
494
495                                 // read integer value
496                                 do {
497                                         val += ch;
498                                         ifs.get(ch);
499                                 } while (ifs && isDigit(ch));
500
501                                 if (!ifs)
502                                         return false;
503
504                         } else if (ch == '"' || ch == '{') {
505                                 // set end delimiter
506                                 char_type delim = ch == '"' ? '"': '}';
507
508                                 //Skip whitespace
509                                 do {
510                                         ifs.get(ch);
511                                 } while (ifs && isSpace(ch));
512                                 
513                                 if (!ifs)
514                                         return false;
515                                 
516                                 //We now have the first non-whitespace character
517                                 //We'll collapse adjacent whitespace.
518                                 bool lastWasWhiteSpace = false;
519                                 
520                                 // inside this delimited text braces must match.
521                                 // Thus we can have a closing delimiter only
522                                 // when nestLevel == 0
523                                 int nestLevel = 0;
524  
525                                 while (ifs && (nestLevel > 0 || ch != delim)) {
526                                         if (isSpace(ch)) {
527                                                 lastWasWhiteSpace = true;
528                                                 ifs.get(ch);
529                                                 continue;
530                                         }
531                                         //We output the space only after we stop getting 
532                                         //whitespace so as not to output any whitespace
533                                         //at the end of the value.
534                                         if (lastWasWhiteSpace) {
535                                                 lastWasWhiteSpace = false;
536                                                 val += ' ';
537                                         }
538                                         
539                                         val += ch;
540
541                                         // update nesting level
542                                         switch (ch) {
543                                                 case '{':
544                                                         ++nestLevel;
545                                                         break;
546                                                 case '}':
547                                                         --nestLevel;
548                                                         if (nestLevel < 0) return false;
549                                                         break;
550                                         }
551
552                                         ifs.get(ch);
553                                 }
554
555                                 if (!ifs)
556                                         return false;
557
558                                 ifs.get(ch);
559
560                                 if (!ifs)
561                                         return false;
562
563                         } else {
564
565                                 // reading a string name
566                                 docstring strName;
567
568                                 while (ifs && !isSpace(ch) && ch != '#' && ch != ',' && ch != '}' && ch != ')') {
569                                         strName += lowercase(ch);
570                                         ifs.get(ch);
571                                 }
572
573                                 if (!ifs)
574                                         return false;
575
576                                 // replace the string with its assigned value or
577                                 // discard it if it's not assigned
578                                 if (strName.length()) {
579                                         VarMap::const_iterator pos = strings.find(strName);
580                                         if (pos != strings.end()) {
581                                                 val += pos->second;
582                                         }
583                                 }
584                         }
585
586                         // skip WS
587                         while (ifs && isSpace(ch)) {
588                                 ifs.get(ch);
589                         }
590
591                         if (!ifs)
592                                 return false;
593
594                         // continue reading next value on concatenate with '#'
595                 } while (ch == '#');
596
597                 ifs.putback(ch);
598
599                 return true;
600         }
601 }
602
603
604 // This method returns a comma separated list of Bibtex entries
605 void InsetBibtex::fillWithBibKeys(BiblioInfo & keylist,
606         InsetIterator const & /*di*/) const
607 {
608         // This bibtex parser is a first step to parse bibtex files
609         // more precisely.
610         //
611         // - it reads the whole bibtex entry and does a syntax check
612         //   (matching delimiters, missing commas,...
613         // - it recovers from errors starting with the next @-character
614         // - it reads @string definitions and replaces them in the
615         //   field values.
616         // - it accepts more characters in keys or value names than
617         //   bibtex does.
618         //
619         // Officially bibtex does only support ASCII, but in practice
620         // you can use the encoding of the main document as long as
621         // some elements like keys and names are pure ASCII. Therefore
622         // we convert the file from the buffer encoding.
623         // We don't restrict keys to ASCII in LyX, since our own
624         // InsetBibitem can generate non-ASCII keys, and nonstandard
625         // 8bit clean bibtex forks exist.
626         support::FileNameList const files = getBibFiles();
627         support::FileNameList::const_iterator it = files.begin();
628         support::FileNameList::const_iterator en = files.end();
629         for (; it != en; ++ it) {
630                 idocfstream ifs(it->toFilesystemEncoding().c_str(),
631                         ios_base::in, buffer().params().encoding().iconvName());
632
633                 char_type ch;
634                 VarMap strings;
635
636                 while (ifs) {
637
638                         ifs.get(ch);
639                         if (!ifs)
640                                 break;
641
642                         if (ch != '@')
643                                 continue;
644
645                         docstring entryType;
646
647                         if (!readTypeOrKey(entryType, ifs, from_ascii("{("), 
648                                            docstring(), makeLowerCase) || !ifs)
649                                 continue;
650
651                         if (entryType == from_ascii("comment")) {
652
653                                 ifs.ignore(numeric_limits<int>::max(), '\n');
654                                 continue;
655                         }
656
657                         ifs.get(ch);
658                         if (!ifs)
659                                 break;
660
661                         if ((ch != '(') && (ch != '{')) {
662                                 // invalid entry delimiter
663                                 ifs.putback(ch);
664                                 continue;
665                         }
666
667                         // process the entry
668                         if (entryType == from_ascii("string")) {
669
670                                 // read string and add it to the strings map
671                                 // (or replace it's old value)
672                                 docstring name;
673                                 docstring value;
674
675                                 if (!readTypeOrKey(name, ifs, from_ascii("="), 
676                                                    from_ascii("#{}(),"), makeLowerCase) || !ifs)
677                                         continue;
678
679                                 // next char must be an equal sign
680                                 ifs.get(ch);
681                                 if (!ifs || ch != '=')
682                                         continue;
683
684                                 if (!readValue(value, ifs, strings))
685                                         continue;
686
687                                 strings[name] = value;
688
689                         } else if (entryType == from_ascii("preamble")) {
690
691                                 // preamble definitions are discarded.
692                                 // can they be of any use in lyx?
693                                 docstring value;
694
695                                 if (!readValue(value, ifs, strings))
696                                         continue;
697
698                         } else {
699
700                                 // Citation entry. Try to read the key.
701                                 docstring key;
702
703                                 if (!readTypeOrKey(key, ifs, from_ascii(","), 
704                                                    from_ascii("}"), keepCase) || !ifs)
705                                         continue;
706
707                                 /////////////////////////////////////////////
708                                 // now we have a key, so we will add an entry 
709                                 // (even if it's empty, as bibtex does)
710                                 //
711                                 // we now read the field = value pairs.
712                                 // all items must be separated by a comma. If
713                                 // it is missing the scanning of this entry is
714                                 // stopped and the next is searched.
715                                 docstring fields;
716                                 docstring name;
717                                 docstring value;
718                                 docstring commaNewline;
719                                 docstring data;
720                                 BibTeXInfo keyvalmap(key, entryType);
721                                 
722                                 bool readNext = removeWSAndComma(ifs);
723  
724                                 while (ifs && readNext) {
725
726                                         // read field name
727                                         if (!readTypeOrKey(name, ifs, from_ascii("="), 
728                                                            from_ascii("{}(),"), makeLowerCase) || !ifs)
729                                                 break;
730
731                                         // next char must be an equal sign
732                                         ifs.get(ch);
733                                         if (!ifs)
734                                                 break;
735                                         if (ch != '=') {
736                                                 ifs.putback(ch);
737                                                 break;
738                                         }
739
740                                         // read field value
741                                         if (!readValue(value, ifs, strings))
742                                                 break;
743
744                                         keyvalmap[name] = value;
745                                         data += "\n\n" + value;
746                                         keylist.addFieldName(name);
747                                         readNext = removeWSAndComma(ifs);
748                                 }
749
750                                 // add the new entry
751                                 keylist.addEntryType(entryType);
752                                 keyvalmap.setAllData(data);
753                                 keylist[key] = keyvalmap;
754                         }
755                 } //< searching '@'
756         } //< for loop over files
757 }
758
759
760 FileName InsetBibtex::getBibTeXPath(docstring const & filename, Buffer const & buf)
761 {
762         string texfile = changeExtension(to_utf8(filename), "bib");
763         // note that, if the filename can be found directly from the path, 
764         // findtexfile will just return a FileName object for that path.
765         FileName file(findtexfile(texfile, "bib"));
766         if (file.empty())
767                 file = FileName(makeAbsPath(texfile, buf.filePath()));
768         return file;
769 }
770  
771
772 bool InsetBibtex::addDatabase(docstring const & db)
773 {
774         docstring bibfiles = getParam("bibfiles");
775         if (tokenPos(bibfiles, ',', db) != -1)
776                 return false;
777         if (!bibfiles.empty())
778                 bibfiles += ',';
779         setParam("bibfiles", bibfiles + db);
780         return true;
781 }
782
783
784 bool InsetBibtex::delDatabase(docstring const & db)
785 {
786         docstring bibfiles = getParam("bibfiles");
787         if (contains(bibfiles, db)) {
788                 int const n = tokenPos(bibfiles, ',', db);
789                 docstring bd = db;
790                 if (n > 0) {
791                         // this is not the first database
792                         docstring tmp = ',' + bd;
793                         setParam("bibfiles", subst(bibfiles, tmp, docstring()));
794                 } else if (n == 0)
795                         // this is the first (or only) database
796                         setParam("bibfiles", split(bibfiles, bd, ','));
797                 else
798                         return false;
799         }
800         return true;
801 }
802
803
804 void InsetBibtex::validate(LaTeXFeatures & features) const
805 {
806         if (features.bufferParams().use_bibtopic)
807                 features.require("bibtopic");
808 }
809
810
811 } // namespace lyx