1 /**
2  * \file Converter.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Dekel Tsur
7  *
8  * Full author contact details are available in file CREDITS.
9  */
10 
11 #include <config.h>
12 
13 #include "Converter.h"
14 
15 #include "Buffer.h"
16 #include "buffer_funcs.h"
17 #include "BufferParams.h"
18 #include "ConverterCache.h"
19 #include "Encoding.h"
20 #include "ErrorList.h"
21 #include "Format.h"
22 #include "InsetList.h"
23 #include "Language.h"
24 #include "LaTeX.h"
25 #include "LyXRC.h"
26 #include "Mover.h"
27 #include "ParagraphList.h"
28 #include "Session.h"
29 
30 #include "frontends/alert.h"
31 
32 #include "insets/InsetInclude.h"
33 
34 #include "support/debug.h"
35 #include "support/FileNameList.h"
36 #include "support/filetools.h"
37 #include "support/gettext.h"
38 #include "support/lassert.h"
39 #include "support/lstrings.h"
40 #include "support/os.h"
41 #include "support/Package.h"
42 #include "support/PathChanger.h"
43 #include "support/Systemcall.h"
44 
45 using namespace std;
46 using namespace lyx::support;
47 
48 namespace lyx {
49 
50 namespace Alert = lyx::frontend::Alert;
51 
52 
53 namespace {
54 
55 string const token_from("$$i");
56 string const token_base("$$b");
57 string const token_to("$$o");
58 string const token_path("$$p");
59 string const token_orig_path("$$r");
60 string const token_orig_from("$$f");
61 string const token_encoding("$$e");
62 string const token_latex_encoding("$$E");
63 
64 
add_options(string const & command,string const & options)65 string const add_options(string const & command, string const & options)
66 {
67 	string head;
68 	string const tail = split(command, head, ' ');
69 	return head + ' ' + options + ' ' + tail;
70 }
71 
72 
dvipdfm_options(BufferParams const & bp)73 string const dvipdfm_options(BufferParams const & bp)
74 {
75 	string result;
76 
77 	if (bp.papersize != PAPER_CUSTOM) {
78 		string const paper_size = bp.paperSizeName(BufferParams::DVIPDFM);
79 		if (!paper_size.empty())
80 			result = "-p "+ paper_size;
81 
82 		if (bp.orientation == ORIENTATION_LANDSCAPE)
83 			result += " -l";
84 	}
85 
86 	return result;
87 }
88 
89 
90 class ConverterEqual {
91 public:
ConverterEqual(string const & from,string const & to)92 	ConverterEqual(string const & from, string const & to)
93 		: from_(from), to_(to) {}
operator ()(Converter const & c) const94 	bool operator()(Converter const & c) const {
95 		return c.from() == from_ && c.to() == to_;
96 	}
97 private:
98 	string const from_;
99 	string const to_;
100 };
101 
102 } // namespace
103 
104 
Converter(string const & f,string const & t,string const & c,string const & l)105 Converter::Converter(string const & f, string const & t,
106 		     string const & c, string const & l)
107 	: from_(f), to_(t), command_(c), flags_(l),
108 	  From_(0), To_(0), latex_(false), xml_(false),
109 	  need_aux_(false), nice_(false), need_auth_(false)
110 {}
111 
112 
readFlags()113 void Converter::readFlags()
114 {
115 	string flag_list(flags_);
116 	while (!flag_list.empty()) {
117 		string flag_name, flag_value;
118 		flag_list = split(flag_list, flag_value, ',');
119 		flag_value = split(flag_value, flag_name, '=');
120 		if (flag_name == "latex") {
121 			latex_ = true;
122 			latex_flavor_ = flag_value.empty() ?
123 				"latex" : flag_value;
124 		} else if (flag_name == "xml")
125 			xml_ = true;
126 		else if (flag_name == "needaux")
127 			need_aux_ = true;
128 		else if (flag_name == "resultdir")
129 			result_dir_ = (flag_value.empty())
130 				? token_base : flag_value;
131 		else if (flag_name == "resultfile")
132 			result_file_ = flag_value;
133 		else if (flag_name == "parselog")
134 			parselog_ = flag_value;
135 		else if (flag_name == "nice")
136 			nice_ = true;
137 		else if (flag_name == "needauth")
138 			need_auth_ = true;
139 		else if (flag_name == "hyperref-driver")
140 			href_driver_ = flag_value;
141 	}
142 	if (!result_dir_.empty() && result_file_.empty())
143 		result_file_ = "index." + theFormats().extension(to_);
144 	//if (!contains(command, token_from))
145 	//	latex = true;
146 }
147 
148 
getConverter(string const & from,string const & to) const149 Converter const * Converters::getConverter(string const & from,
150 					    string const & to) const
151 {
152 	ConverterList::const_iterator const cit =
153 		find_if(converterlist_.begin(), converterlist_.end(),
154 			ConverterEqual(from, to));
155 	if (cit != converterlist_.end())
156 		return &(*cit);
157 	else
158 		return 0;
159 }
160 
161 
getNumber(string const & from,string const & to) const162 int Converters::getNumber(string const & from, string const & to) const
163 {
164 	ConverterList::const_iterator const cit =
165 		find_if(converterlist_.begin(), converterlist_.end(),
166 			ConverterEqual(from, to));
167 	if (cit != converterlist_.end())
168 		return distance(converterlist_.begin(), cit);
169 	else
170 		return -1;
171 }
172 
173 
add(string const & from,string const & to,string const & command,string const & flags)174 void Converters::add(string const & from, string const & to,
175 		     string const & command, string const & flags)
176 {
177 	theFormats().add(from);
178 	theFormats().add(to);
179 	ConverterList::iterator it = find_if(converterlist_.begin(),
180 					     converterlist_.end(),
181 					     ConverterEqual(from , to));
182 
183 	Converter converter(from, to, command, flags);
184 	if (it != converterlist_.end() && !flags.empty() && flags[0] == '*') {
185 		converter = *it;
186 		converter.setCommand(command);
187 		converter.setFlags(flags);
188 	}
189 	converter.readFlags();
190 
191 	// The latex_command is used to update the .aux file when running
192 	// a converter that uses it.
193 	if (converter.latex()) {
194 		if (latex_command_.empty() ||
195 		    converter.latex_flavor() == "latex")
196 			latex_command_ = subst(command, token_from, "");
197 		if (dvilualatex_command_.empty() ||
198 		    converter.latex_flavor() == "dvilualatex")
199 			dvilualatex_command_ = subst(command, token_from, "");
200 		if (lualatex_command_.empty() ||
201 		    converter.latex_flavor() == "lualatex")
202 			lualatex_command_ = subst(command, token_from, "");
203 		if (pdflatex_command_.empty() ||
204 		    converter.latex_flavor() == "pdflatex")
205 			pdflatex_command_ = subst(command, token_from, "");
206 		if (xelatex_command_.empty() ||
207 		    converter.latex_flavor() == "xelatex")
208 			xelatex_command_ = subst(command, token_from, "");
209 	}
210 
211 	if (it == converterlist_.end()) {
212 		converterlist_.push_back(converter);
213 	} else {
214 		converter.setFrom(it->From());
215 		converter.setTo(it->To());
216 		*it = converter;
217 	}
218 }
219 
220 
erase(string const & from,string const & to)221 void Converters::erase(string const & from, string const & to)
222 {
223 	ConverterList::iterator const it =
224 		find_if(converterlist_.begin(),
225 			converterlist_.end(),
226 			ConverterEqual(from, to));
227 	if (it != converterlist_.end())
228 		converterlist_.erase(it);
229 }
230 
231 
232 // This method updates the pointers From and To in all the converters.
233 // The code is not very efficient, but it doesn't matter as the number
234 // of formats and converters is small.
235 // Furthermore, this method is called only on startup, or after
236 // adding/deleting a format in FormPreferences (the latter calls can be
237 // eliminated if the formats in the Formats class are stored using a map or
238 // a list (instead of a vector), but this will cause other problems).
update(Formats const & formats)239 void Converters::update(Formats const & formats)
240 {
241 	ConverterList::iterator it = converterlist_.begin();
242 	ConverterList::iterator end = converterlist_.end();
243 	for (; it != end; ++it) {
244 		it->setFrom(formats.getFormat(it->from()));
245 		it->setTo(formats.getFormat(it->to()));
246 	}
247 }
248 
249 
250 // This method updates the pointers From and To in the last converter.
251 // It is called when adding a new converter in FormPreferences
updateLast(Formats const & formats)252 void Converters::updateLast(Formats const & formats)
253 {
254 	if (converterlist_.begin() != converterlist_.end()) {
255 		ConverterList::iterator it = converterlist_.end() - 1;
256 		it->setFrom(formats.getFormat(it->from()));
257 		it->setTo(formats.getFormat(it->to()));
258 	}
259 }
260 
261 
getFlavor(Graph::EdgePath const & path,Buffer const * buffer)262 OutputParams::FLAVOR Converters::getFlavor(Graph::EdgePath const & path,
263 					   Buffer const * buffer)
264 {
265 	for (Graph::EdgePath::const_iterator cit = path.begin();
266 	     cit != path.end(); ++cit) {
267 		Converter const & conv = converterlist_[*cit];
268 		if (conv.latex()) {
269 			if (conv.latex_flavor() == "latex")
270 				return OutputParams::LATEX;
271 			if (conv.latex_flavor() == "xelatex")
272 				return OutputParams::XETEX;
273 			if (conv.latex_flavor() == "lualatex")
274 				return OutputParams::LUATEX;
275 			if (conv.latex_flavor() == "dvilualatex")
276 				return OutputParams::DVILUATEX;
277 			if (conv.latex_flavor() == "pdflatex")
278 				return OutputParams::PDFLATEX;
279 		}
280 		if (conv.xml())
281 			return OutputParams::XML;
282 	}
283 	return buffer ? buffer->params().getOutputFlavor()
284 		      : OutputParams::LATEX;
285 }
286 
287 
getHyperrefDriver(Graph::EdgePath const & path)288 string Converters::getHyperrefDriver(Graph::EdgePath const & path)
289 {
290 	for (Graph::EdgePath::const_iterator cit = path.begin();
291 	     cit != path.end(); ++cit) {
292 		Converter const & conv = converterlist_[*cit];
293 		if (!conv.hyperref_driver().empty())
294 			return conv.hyperref_driver();
295 	}
296 	return string();
297 }
298 
299 
checkAuth(Converter const & conv,string const & doc_fname,bool use_shell_escape)300 bool Converters::checkAuth(Converter const & conv, string const & doc_fname,
301 			   bool use_shell_escape)
302 {
303 	string conv_command = conv.command();
304 	bool const has_shell_escape = contains(conv_command, "-shell-escape")
305 				|| contains(conv_command, "-enable-write18");
306 	if (conv.latex() && has_shell_escape && !use_shell_escape) {
307 		docstring const shellescape_warning =
308 		      bformat(_("<p>The following LaTeX backend has been "
309 		        "configured to allow execution of external programs "
310 		        "for any document:</p>"
311 		        "<center><p><tt>%1$s</tt></p></center>"
312 		        "<p>This is a dangerous configuration. Please, "
313 		        "consider using the support offered by LyX for "
314 		        "allowing this privilege only to documents that "
315 			"actually need it, instead.</p>"),
316 		        from_utf8(conv_command));
317 		frontend::Alert::error(_("Security Warning"),
318 					shellescape_warning , false);
319 	} else if (!conv.latex())
320 		use_shell_escape = false;
321 	if (!conv.need_auth() && !use_shell_escape)
322 		return true;
323 	size_t const token_pos = conv_command.find("$$");
324 	bool const has_token = token_pos != string::npos;
325 	string const command = use_shell_escape && !has_shell_escape
326 		? (has_token ? conv_command.insert(token_pos, "-shell-escape ")
327 			     : conv_command.append(" -shell-escape"))
328 		: conv_command;
329 	docstring const security_warning = (use_shell_escape
330 	    ? bformat(_("<p>The following LaTeX backend has been requested "
331 	        "to allow execution of external programs:</p>"
332 	        "<center><p><tt>%1$s</tt></p></center>"
333 	        "<p>The external programs can execute arbitrary commands on "
334 	        "your system, including dangerous ones, if instructed to do "
335 	        "so by a maliciously crafted LyX document.</p>"),
336 	      from_utf8(command))
337 	    : bformat(_("<p>The requested operation requires the use of a "
338 	        "converter from %2$s to %3$s:</p>"
339 	        "<blockquote><p><tt>%1$s</tt></p></blockquote>"
340 	        "<p>This external program can execute arbitrary commands on "
341 	        "your system, including dangerous ones, if instructed to do "
342 	        "so by a maliciously crafted LyX document.</p>"),
343 	      from_utf8(command), from_utf8(conv.from()),
344 	      from_utf8(conv.to())));
345 	if (lyxrc.use_converter_needauth_forbidden && !use_shell_escape) {
346 		frontend::Alert::error(
347 		    _("An external converter is disabled for security reasons"),
348 		    security_warning + _(
349 		    "<p><b>Your current preference settings forbid its execution.</b></p>"
350 		    "<p>(To change this setting, go to <i>Preferences &#x25b9; File "
351 		    "Handling &#x25b9; Converters</i> and uncheck <i>Security &#x25b9; "
352 		    "Forbid needauth converters</i>.)"), false);
353 		return false;
354 	}
355 	if (!lyxrc.use_converter_needauth && !use_shell_escape)
356 		return true;
357 	docstring const security_title = use_shell_escape
358 		? _("A LaTeX backend requires your authorization")
359 		: _("An external converter requires your authorization");
360 	int choice;
361 	docstring const security_warning2 = security_warning + (use_shell_escape
362 		? _("<p>Should LaTeX backends be allowed to run external "
363 		    "programs?</p><p><b>Allow them only if you trust the "
364 		    "origin/sender of the LyX document!</b></p>")
365 		: _("<p>Would you like to run this converter?</p>"
366 		    "<p><b>Only run if you trust the origin/sender of the LyX "
367 		    "document!</b></p>"));
368 	docstring const no = use_shell_escape
369 				? _("Do &not allow") : _("Do &not run");
370 	docstring const yes = use_shell_escape ? _("A&llow") : _("&Run");
371 	docstring const always = use_shell_escape
372 					? _("&Always allow for this document")
373 					: _("&Always run for this document");
374 	if (!doc_fname.empty()) {
375 		LYXERR(Debug::FILES, "looking up: " << doc_fname);
376 		bool authorized = use_shell_escape
377 			? theSession().shellescapeFiles().findAuth(doc_fname)
378 			: theSession().authFiles().find(doc_fname);
379 		if (!authorized) {
380 			choice = frontend::Alert::prompt(security_title,
381 							 security_warning2,
382 							 0, 0, no, yes, always);
383 			if (choice == 2) {
384 				if (use_shell_escape)
385 					theSession().shellescapeFiles().insert(doc_fname, true);
386 				else
387 					theSession().authFiles().insert(doc_fname);
388 			}
389 		} else {
390 			choice = 1;
391 		}
392 	} else {
393 		choice = frontend::Alert::prompt(security_title,
394 						 security_warning2,
395 						 0, 0, no, yes);
396 	}
397 	return choice != 0;
398 }
399 
400 
convert(Buffer const * buffer,FileName const & from_file,FileName const & to_file,FileName const & orig_from,string const & from_format,string const & to_format,ErrorList & errorList,int conversionflags)401 bool Converters::convert(Buffer const * buffer,
402 			 FileName const & from_file, FileName const & to_file,
403 			 FileName const & orig_from,
404 			 string const & from_format, string const & to_format,
405 			 ErrorList & errorList, int conversionflags)
406 {
407 	if (from_format == to_format)
408 		return move(from_format, from_file, to_file, false);
409 
410 	if ((conversionflags & try_cache) &&
411 	    ConverterCache::get().inCache(orig_from, to_format))
412 		return ConverterCache::get().copy(orig_from, to_format, to_file);
413 
414 	Graph::EdgePath edgepath = getPath(from_format, to_format);
415 	if (edgepath.empty()) {
416 		if (conversionflags & try_default) {
417 			// if no special converter defined, then we take the
418 			// default one from ImageMagic.
419 			string const from_ext = from_format.empty() ?
420 				getExtension(from_file.absFileName()) :
421 				theFormats().extension(from_format);
422 			string const to_ext = theFormats().extension(to_format);
423 			string const command =
424 				os::python() + ' ' +
425 				quoteName(libFileSearch("scripts", "convertDefault.py").toFilesystemEncoding()) +
426 				' ' + from_ext + ' ' +
427 				quoteName(from_file.toFilesystemEncoding()) +
428 				' ' + to_ext + ' ' +
429 				quoteName(to_file.toFilesystemEncoding());
430 			LYXERR(Debug::FILES, "No converter defined! "
431 				   "I use convertDefault.py:\n\t" << command);
432 			Systemcall one;
433 			one.startscript(Systemcall::Wait, command,
434 			                buffer ? buffer->filePath() : string(),
435 			                buffer ? buffer->layoutPos() : string());
436 			if (to_file.isReadableFile()) {
437 				if (conversionflags & try_cache)
438 					ConverterCache::get().add(orig_from,
439 							to_format, to_file);
440 				return true;
441 			}
442 		}
443 
444 		// only warn once per session and per file type
445 		static std::map<string, string> warned;
446 		if (warned.find(from_format) != warned.end() && warned.find(from_format)->second == to_format) {
447 			return false;
448 		}
449 		warned.insert(make_pair(from_format, to_format));
450 
451 		Alert::error(_("Cannot convert file"),
452 			     bformat(_("No information for converting %1$s "
453 						    "format files to %2$s.\n"
454 						    "Define a converter in the preferences."),
455 							from_ascii(from_format), from_ascii(to_format)));
456 		return false;
457 	}
458 
459 	// buffer is only invalid for importing, and then runparams is not
460 	// used anyway.
461 	OutputParams runparams(buffer ? &buffer->params().encoding() : 0);
462 	runparams.flavor = getFlavor(edgepath, buffer);
463 
464 	if (buffer) {
465 		runparams.use_japanese =
466 			(buffer->params().bufferFormat() == "latex"
467 			 || suffixIs(buffer->params().bufferFormat(), "-ja"))
468 			&& buffer->params().encoding().package() == Encoding::japanese;
469 		runparams.use_indices = buffer->params().use_indices;
470 		runparams.bibtex_command = buffer->params().bibtexCommand();
471 		runparams.index_command = (buffer->params().index_command == "default") ?
472 			string() : buffer->params().index_command;
473 		runparams.document_language = buffer->params().language->babel();
474 		runparams.only_childbibs = !buffer->params().useBiblatex()
475 				&& !buffer->params().useBibtopic()
476 				&& buffer->params().multibib == "child";
477 	}
478 
479 	// Some converters (e.g. lilypond) can only output files to the
480 	// current directory, so we need to change the current directory.
481 	// This has the added benefit that all other files that may be
482 	// generated by the converter are deleted when LyX closes and do not
483 	// clutter the real working directory.
484 	// FIXME: This does not work if path is an UNC path on windows
485 	//        (bug 6127).
486 	string const path(onlyPath(from_file.absFileName()));
487 	// Prevent the compiler from optimizing away p
488 	FileName pp(path);
489 	PathChanger p(pp);
490 
491 	// empty the error list before any new conversion takes place.
492 	errorList.clear();
493 
494 	bool run_latex = false;
495 	string from_base = changeExtension(from_file.absFileName(), "");
496 	string to_base = changeExtension(to_file.absFileName(), "");
497 	FileName infile;
498 	FileName outfile = from_file;
499 	for (Graph::EdgePath::const_iterator cit = edgepath.begin();
500 	     cit != edgepath.end(); ++cit) {
501 		Converter const & conv = converterlist_[*cit];
502 		bool dummy = conv.To()->dummy() && conv.to() != "program";
503 		if (!dummy) {
504 			LYXERR(Debug::FILES, "Converting from  "
505 			       << conv.from() << " to " << conv.to());
506 		}
507 		infile = outfile;
508 		outfile = FileName(conv.result_file().empty()
509 			? changeExtension(from_file.absFileName(), conv.To()->extension())
510 			: addName(subst(conv.result_dir(),
511 					token_base, from_base),
512 				  subst(conv.result_file(),
513 					token_base, onlyFileName(from_base))));
514 
515 		// if input and output files are equal, we use a
516 		// temporary file as intermediary (JMarc)
517 		FileName real_outfile;
518 		if (!conv.result_file().empty())
519 			real_outfile = FileName(changeExtension(from_file.absFileName(),
520 				conv.To()->extension()));
521 		if (outfile == infile) {
522 			real_outfile = infile;
523 			// when importing, a buffer does not necessarily exist
524 			if (buffer)
525 				outfile = FileName(addName(buffer->temppath(), "tmpfile.out"));
526 			else
527 				outfile = FileName(addName(package().temp_dir().absFileName(),
528 						   "tmpfile.out"));
529 		}
530 
531 		if (buffer && buffer->params().use_minted
532 		    && lyxrc.pygmentize_command.empty() && conv.latex()) {
533 			bool dowarn = false;
534 			// Warn only if listings insets are actually used
535 			for (Paragraph const & par : buffer->paragraphs()) {
536 				InsetList const & insets = par.insetList();
537 				pos_type lstpos = insets.find(LISTINGS_CODE, 0);
538 				pos_type incpos = insets.find(INCLUDE_CODE, 0);
539 				if (incpos >= 0) {
540 					InsetInclude const * include =
541 						static_cast<InsetInclude *>
542 						        (insets.get(incpos));
543 					if (include->params().getCmdName() !=
544 					                        "inputminted") {
545 						incpos = -1;
546 					}
547 				}
548 				if (lstpos >= 0 || incpos >= 0) {
549 					dowarn = true;
550 					break;
551 				}
552 			}
553 			if (dowarn) {
554 				Alert::warning(_("Pygments driver command not found!"),
555 				    _("The driver command necessary to use the minted package\n"
556 				      "(pygmentize) has not been found. Make sure you have\n"
557 				      "the python-pygments module installed or, if the driver\n"
558 				      "is named differently, to add the following line to the\n"
559 				      "document preamble:\n\n"
560 				      "\\AtBeginDocument{\\renewcommand{\\MintedPygmentize}{driver}}\n\n"
561 				      "where 'driver' is name of the driver command."));
562 			}
563 		}
564 
565 		if (!checkAuth(conv, buffer ? buffer->absFileName() : string(),
566 			       buffer && buffer->params().shell_escape))
567 			return false;
568 
569 		if (conv.latex()) {
570 			// We are not importing, we have a buffer
571 			LATTEST(buffer);
572 			run_latex = true;
573 			string command = conv.command();
574 			command = subst(command, token_from, "");
575 			command = subst(command, token_latex_encoding,
576 			                buffer->params().encoding().latexName());
577 			if (buffer->params().shell_escape
578 			    && !contains(command, "-shell-escape"))
579 				command += " -shell-escape ";
580 			LYXERR(Debug::FILES, "Running " << command);
581 			if (!runLaTeX(*buffer, command, runparams, errorList))
582 				return false;
583 		} else {
584 			if (conv.need_aux() && !run_latex) {
585 				// We are not importing, we have a buffer
586 				LATTEST(buffer);
587 				string command;
588 				switch (runparams.flavor) {
589 				case OutputParams::DVILUATEX:
590 					command = dvilualatex_command_;
591 					break;
592 				case OutputParams::LUATEX:
593 					command = lualatex_command_;
594 					break;
595 				case OutputParams::PDFLATEX:
596 					command = pdflatex_command_;
597 					break;
598 				case OutputParams::XETEX:
599 					command = xelatex_command_;
600 					break;
601 				default:
602 					command = latex_command_;
603 					break;
604 				}
605 				if (!command.empty()) {
606 					LYXERR(Debug::FILES, "Running "
607 						<< command
608 						<< " to update aux file");
609 					if (!runLaTeX(*buffer, command,
610 						      runparams, errorList))
611 						return false;
612 				}
613 			}
614 
615 			// FIXME UNICODE
616 			string const infile2 =
617 				to_utf8(makeRelPath(from_utf8(infile.absFileName()), from_utf8(path)));
618 			string const outfile2 =
619 				to_utf8(makeRelPath(from_utf8(outfile.absFileName()), from_utf8(path)));
620 
621 			string command = conv.command();
622 			command = subst(command, token_from, quoteName(infile2));
623 			command = subst(command, token_base, quoteName(from_base));
624 			command = subst(command, token_to, quoteName(outfile2));
625 			command = subst(command, token_path, quoteName(onlyPath(infile.absFileName())));
626 			command = subst(command, token_orig_path, quoteName(onlyPath(orig_from.absFileName())));
627 			command = subst(command, token_orig_from, quoteName(onlyFileName(orig_from.absFileName())));
628 			command = subst(command, token_encoding, buffer ? buffer->params().encoding().iconvName() : string());
629 
630 			if (!conv.parselog().empty())
631 				command += " 2> " + quoteName(infile2 + ".out");
632 
633 			// it is not actually not necessary to test for buffer here,
634 			// but it pleases coverity.
635 			if (buffer && conv.from() == "dvi" && conv.to() == "ps")
636 				command = add_options(command,
637 						      buffer->params().dvips_options());
638 			else if (buffer && conv.from() == "dvi" && prefixIs(conv.to(), "pdf"))
639 				command = add_options(command,
640 						      dvipdfm_options(buffer->params()));
641 
642 			LYXERR(Debug::FILES, "Calling " << command);
643 			if (buffer)
644 				buffer->message(_("Executing command: ")
645 				+ from_utf8(command));
646 
647 			Systemcall one;
648 			int res;
649 			if (dummy) {
650 				res = one.startscript(Systemcall::DontWait,
651 					to_filesystem8bit(from_utf8(command)),
652 					buffer ? buffer->filePath() : string(),
653 					buffer ? buffer->layoutPos() : string());
654 				// We're not waiting for the result, so we can't do anything
655 				// else here.
656 			} else {
657 				res = one.startscript(Systemcall::Wait,
658 						to_filesystem8bit(from_utf8(command)),
659 						buffer ? buffer->filePath()
660 						       : string(),
661 						buffer ? buffer->layoutPos()
662 						       : string());
663 				if (!real_outfile.empty()) {
664 					Mover const & mover = getMover(conv.to());
665 					if (!mover.rename(outfile, real_outfile))
666 						res = -1;
667 					else
668 						LYXERR(Debug::FILES, "renaming file " << outfile
669 							<< " to " << real_outfile);
670 					// Finally, don't forget to tell any future
671 					// converters to use the renamed file...
672 					outfile = real_outfile;
673 				}
674 
675 				if (!conv.parselog().empty()) {
676 					string const logfile =  infile2 + ".log";
677 					string const command2 = conv.parselog() +
678 						" < " + quoteName(infile2 + ".out") +
679 						" > " + quoteName(logfile);
680 					one.startscript(Systemcall::Wait,
681 						to_filesystem8bit(from_utf8(command2)),
682 						buffer->filePath(),
683 						buffer->layoutPos());
684 					if (!scanLog(*buffer, command, makeAbsPath(logfile, path), errorList))
685 						return false;
686 				}
687 			}
688 
689 			if (res) {
690 				if (conv.to() == "program") {
691 					Alert::error(_("Build errors"),
692 						_("There were errors during the build process."));
693 				} else {
694 // FIXME: this should go out of here. For example, here we cannot say if
695 // it is a document (.lyx) or something else. Same goes for elsewhere.
696 					Alert::error(_("Cannot convert file"),
697 						bformat(_("An error occurred while running:\n%1$s"),
698 						wrapParas(from_utf8(command))));
699 				}
700 				return false;
701 			}
702 		}
703 	}
704 
705 	Converter const & conv = converterlist_[edgepath.back()];
706 	if (conv.To()->dummy())
707 		return true;
708 
709 	if (!conv.result_dir().empty()) {
710 		// The converter has put the file(s) in a directory.
711 		// In this case we ignore the given to_file.
712 		if (from_base != to_base) {
713 			string const from = subst(conv.result_dir(),
714 					    token_base, from_base);
715 			string const to = subst(conv.result_dir(),
716 					  token_base, to_base);
717 			Mover const & mover = getMover(conv.from());
718 			if (!mover.rename(FileName(from), FileName(to))) {
719 				Alert::error(_("Cannot convert file"),
720 					bformat(_("Could not move a temporary directory from %1$s to %2$s."),
721 						from_utf8(from), from_utf8(to)));
722 				return false;
723 			}
724 		}
725 		return true;
726 	} else {
727 		if (conversionflags & try_cache)
728 			ConverterCache::get().add(orig_from, to_format, outfile);
729 		return move(conv.to(), outfile, to_file, conv.latex());
730 	}
731 }
732 
733 
move(string const & fmt,FileName const & from,FileName const & to,bool copy)734 bool Converters::move(string const & fmt,
735 		      FileName const & from, FileName const & to, bool copy)
736 {
737 	if (from == to)
738 		return true;
739 
740 	bool no_errors = true;
741 	string const path = onlyPath(from.absFileName());
742 	string const base = onlyFileName(removeExtension(from.absFileName()));
743 	string const to_base = removeExtension(to.absFileName());
744 	string const to_extension = getExtension(to.absFileName());
745 
746 	support::FileNameList const files = FileName(path).dirList(getExtension(from.absFileName()));
747 	for (support::FileNameList::const_iterator it = files.begin();
748 	     it != files.end(); ++it) {
749 		string const from2 = it->absFileName();
750 		string const file2 = onlyFileName(from2);
751 		if (prefixIs(file2, base)) {
752 			string const to2 = changeExtension(
753 				to_base + file2.substr(base.length()),
754 				to_extension);
755 			LYXERR(Debug::FILES, "moving " << from2 << " to " << to2);
756 
757 			Mover const & mover = getMover(fmt);
758 			bool const moved = copy
759 				? mover.copy(*it, FileName(to2))
760 				: mover.rename(*it, FileName(to2));
761 			if (!moved && no_errors) {
762 				Alert::error(_("Cannot convert file"),
763 					bformat(copy ?
764 						_("Could not copy a temporary file from %1$s to %2$s.") :
765 						_("Could not move a temporary file from %1$s to %2$s."),
766 						from_utf8(from2), from_utf8(to2)));
767 				no_errors = false;
768 			}
769 		}
770 	}
771 	return no_errors;
772 }
773 
774 
formatIsUsed(string const & format)775 bool Converters::formatIsUsed(string const & format)
776 {
777 	ConverterList::const_iterator cit = converterlist_.begin();
778 	ConverterList::const_iterator end = converterlist_.end();
779 	for (; cit != end; ++cit) {
780 		if (cit->from() == format || cit->to() == format)
781 			return true;
782 	}
783 	return false;
784 }
785 
786 
scanLog(Buffer const & buffer,string const &,FileName const & filename,ErrorList & errorList)787 bool Converters::scanLog(Buffer const & buffer, string const & /*command*/,
788 			 FileName const & filename, ErrorList & errorList)
789 {
790 	OutputParams runparams(0);
791 	runparams.flavor = OutputParams::LATEX;
792 	LaTeX latex("", runparams, filename);
793 	TeXErrors terr;
794 	int const result = latex.scanLogFile(terr);
795 
796 	if (result & LaTeX::ERRORS)
797 		buffer.bufferErrors(terr, errorList);
798 
799 	return true;
800 }
801 
802 
runLaTeX(Buffer const & buffer,string const & command,OutputParams const & runparams,ErrorList & errorList)803 bool Converters::runLaTeX(Buffer const & buffer, string const & command,
804 			  OutputParams const & runparams, ErrorList & errorList)
805 {
806 	buffer.setBusy(true);
807 	buffer.message(_("Running LaTeX..."));
808 
809 	// do the LaTeX run(s)
810 	string const name = buffer.latexName();
811 	LaTeX latex(command, runparams, FileName(makeAbsPath(name)),
812 	            buffer.filePath(), buffer.layoutPos(),
813 	            buffer.lastPreviewError());
814 	TeXErrors terr;
815 	// The connection closes itself at the end of the scope when latex is
816 	// destroyed. One cannot close (and destroy) buffer while the converter is
817 	// running.
818 	latex.message.connect([&buffer](docstring const & msg){
819 			buffer.message(msg);
820 		});
821 	int const result = latex.run(terr);
822 
823 	if (result & LaTeX::ERRORS)
824 		buffer.bufferErrors(terr, errorList);
825 
826 	if (!errorList.empty()) {
827 	  // We will show the LaTeX Errors GUI later which contains
828 	  // specific error messages so it would be repetitive to give
829 	  // e.g. the "finished with an error" dialog in addition.
830 	}
831 	else if (result & LaTeX::NO_LOGFILE) {
832 		docstring const str =
833 			bformat(_("LaTeX did not run successfully. "
834 					       "Additionally, LyX could not locate "
835 					       "the LaTeX log %1$s."), from_utf8(name));
836 		Alert::error(_("LaTeX failed"), str);
837 	} else if (result & LaTeX::NONZERO_ERROR) {
838 		docstring const str =
839 			bformat(_( "The external program\n%1$s\n"
840 			      "finished with an error. "
841 			      "It is recommended you fix the cause of the external "
842 			      "program's error (check the logs). "), from_utf8(command));
843 		Alert::error(_("LaTeX failed"), str);
844 	} else if (result & LaTeX::NO_OUTPUT) {
845 		Alert::warning(_("Output is empty"),
846 			       _("No output file was generated."));
847 	}
848 
849 
850 	buffer.setBusy(false);
851 
852 	int const ERROR_MASK =
853 			LaTeX::NO_LOGFILE |
854 			LaTeX::ERRORS |
855 			LaTeX::NO_OUTPUT;
856 
857 	return (result & ERROR_MASK) == 0;
858 }
859 
860 
861 
buildGraph()862 void Converters::buildGraph()
863 {
864 	// clear graph's data structures
865 	G_.init(theFormats().size());
866 	// each of the converters knows how to convert one format to another
867 	// so, for each of them, we create an arrow on the graph, going from
868 	// the one to the other
869 	ConverterList::iterator it = converterlist_.begin();
870 	ConverterList::iterator const end = converterlist_.end();
871 	for (; it != end ; ++it) {
872 		int const from = theFormats().getNumber(it->from());
873 		int const to   = theFormats().getNumber(it->to());
874 		LASSERT(from >= 0, continue);
875 		LASSERT(to >= 0, continue);
876 		G_.addEdge(from, to);
877 	}
878 }
879 
880 
intToFormat(vector<int> const & input)881 FormatList const Converters::intToFormat(vector<int> const & input)
882 {
883 	FormatList result(input.size());
884 
885 	vector<int>::const_iterator it = input.begin();
886 	vector<int>::const_iterator const end = input.end();
887 	FormatList::iterator rit = result.begin();
888 	for ( ; it != end; ++it, ++rit) {
889 		*rit = &theFormats().get(*it);
890 	}
891 	return result;
892 }
893 
894 
getReachableTo(string const & target,bool const clear_visited)895 FormatList const Converters::getReachableTo(string const & target,
896 		bool const clear_visited)
897 {
898 	vector<int> const & reachablesto =
899 		G_.getReachableTo(theFormats().getNumber(target), clear_visited);
900 
901 	return intToFormat(reachablesto);
902 }
903 
904 
getReachable(string const & from,bool const only_viewable,bool const clear_visited,set<string> const & excludes)905 FormatList const Converters::getReachable(string const & from,
906 		bool const only_viewable, bool const clear_visited,
907 		set<string> const & excludes)
908 {
909 	set<int> excluded_numbers;
910 
911 	set<string>::const_iterator sit = excludes.begin();
912 	set<string>::const_iterator const end = excludes.end();
913 	for (; sit != end; ++sit)
914 		excluded_numbers.insert(theFormats().getNumber(*sit));
915 
916 	vector<int> const & reachables =
917 		G_.getReachable(theFormats().getNumber(from),
918 				only_viewable,
919 				clear_visited,
920 				excluded_numbers);
921 
922 	return intToFormat(reachables);
923 }
924 
925 
isReachable(string const & from,string const & to)926 bool Converters::isReachable(string const & from, string const & to)
927 {
928 	return G_.isReachable(theFormats().getNumber(from),
929 			      theFormats().getNumber(to));
930 }
931 
932 
getPath(string const & from,string const & to)933 Graph::EdgePath Converters::getPath(string const & from, string const & to)
934 {
935 	return G_.getPath(theFormats().getNumber(from),
936 			  theFormats().getNumber(to));
937 }
938 
939 
importableFormats()940 FormatList Converters::importableFormats()
941 {
942 	vector<string> l = loaders();
943 	FormatList result = getReachableTo(l[0], true);
944 	vector<string>::const_iterator it = l.begin() + 1;
945 	vector<string>::const_iterator en = l.end();
946 	for (; it != en; ++it) {
947 		FormatList r = getReachableTo(*it, false);
948 		result.insert(result.end(), r.begin(), r.end());
949 	}
950 	return result;
951 }
952 
953 
exportableFormats(bool only_viewable)954 FormatList Converters::exportableFormats(bool only_viewable)
955 {
956 	vector<string> s = savers();
957 	FormatList result = getReachable(s[0], only_viewable, true);
958 	vector<string>::const_iterator it = s.begin() + 1;
959 	vector<string>::const_iterator en = s.end();
960 	for (; it != en; ++it) {
961 		 FormatList r = getReachable(*it, only_viewable, false);
962 		result.insert(result.end(), r.begin(), r.end());
963 	}
964 	return result;
965 }
966 
967 
loaders() const968 vector<string> Converters::loaders() const
969 {
970 	vector<string> v;
971 	v.push_back("lyx");
972 	v.push_back("text");
973 	v.push_back("textparagraph");
974 	return v;
975 }
976 
977 
savers() const978 vector<string> Converters::savers() const
979 {
980 	vector<string> v;
981 	v.push_back("docbook");
982 	v.push_back("latex");
983 	v.push_back("literate");
984 	v.push_back("luatex");
985 	v.push_back("dviluatex");
986 	v.push_back("lyx");
987 	v.push_back("xhtml");
988 	v.push_back("pdflatex");
989 	v.push_back("platex");
990 	v.push_back("text");
991 	v.push_back("xetex");
992 	return v;
993 }
994 
995 
996 } // namespace lyx
997