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"
15 #include "ConverterCache.h"
17 #include "buffer_funcs.h"
18 #include "BufferParams.h"
19 #include "ErrorList.h"
25 #include "frontends/alert.h"
27 #include "support/debug.h"
28 #include "support/FileNameList.h"
29 #include "support/filetools.h"
30 #include "support/gettext.h"
31 #include "support/lstrings.h"
32 #include "support/os.h"
33 #include "support/Package.h"
34 #include "support/Path.h"
35 #include "support/Systemcall.h"
38 using namespace lyx::support
;
42 namespace Alert
= lyx::frontend::Alert
;
47 string
const token_from("$$i");
48 string
const token_base("$$b");
49 string
const token_to("$$o");
50 string
const token_path("$$p");
51 string
const token_orig_path("$$r");
55 string
const add_options(string
const & command
, string
const & options
)
58 string
const tail
= split(command
, head
, ' ');
59 return head
+ ' ' + options
+ ' ' + tail
;
63 string
const dvipdfm_options(BufferParams
const & bp
)
67 if (bp
.papersize
!= PAPER_CUSTOM
) {
68 string
const paper_size
= bp
.paperSizeName(BufferParams::DVIPDFM
);
69 if (!paper_size
.empty())
70 result
= "-p "+ paper_size
;
72 if (bp
.orientation
== ORIENTATION_LANDSCAPE
)
80 class ConverterEqual
{
82 ConverterEqual(string
const & from
, string
const & to
)
83 : from_(from
), to_(to
) {}
84 bool operator()(Converter
const & c
) const {
85 return c
.from
== from_
&& c
.to
== to_
;
95 Converter::Converter(string
const & f
, string
const & t
,
96 string
const & c
, string
const & l
)
97 : from(f
), to(t
), command(c
), flags(l
),
98 From(0), To(0), latex(false), xml(false),
103 void Converter::readFlags()
105 string
flag_list(flags
);
106 while (!flag_list
.empty()) {
107 string flag_name
, flag_value
;
108 flag_list
= split(flag_list
, flag_value
, ',');
109 flag_value
= split(flag_value
, flag_name
, '=');
110 if (flag_name
== "latex")
112 else if (flag_name
== "xml")
114 else if (flag_name
== "needaux")
116 else if (flag_name
== "resultdir")
117 result_dir
= (flag_value
.empty())
118 ? token_base
: flag_value
;
119 else if (flag_name
== "resultfile")
120 result_file
= flag_value
;
121 else if (flag_name
== "parselog")
122 parselog
= flag_value
;
124 if (!result_dir
.empty() && result_file
.empty())
125 result_file
= "index." + formats
.extension(to
);
126 //if (!contains(command, token_from))
131 bool operator<(Converter
const & a
, Converter
const & b
)
133 // use the compare_ascii_no_case instead of compare_no_case,
134 // because in turkish, 'i' is not the lowercase version of 'I',
135 // and thus turkish locale breaks parsing of tags.
136 int const i
= compare_ascii_no_case(a
.From
->prettyname(),
137 b
.From
->prettyname());
139 return compare_ascii_no_case(a
.To
->prettyname(),
140 b
.To
->prettyname()) < 0;
146 Converter
const * Converters::getConverter(string
const & from
,
147 string
const & to
) const
149 ConverterList::const_iterator
const cit
=
150 find_if(converterlist_
.begin(), converterlist_
.end(),
151 ConverterEqual(from
, to
));
152 if (cit
!= converterlist_
.end())
159 int Converters::getNumber(string
const & from
, string
const & to
) const
161 ConverterList::const_iterator
const cit
=
162 find_if(converterlist_
.begin(), converterlist_
.end(),
163 ConverterEqual(from
, to
));
164 if (cit
!= converterlist_
.end())
165 return distance(converterlist_
.begin(), cit
);
171 void Converters::add(string
const & from
, string
const & to
,
172 string
const & command
, string
const & flags
)
176 ConverterList::iterator it
= find_if(converterlist_
.begin(),
177 converterlist_
.end(),
178 ConverterEqual(from
, to
));
180 Converter
converter(from
, to
, command
, flags
);
181 if (it
!= converterlist_
.end() && !flags
.empty() && flags
[0] == '*') {
183 converter
.command
= command
;
184 converter
.flags
= flags
;
186 converter
.readFlags();
188 if (converter
.latex
&& (latex_command_
.empty() || to
== "dvi"))
189 latex_command_
= subst(command
, token_from
, "");
190 // If we have both latex & pdflatex, we set latex_command to latex.
191 // The latex_command is used to update the .aux file when running
192 // a converter that uses it.
194 if (it
== converterlist_
.end()) {
195 converterlist_
.push_back(converter
);
197 converter
.From
= it
->From
;
198 converter
.To
= it
->To
;
204 void Converters::erase(string
const & from
, string
const & to
)
206 ConverterList::iterator
const it
=
207 find_if(converterlist_
.begin(),
208 converterlist_
.end(),
209 ConverterEqual(from
, to
));
210 if (it
!= converterlist_
.end())
211 converterlist_
.erase(it
);
215 // This method updates the pointers From and To in all the converters.
216 // The code is not very efficient, but it doesn't matter as the number
217 // of formats and converters is small.
218 // Furthermore, this method is called only on startup, or after
219 // adding/deleting a format in FormPreferences (the latter calls can be
220 // eliminated if the formats in the Formats class are stored using a map or
221 // a list (instead of a vector), but this will cause other problems).
222 void Converters::update(Formats
const & formats
)
224 ConverterList::iterator it
= converterlist_
.begin();
225 ConverterList::iterator end
= converterlist_
.end();
226 for (; it
!= end
; ++it
) {
227 it
->From
= formats
.getFormat(it
->from
);
228 it
->To
= formats
.getFormat(it
->to
);
233 // This method updates the pointers From and To in the last converter.
234 // It is called when adding a new converter in FormPreferences
235 void Converters::updateLast(Formats
const & formats
)
237 if (converterlist_
.begin() != converterlist_
.end()) {
238 ConverterList::iterator it
= converterlist_
.end() - 1;
239 it
->From
= formats
.getFormat(it
->from
);
240 it
->To
= formats
.getFormat(it
->to
);
245 void Converters::sort()
247 std::sort(converterlist_
.begin(), converterlist_
.end());
251 OutputParams::FLAVOR
Converters::getFlavor(Graph::EdgePath
const & path
)
253 for (Graph::EdgePath::const_iterator cit
= path
.begin();
254 cit
!= path
.end(); ++cit
) {
255 Converter
const & conv
= converterlist_
[*cit
];
257 if (contains(conv
.from
, "xetex"))
258 return OutputParams::XETEX
;
259 if (contains(conv
.to
, "pdf"))
260 return OutputParams::PDFLATEX
;
262 return OutputParams::XML
;
264 return OutputParams::LATEX
;
268 bool Converters::convert(Buffer
const * buffer
,
269 FileName
const & from_file
, FileName
const & to_file
,
270 FileName
const & orig_from
,
271 string
const & from_format
, string
const & to_format
,
272 ErrorList
& errorList
, int conversionflags
)
274 if (from_format
== to_format
)
275 return move(from_format
, from_file
, to_file
, false);
277 if ((conversionflags
& try_cache
) &&
278 ConverterCache::get().inCache(orig_from
, to_format
))
279 return ConverterCache::get().copy(orig_from
, to_format
, to_file
);
281 Graph::EdgePath edgepath
= getPath(from_format
, to_format
);
282 if (edgepath
.empty()) {
283 if (conversionflags
& try_default
) {
284 // if no special converter defined, then we take the
285 // default one from ImageMagic.
286 string
const from_ext
= from_format
.empty() ?
287 getExtension(from_file
.absFilename()) :
288 formats
.extension(from_format
);
289 string
const to_ext
= formats
.extension(to_format
);
290 string
const command
=
292 quoteName(libFileSearch("scripts", "convertDefault.py").toFilesystemEncoding()) +
294 quoteName(from_ext
+ ':' + from_file
.toFilesystemEncoding()) +
296 quoteName(to_ext
+ ':' + to_file
.toFilesystemEncoding());
297 LYXERR(Debug::FILES
, "No converter defined! "
298 "I use convertDefault.py:\n\t" << command
);
300 one
.startscript(Systemcall::Wait
, command
);
301 if (to_file
.isReadableFile()) {
302 if (conversionflags
& try_cache
)
303 ConverterCache::get().add(orig_from
,
309 // only warn once per session and per file type
310 static std::map
<string
, string
> warned
;
311 if (warned
.find(from_format
) != warned
.end() && warned
.find(from_format
)->second
== to_format
) {
314 warned
.insert(make_pair(from_format
, to_format
));
316 Alert::error(_("Cannot convert file"),
317 bformat(_("No information for converting %1$s "
318 "format files to %2$s.\n"
319 "Define a converter in the preferences."),
320 from_ascii(from_format
), from_ascii(to_format
)));
324 // buffer is only invalid for importing, and then runparams is not
326 OutputParams
runparams(buffer
? &buffer
->params().encoding() : 0);
327 runparams
.flavor
= getFlavor(edgepath
);
330 runparams
.use_japanese
= buffer
->bufferFormat() == "platex";
331 runparams
.use_indices
= buffer
->params().use_indices
;
332 runparams
.bibtex_command
= (buffer
->params().bibtex_command
== "default") ?
333 string() : buffer
->params().bibtex_command
;
334 runparams
.index_command
= (buffer
->params().index_command
== "default") ?
335 string() : buffer
->params().index_command
;
338 // Some converters (e.g. lilypond) can only output files to the
339 // current directory, so we need to change the current directory.
340 // This has the added benefit that all other files that may be
341 // generated by the converter are deleted when LyX closes and do not
342 // clutter the real working directory.
343 string
const path(onlyPath(from_file
.absFilename()));
344 // Prevent the compiler from optimizing away p
348 // empty the error list before any new conversion takes place.
351 bool run_latex
= false;
352 string from_base
= changeExtension(from_file
.absFilename(), "");
353 string to_base
= changeExtension(to_file
.absFilename(), "");
355 FileName outfile
= from_file
;
356 for (Graph::EdgePath::const_iterator cit
= edgepath
.begin();
357 cit
!= edgepath
.end(); ++cit
) {
358 Converter
const & conv
= converterlist_
[*cit
];
359 bool dummy
= conv
.To
->dummy() && conv
.to
!= "program";
361 LYXERR(Debug::FILES
, "Converting from "
362 << conv
.from
<< " to " << conv
.to
);
365 outfile
= FileName(conv
.result_dir
.empty()
366 ? changeExtension(from_file
.absFilename(), conv
.To
->extension())
367 : addName(subst(conv
.result_dir
,
368 token_base
, from_base
),
369 subst(conv
.result_file
,
370 token_base
, onlyFilename(from_base
))));
372 // if input and output files are equal, we use a
373 // temporary file as intermediary (JMarc)
374 FileName real_outfile
;
375 if (outfile
== infile
) {
376 real_outfile
= infile
;
377 // when importing, a buffer does not necessarily exist
379 outfile
= FileName(addName(buffer
->temppath(), "tmpfile.out"));
381 outfile
= FileName(addName(package().temp_dir().absFilename(),
387 string
const command
= subst(conv
.command
, token_from
, "");
388 LYXERR(Debug::FILES
, "Running " << command
);
389 if (!runLaTeX(*buffer
, command
, runparams
, errorList
))
392 if (conv
.need_aux
&& !run_latex
393 && !latex_command_
.empty()) {
394 LYXERR(Debug::FILES
, "Running " << latex_command_
395 << " to update aux file");
396 runLaTeX(*buffer
, latex_command_
, runparams
, errorList
);
400 string
const infile2
=
401 to_utf8(makeRelPath(from_utf8(infile
.absFilename()), from_utf8(path
)));
402 string
const outfile2
=
403 to_utf8(makeRelPath(from_utf8(outfile
.absFilename()), from_utf8(path
)));
405 string command
= conv
.command
;
406 command
= subst(command
, token_from
, quoteName(infile2
));
407 command
= subst(command
, token_base
, quoteName(from_base
));
408 command
= subst(command
, token_to
, quoteName(outfile2
));
409 command
= subst(command
, token_path
, quoteName(infile
.onlyPath().absFilename()));
410 command
= subst(command
, token_orig_path
, quoteName(orig_from
.onlyPath().absFilename()));
411 command
= libScriptSearch(command
);
413 if (!conv
.parselog
.empty())
414 command
+= " 2> " + quoteName(infile2
+ ".out");
416 if (conv
.from
== "dvi" && conv
.to
== "ps")
417 command
= add_options(command
,
418 buffer
->params().dvips_options());
419 else if (conv
.from
== "dvi" && prefixIs(conv
.to
, "pdf"))
420 command
= add_options(command
,
421 dvipdfm_options(buffer
->params()));
423 LYXERR(Debug::FILES
, "Calling " << command
);
425 buffer
->message(_("Executing command: ")
426 + from_utf8(command
));
431 res
= one
.startscript(Systemcall::DontWait
,
432 to_filesystem8bit(from_utf8(command
)));
433 // We're not waiting for the result, so we can't do anything
436 res
= one
.startscript(Systemcall::Wait
,
437 to_filesystem8bit(from_utf8(command
)));
438 if (!real_outfile
.empty()) {
439 Mover
const & mover
= getMover(conv
.to
);
440 if (!mover
.rename(outfile
, real_outfile
))
443 LYXERR(Debug::FILES
, "renaming file " << outfile
444 << " to " << real_outfile
);
445 // Finally, don't forget to tell any future
446 // converters to use the renamed file...
447 outfile
= real_outfile
;
450 if (!conv
.parselog
.empty()) {
451 string
const logfile
= infile2
+ ".log";
452 string
const script
= libScriptSearch(conv
.parselog
);
453 string
const command2
= script
+
454 " < " + quoteName(infile2
+ ".out") +
455 " > " + quoteName(logfile
);
456 one
.startscript(Systemcall::Wait
,
457 to_filesystem8bit(from_utf8(command2
)));
458 if (!scanLog(*buffer
, command
, makeAbsPath(logfile
, path
), errorList
))
464 if (conv
.to
== "program") {
465 Alert::error(_("Build errors"),
466 _("There were errors during the build process."));
468 // FIXME: this should go out of here. For example, here we cannot say if
469 // it is a document (.lyx) or something else. Same goes for elsewhere.
470 Alert::error(_("Cannot convert file"),
471 bformat(_("An error occurred whilst running %1$s"),
472 from_utf8(command
.substr(0, 50))));
479 Converter
const & conv
= converterlist_
[edgepath
.back()];
480 if (conv
.To
->dummy())
483 if (!conv
.result_dir
.empty()) {
484 // The converter has put the file(s) in a directory.
485 // In this case we ignore the given to_file.
486 if (from_base
!= to_base
) {
487 string
const from
= subst(conv
.result_dir
,
488 token_base
, from_base
);
489 string
const to
= subst(conv
.result_dir
,
490 token_base
, to_base
);
491 Mover
const & mover
= getMover(conv
.from
);
492 if (!mover
.rename(FileName(from
), FileName(to
))) {
493 Alert::error(_("Cannot convert file"),
494 bformat(_("Could not move a temporary directory from %1$s to %2$s."),
495 from_utf8(from
), from_utf8(to
)));
501 if (conversionflags
& try_cache
)
502 ConverterCache::get().add(orig_from
, to_format
, outfile
);
503 return move(conv
.to
, outfile
, to_file
, conv
.latex
);
508 bool Converters::move(string
const & fmt
,
509 FileName
const & from
, FileName
const & to
, bool copy
)
514 bool no_errors
= true;
515 string
const path
= onlyPath(from
.absFilename());
516 string
const base
= onlyFilename(removeExtension(from
.absFilename()));
517 string
const to_base
= removeExtension(to
.absFilename());
518 string
const to_extension
= getExtension(to
.absFilename());
520 support::FileNameList
const files
= FileName(path
).dirList(getExtension(from
.absFilename()));
521 for (support::FileNameList::const_iterator it
= files
.begin();
522 it
!= files
.end(); ++it
) {
523 string
const from2
= it
->absFilename();
524 string
const file2
= onlyFilename(from2
);
525 if (prefixIs(file2
, base
)) {
526 string
const to2
= changeExtension(
527 to_base
+ file2
.substr(base
.length()),
529 LYXERR(Debug::FILES
, "moving " << from2
<< " to " << to2
);
531 Mover
const & mover
= getMover(fmt
);
532 bool const moved
= copy
533 ? mover
.copy(*it
, FileName(to2
))
534 : mover
.rename(*it
, FileName(to2
));
535 if (!moved
&& no_errors
) {
536 Alert::error(_("Cannot convert file"),
538 _("Could not copy a temporary file from %1$s to %2$s.") :
539 _("Could not move a temporary file from %1$s to %2$s."),
540 from_utf8(from2
), from_utf8(to2
)));
549 bool Converters::formatIsUsed(string
const & format
)
551 ConverterList::const_iterator cit
= converterlist_
.begin();
552 ConverterList::const_iterator end
= converterlist_
.end();
553 for (; cit
!= end
; ++cit
) {
554 if (cit
->from
== format
|| cit
->to
== format
)
561 bool Converters::scanLog(Buffer
const & buffer
, string
const & /*command*/,
562 FileName
const & filename
, ErrorList
& errorList
)
564 OutputParams
runparams(0);
565 runparams
.flavor
= OutputParams::LATEX
;
566 LaTeX
latex("", runparams
, filename
);
568 int const result
= latex
.scanLogFile(terr
);
570 if (result
& LaTeX::ERRORS
)
571 buffer
.bufferErrors(terr
, errorList
);
580 : public boost::signals::trackable
{
582 ShowMessage(Buffer
const & b
) : buffer_(b
) {}
583 void operator()(docstring
const & msg
) const { buffer_
.message(msg
); }
585 Buffer
const & buffer_
;
591 bool Converters::runLaTeX(Buffer
const & buffer
, string
const & command
,
592 OutputParams
const & runparams
, ErrorList
& errorList
)
594 buffer
.setBusy(true);
595 buffer
.message(_("Running LaTeX..."));
597 runparams
.document_language
= buffer
.params().language
->babel();
599 // do the LaTeX run(s)
600 string
const name
= buffer
.latexName();
601 LaTeX
latex(command
, runparams
, FileName(makeAbsPath(name
)));
603 ShowMessage
show(buffer
);
604 latex
.message
.connect(show
);
605 int const result
= latex
.run(terr
);
607 if (result
& LaTeX::ERRORS
)
608 buffer
.bufferErrors(terr
, errorList
);
610 // check return value from latex.run().
611 if ((result
& LaTeX::NO_LOGFILE
)) {
612 docstring
const str
=
613 bformat(_("LaTeX did not run successfully. "
614 "Additionally, LyX could not locate "
615 "the LaTeX log %1$s."), from_utf8(name
));
616 Alert::error(_("LaTeX failed"), str
);
617 } else if (result
& LaTeX::NO_OUTPUT
) {
618 Alert::warning(_("Output is empty"),
619 _("An empty output file was generated."));
623 buffer
.setBusy(false);
625 int const ERROR_MASK
=
630 return (result
& ERROR_MASK
) == 0;
636 void Converters::buildGraph()
638 G_
.init(formats
.size());
639 ConverterList::iterator beg
= converterlist_
.begin();
640 ConverterList::iterator
const end
= converterlist_
.end();
641 for (ConverterList::iterator it
= beg
; it
!= end
; ++it
) {
642 int const s
= formats
.getNumber(it
->from
);
643 int const t
= formats
.getNumber(it
->to
);
649 vector
<Format
const *> const
650 Converters::intToFormat(vector
<int> const & input
)
652 vector
<Format
const *> result(input
.size());
654 vector
<int>::const_iterator it
= input
.begin();
655 vector
<int>::const_iterator
const end
= input
.end();
656 vector
<Format
const *>::iterator rit
= result
.begin();
657 for ( ; it
!= end
; ++it
, ++rit
) {
658 *rit
= &formats
.get(*it
);
664 vector
<Format
const *> const
665 Converters::getReachableTo(string
const & target
, bool const clear_visited
)
667 vector
<int> const & reachablesto
=
668 G_
.getReachableTo(formats
.getNumber(target
), clear_visited
);
670 return intToFormat(reachablesto
);
674 vector
<Format
const *> const
675 Converters::getReachable(string
const & from
, bool const only_viewable
,
676 bool const clear_visited
)
678 vector
<int> const & reachables
=
679 G_
.getReachable(formats
.getNumber(from
),
683 return intToFormat(reachables
);
687 bool Converters::isReachable(string
const & from
, string
const & to
)
689 return G_
.isReachable(formats
.getNumber(from
),
690 formats
.getNumber(to
));
694 Graph::EdgePath
Converters::getPath(string
const & from
, string
const & to
)
696 return G_
.getPath(formats
.getNumber(from
),
697 formats
.getNumber(to
));
701 vector
<Format
const *> Converters::importableFormats()
703 vector
<string
> l
= loaders();
704 vector
<Format
const *> result
= getReachableTo(l
[0], true);
705 for (vector
<string
>::const_iterator it
= l
.begin() + 1;
706 it
!= l
.end(); ++it
) {
707 vector
<Format
const *> r
= getReachableTo(*it
, false);
708 result
.insert(result
.end(), r
.begin(), r
.end());
714 vector
<Format
const *> Converters::exportableFormats(bool only_viewable
)
716 vector
<string
> s
= savers();
717 vector
<Format
const *> result
= getReachable(s
[0], only_viewable
, true);
718 for (vector
<string
>::const_iterator it
= s
.begin() + 1;
719 it
!= s
.end(); ++it
) {
720 vector
<Format
const *> r
=
721 getReachable(*it
, only_viewable
, false);
722 result
.insert(result
.end(), r
.begin(), r
.end());
728 vector
<string
> Converters::loaders() const
733 v
.push_back("textparagraph");
738 vector
<string
> Converters::savers() const
741 v
.push_back("docbook");
742 v
.push_back("latex");
743 v
.push_back("literate");
745 v
.push_back("xhtml");
746 v
.push_back("pdflatex");
747 v
.push_back("platex");
749 v
.push_back("xetex");