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