3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
8 * Full author contact details are available in file CREDITS.
13 #include "converter.h"
16 #include "buffer_funcs.h"
17 #include "bufferparams.h"
25 #include "frontends/Alert.h"
27 #include "support/filetools.h"
28 #include "support/lyxlib.h"
29 #include "support/path.h"
30 #include "support/systemcall.h"
32 using lyx::support::AddName;
33 using lyx::support::bformat;
34 using lyx::support::ChangeExtension;
35 using lyx::support::compare_ascii_no_case;
36 using lyx::support::contains;
37 using lyx::support::DirList;
38 using lyx::support::GetExtension;
39 using lyx::support::LibScriptSearch;
40 using lyx::support::MakeRelPath;
41 using lyx::support::OnlyFilename;
42 using lyx::support::OnlyPath;
43 using lyx::support::Path;
44 using lyx::support::prefixIs;
45 using lyx::support::QuoteName;
46 using lyx::support::split;
47 using lyx::support::subst;
48 using lyx::support::Systemcall;
59 string const token_from("$$i");
60 string const token_base("$$b");
61 string const token_to("$$o");
62 string const token_path("$$p");
66 string const add_options(string const & command, string const & options)
69 string const tail = split(command, head, ' ');
70 return head + ' ' + options + ' ' + tail;
74 string const dvipdfm_options(BufferParams const & bp)
78 if (bp.papersize2 != VM_PAPER_CUSTOM) {
79 string const paper_size = bp.paperSizeName();
80 if (paper_size != "b5" && paper_size != "foolscap")
81 result = "-p "+ paper_size;
83 if (bp.orientation == ORIENTATION_LANDSCAPE)
91 class ConverterEqual : public std::binary_function<string, string, bool> {
93 ConverterEqual(string const & from, string const & to)
94 : from_(from), to_(to) {}
95 bool operator()(Converter const & c) const {
96 return c.from == from_ && c.to == to_;
106 Converter::Converter(string const & f, string const & t,
107 string const & c, string const & l)
108 : from(f), to(t), command(c), flags(l),
109 From(0), To(0), latex(false), xml(false),
110 original_dir(false), need_aux(false)
114 void Converter::readFlags()
116 string flag_list(flags);
117 while (!flag_list.empty()) {
118 string flag_name, flag_value;
119 flag_list = split(flag_list, flag_value, ',');
120 flag_value = split(flag_value, flag_name, '=');
121 if (flag_name == "latex")
123 else if (flag_name == "xml")
125 else if (flag_name == "originaldir")
127 else if (flag_name == "needaux")
129 else if (flag_name == "resultdir")
130 result_dir = (flag_value.empty())
131 ? token_base : flag_value;
132 else if (flag_name == "resultfile")
133 result_file = flag_value;
134 else if (flag_name == "parselog")
135 parselog = flag_value;
137 if (!result_dir.empty() && result_file.empty())
138 result_file = "index." + formats.extension(to);
139 //if (!contains(command, token_from))
144 bool operator<(Converter const & a, Converter const & b)
146 // use the compare_ascii_no_case instead of compare_no_case,
147 // because in turkish, 'i' is not the lowercase version of 'I',
148 // and thus turkish locale breaks parsing of tags.
149 int const i = compare_ascii_no_case(a.From->prettyname(),
150 b.From->prettyname());
152 return compare_ascii_no_case(a.To->prettyname(),
153 b.To->prettyname()) < 0;
159 Converter const * Converters::getConverter(string const & from,
160 string const & to) const
162 ConverterList::const_iterator const cit =
163 find_if(converterlist_.begin(), converterlist_.end(),
164 ConverterEqual(from, to));
165 if (cit != converterlist_.end())
172 int Converters::getNumber(string const & from, string const & to) const
174 ConverterList::const_iterator const cit =
175 find_if(converterlist_.begin(), converterlist_.end(),
176 ConverterEqual(from, to));
177 if (cit != converterlist_.end())
178 return distance(converterlist_.begin(), cit);
184 void Converters::add(string const & from, string const & to,
185 string const & command, string const & flags)
189 ConverterList::iterator it = find_if(converterlist_.begin(),
190 converterlist_.end(),
191 ConverterEqual(from , to));
193 Converter converter(from, to, command, flags);
194 if (it != converterlist_.end() && !flags.empty() && flags[0] == '*') {
196 converter.command = command;
197 converter.flags = flags;
199 converter.readFlags();
201 if (converter.latex && (latex_command_.empty() || to == "dvi"))
202 latex_command_ = subst(command, token_from, "");
203 // If we have both latex & pdflatex, we set latex_command to latex.
204 // The latex_command is used to update the .aux file when running
205 // a converter that uses it.
207 if (it == converterlist_.end()) {
208 converterlist_.push_back(converter);
210 converter.From = it->From;
211 converter.To = it->To;
217 void Converters::erase(string const & from, string const & to)
219 ConverterList::iterator const it =
220 find_if(converterlist_.begin(),
221 converterlist_.end(),
222 ConverterEqual(from, to));
223 if (it != converterlist_.end())
224 converterlist_.erase(it);
228 // This method updates the pointers From and To in all the converters.
229 // The code is not very efficient, but it doesn't matter as the number
230 // of formats and converters is small.
231 // Furthermore, this method is called only on startup, or after
232 // adding/deleting a format in FormPreferences (the latter calls can be
233 // eliminated if the formats in the Formats class are stored using a map or
234 // a list (instead of a vector), but this will cause other problems).
235 void Converters::update(Formats const & formats)
237 ConverterList::iterator it = converterlist_.begin();
238 ConverterList::iterator end = converterlist_.end();
239 for (; it != end; ++it) {
240 it->From = formats.getFormat(it->from);
241 it->To = formats.getFormat(it->to);
246 // This method updates the pointers From and To in the last converter.
247 // It is called when adding a new converter in FormPreferences
248 void Converters::updateLast(Formats const & formats)
250 if (converterlist_.begin() != converterlist_.end()) {
251 ConverterList::iterator it = converterlist_.end() - 1;
252 it->From = formats.getFormat(it->from);
253 it->To = formats.getFormat(it->to);
258 void Converters::sort()
260 std::sort(converterlist_.begin(), converterlist_.end());
264 OutputParams::FLAVOR Converters::getFlavor(Graph::EdgePath const & path)
266 for (Graph::EdgePath::const_iterator cit = path.begin();
267 cit != path.end(); ++cit) {
268 Converter const & conv = converterlist_[*cit];
270 if (contains(conv.to, "pdf"))
271 return OutputParams::PDFLATEX;
273 return OutputParams::XML;
275 return OutputParams::LATEX;
279 bool Converters::convert(Buffer const * buffer,
280 string const & from_file, string const & to_file_base,
281 string const & from_format, string const & to_format,
284 to_file = ChangeExtension(to_file_base,
285 formats.extension(to_format));
287 if (from_format == to_format)
288 return move(from_format, from_file, to_file, false);
290 Graph::EdgePath edgepath = getPath(from_format, to_format);
291 if (edgepath.empty()) {
294 OutputParams runparams;
295 runparams.flavor = getFlavor(edgepath);
296 string path = OnlyPath(from_file);
299 bool run_latex = false;
300 string from_base = ChangeExtension(from_file, "");
301 string to_base = ChangeExtension(to_file, "");
303 string outfile = from_file;
304 for (Graph::EdgePath::const_iterator cit = edgepath.begin();
305 cit != edgepath.end(); ++cit) {
306 Converter const & conv = converterlist_[*cit];
307 bool dummy = conv.To->dummy() && conv.to != "program";
309 lyxerr[Debug::FILES] << "Converting from "
310 << conv.from << " to " << conv.to << endl;
312 outfile = conv.result_dir.empty()
313 ? ChangeExtension(from_file, conv.To->extension())
314 : AddName(subst(conv.result_dir,
315 token_base, from_base),
316 subst(conv.result_file,
317 token_base, OnlyFilename(from_base)));
319 // if input and output files are equal, we use a
320 // temporary file as intermediary (JMarc)
322 if (outfile == infile) {
323 real_outfile = infile;
324 outfile = AddName(buffer->temppath(), "tmpfile.out");
329 string const command = subst(conv.command, token_from, "");
330 lyxerr[Debug::FILES] << "Running " << command << endl;
331 if (!runLaTeX(*buffer, command, runparams))
334 if (conv.need_aux && !run_latex
335 && !latex_command_.empty()) {
337 << "Running " << latex_command_
338 << " to update aux file"<< endl;
339 runLaTeX(*buffer, latex_command_, runparams);
342 string const infile2 = (conv.original_dir)
343 ? infile : MakeRelPath(infile, path);
344 string const outfile2 = (conv.original_dir)
345 ? outfile : MakeRelPath(outfile, path);
347 string command = conv.command;
348 command = subst(command, token_from, QuoteName(infile2));
349 command = subst(command, token_base, QuoteName(from_base));
350 command = subst(command, token_to, QuoteName(outfile2));
351 command = LibScriptSearch(command);
353 if (!conv.parselog.empty())
354 command += " 2> " + QuoteName(infile2 + ".out");
356 if (conv.from == "dvi" && conv.to == "ps")
357 command = add_options(command,
358 buffer->params().dvips_options());
359 else if (conv.from == "dvi" && prefixIs(conv.to, "pdf"))
360 command = add_options(command,
361 dvipdfm_options(buffer->params()));
363 lyxerr[Debug::FILES] << "Calling " << command << endl;
365 buffer->message(_("Executing command: ")
368 Systemcall::Starttype const type = (dummy)
369 ? Systemcall::DontWait : Systemcall::Wait;
372 if (conv.original_dir) {
373 Path p(buffer->filePath());
374 res = one.startscript(type, command);
376 res = one.startscript(type, command);
378 if (!real_outfile.empty()) {
379 Mover const & mover = movers(conv.to);
380 if (!mover.rename(outfile, real_outfile))
384 << "renaming file " << outfile
385 << " to " << real_outfile
389 if (!conv.parselog.empty()) {
390 string const logfile = infile2 + ".log";
391 string const script = LibScriptSearch(conv.parselog);
392 string const command2 = script +
393 " < " + QuoteName(infile2 + ".out") +
394 " > " + QuoteName(logfile);
395 one.startscript(Systemcall::Wait, command2);
396 if (!scanLog(*buffer, command, logfile))
401 if (conv.to == "program") {
402 Alert::error(_("Build errors"),
403 _("There were errors during the build process."));
405 // FIXME: this should go out of here. For example, here we cannot say if
406 // it is a document (.lyx) or something else. Same goes for elsewhere.
407 Alert::error(_("Cannot convert file"),
408 bformat(_("An error occurred whilst running %1$s"),
409 command.substr(0, 50)));
416 Converter const & conv = converterlist_[edgepath.back()];
417 if (conv.To->dummy())
420 if (!conv.result_dir.empty()) {
421 to_file = AddName(subst(conv.result_dir, token_base, to_base),
422 subst(conv.result_file,
423 token_base, OnlyFilename(to_base)));
424 if (from_base != to_base) {
425 string const from = subst(conv.result_dir,
426 token_base, from_base);
427 string const to = subst(conv.result_dir,
428 token_base, to_base);
429 Mover const & mover = movers(conv.from);
430 if (!mover.rename(from, to)) {
431 Alert::error(_("Cannot convert file"),
432 bformat(_("Could not move a temporary file from %1$s to %2$s."),
439 return move(conv.to, outfile, to_file, conv.latex);
443 bool Converters::move(string const & fmt,
444 string const & from, string const & to, bool copy)
449 bool no_errors = true;
450 string const path = OnlyPath(from);
451 string const base = OnlyFilename(ChangeExtension(from, ""));
452 string const to_base = ChangeExtension(to, "");
453 string const to_extension = GetExtension(to);
455 vector<string> files = DirList(OnlyPath(from), GetExtension(from));
456 for (vector<string>::const_iterator it = files.begin();
457 it != files.end(); ++it)
458 if (prefixIs(*it, base)) {
459 string const from2 = path + *it;
460 string to2 = to_base + it->substr(base.length());
461 to2 = ChangeExtension(to2, to_extension);
462 lyxerr[Debug::FILES] << "moving " << from2
463 << " to " << to2 << endl;
465 Mover const & mover = movers(fmt);
466 bool const moved = copy
467 ? mover.copy(from2, to2)
468 : mover.rename(from2, to2);
469 if (!moved && no_errors) {
470 Alert::error(_("Cannot convert file"),
472 _("Could not copy a temporary file from %1$s to %2$s.") :
473 _("Could not move a temporary file from %1$s to %2$s."),
482 bool Converters::convert(Buffer const * buffer,
483 string const & from_file, string const & to_file_base,
484 string const & from_format, string const & to_format)
487 return convert(buffer, from_file, to_file_base, from_format, to_format,
492 bool Converters::formatIsUsed(string const & format)
494 ConverterList::const_iterator cit = converterlist_.begin();
495 ConverterList::const_iterator end = converterlist_.end();
496 for (; cit != end; ++cit) {
497 if (cit->from == format || cit->to == format)
504 bool Converters::scanLog(Buffer const & buffer, string const & /*command*/,
505 string const & filename)
507 OutputParams runparams;
508 runparams.flavor = OutputParams::LATEX;
509 LaTeX latex("", runparams, filename, "");
511 int const result = latex.scanLogFile(terr);
513 if (result & LaTeX::ERRORS)
514 bufferErrors(buffer, terr);
522 class showMessage : public std::unary_function<string, void>, public boost::signals::trackable {
524 showMessage(Buffer const & b) : buffer_(b) {};
525 void operator()(string const & m) const
530 Buffer const & buffer_;
536 bool Converters::runLaTeX(Buffer const & buffer, string const & command,
537 OutputParams const & runparams)
540 buffer.message(_("Running LaTeX..."));
542 runparams.document_language = buffer.params().language->babel();
544 // do the LaTeX run(s)
545 string const name = buffer.getLatexName();
546 LaTeX latex(command, runparams, name, buffer.filePath());
548 showMessage show(buffer);
549 latex.message.connect(show);
550 int const result = latex.run(terr);
552 if (result & LaTeX::ERRORS)
553 bufferErrors(buffer, terr);
555 // check return value from latex.run().
556 if ((result & LaTeX::NO_LOGFILE)) {
558 bformat(_("LaTeX did not run successfully. "
559 "Additionally, LyX could not locate "
560 "the LaTeX log %1$s."), name);
561 Alert::error(_("LaTeX failed"), str);
562 } else if (result & LaTeX::NO_OUTPUT) {
563 Alert::warning(_("Output is empty"),
564 _("An empty output file was generated."));
570 int const ERROR_MASK =
575 return (result & ERROR_MASK) == 0;
581 void Converters::buildGraph()
583 G_.init(formats.size());
584 ConverterList::iterator beg = converterlist_.begin();
585 ConverterList::iterator const end = converterlist_.end();
586 for (ConverterList::iterator it = beg; it != end ; ++it) {
587 int const s = formats.getNumber(it->from);
588 int const t = formats.getNumber(it->to);
594 std::vector<Format const *> const
595 Converters::intToFormat(std::vector<int> const & input)
597 vector<Format const *> result(input.size());
599 vector<int>::const_iterator it = input.begin();
600 vector<int>::const_iterator const end = input.end();
601 vector<Format const *>::iterator rit = result.begin();
602 for ( ; it != end; ++it, ++rit) {
603 *rit = &formats.get(*it);
609 vector<Format const *> const
610 Converters::getReachableTo(string const & target, bool const clear_visited)
612 vector<int> const & reachablesto =
613 G_.getReachableTo(formats.getNumber(target), clear_visited);
615 return intToFormat(reachablesto);
619 vector<Format const *> const
620 Converters::getReachable(string const & from, bool const only_viewable,
621 bool const clear_visited)
623 vector<int> const & reachables =
624 G_.getReachable(formats.getNumber(from),
628 return intToFormat(reachables);
632 bool Converters::isReachable(string const & from, string const & to)
634 return G_.isReachable(formats.getNumber(from),
635 formats.getNumber(to));
639 Graph::EdgePath const
640 Converters::getPath(string const & from, string const & to)
642 return G_.getPath(formats.getNumber(from),
643 formats.getNumber(to));
647 /// The global instance
648 Converters converters;
650 // The global copy after reading lyxrc.defaults
651 Converters system_converters;