]> git.lyx.org Git - lyx.git/blob - src/converter.C
The remaining External Template clean-up patch ;-)
[lyx.git] / src / converter.C
1 /**
2  * \file converter.C
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 #include "graph.h"
15 #include "format.h"
16 #include "lyxrc.h"
17 #include "buffer.h"
18 #include "bufferparams.h"
19 #include "buffer_funcs.h"
20 #include "bufferview_funcs.h"
21 #include "errorlist.h"
22 #include "LaTeX.h"
23 #include "gettext.h"
24 #include "debug.h"
25
26 #include "frontends/Alert.h"
27 #include "frontends/LyXView.h"
28
29 #include "support/filetools.h"
30 #include "support/lyxfunctional.h"
31 #include "support/lyxlib.h"
32 #include "support/path.h"
33 #include "support/tostr.h"
34 #include "support/systemcall.h"
35
36 #include <boost/signals/signal1.hpp>
37 #include <boost/signals/trackable.hpp>
38
39 #include <cctype>
40
41 using namespace lyx::support;
42
43 #ifndef CXX_GLOBAL_CSTD
44 using std::isdigit;
45 #endif
46
47 using std::vector;
48 using std::endl;
49 using std::find_if;
50
51 namespace {
52
53 string const token_from("$$i");
54 string const token_base("$$b");
55 string const token_to("$$o");
56 string const token_path("$$p");
57
58
59
60 string const add_options(string const & command, string const & options)
61 {
62         string head;
63         string const tail = split(command, head, ' ');
64         return head + ' ' + options + ' ' + tail;
65 }
66
67
68 string const dvipdfm_options(BufferParams const & bp)
69 {
70         string result;
71
72         if (bp.papersize2 != VM_PAPER_CUSTOM) {
73                 string const paper_size = bp.paperSizeName();
74                 if (paper_size != "b5" && paper_size != "foolscap")
75                         result = "-p "+ paper_size;
76
77                 if (bp.orientation == ORIENTATION_LANDSCAPE)
78                         result += " -l";
79         }
80
81         return result;
82 }
83
84 } // namespace anon
85
86
87 Converter::Converter(string const & f, string const & t, string const & c,
88           string const & l): from(f), to(t), command(c), flags(l),
89                              From(0), To(0), latex(false),
90                              original_dir(false), need_aux(false)
91 {}
92
93
94 void Converter::readFlags()
95 {
96         string flag_list(flags);
97         while (!flag_list.empty()) {
98                 string flag_name, flag_value;
99                 flag_list = split(flag_list, flag_value, ',');
100                 flag_value = split(flag_value, flag_name, '=');
101                 if (flag_name == "latex")
102                         latex = true;
103                 else if (flag_name == "originaldir")
104                         original_dir = true;
105                 else if (flag_name == "needaux")
106                         need_aux = true;
107                 else if (flag_name == "resultdir")
108                         result_dir = (flag_value.empty())
109                                 ? token_base : flag_value;
110                 else if (flag_name == "resultfile")
111                         result_file = flag_value;
112                 else if (flag_name == "parselog")
113                         parselog = flag_value;
114         }
115         if (!result_dir.empty() && result_file.empty())
116                 result_file = "index." + formats.extension(to);
117         //if (!contains(command, token_from))
118         //      latex = true;
119 }
120
121
122 bool operator<(Converter const & a, Converter const & b)
123 {
124         // use the compare_ascii_no_case instead of compare_no_case,
125         // because in turkish, 'i' is not the lowercase version of 'I',
126         // and thus turkish locale breaks parsing of tags.
127         int const i = compare_ascii_no_case(a.From->prettyname(),
128                                             b.From->prettyname());
129         if (i == 0)
130                 return compare_ascii_no_case(a.To->prettyname(),
131                                              b.To->prettyname()) < 0;
132         else
133                 return i < 0;
134 }
135
136
137 class compare_Converter {
138 public:
139         compare_Converter(string const & f, string const & t)
140                 : from(f), to(t) {}
141         bool operator()(Converter const & c) {
142                 return c.from == from && c.to == to;
143         }
144 private:
145         string const & from;
146         string const & to;
147 };
148
149
150
151 Converter const * Converters::getConverter(string const & from,
152                                             string const & to)
153 {
154         ConverterList::const_iterator cit =
155                 find_if(converterlist_.begin(), converterlist_.end(),
156                         compare_Converter(from, to));
157         if (cit != converterlist_.end())
158                 return &(*cit);
159         else
160                 return 0;
161 }
162
163
164 int Converters::getNumber(string const & from, string const & to)
165 {
166         ConverterList::const_iterator cit =
167                 find_if(converterlist_.begin(), converterlist_.end(),
168                         compare_Converter(from, to));
169         if (cit != converterlist_.end())
170                 return cit - converterlist_.begin();
171         else
172                 return -1;
173 }
174
175
176 void Converters::add(string const & from, string const & to,
177                      string const & command, string const & flags)
178 {
179         formats.add(from);
180         formats.add(to);
181         ConverterList::iterator it = find_if(converterlist_.begin(),
182                                              converterlist_.end(),
183                                              compare_Converter(from, to));
184
185         Converter converter(from, to, command, flags);
186         if (it != converterlist_.end() && !flags.empty() && flags[0] == '*') {
187                 converter = *it;
188                 converter.command = command;
189                 converter.flags = flags;
190         }
191         converter.readFlags();
192
193         if (converter.latex && (latex_command_.empty() || to == "dvi"))
194                 latex_command_ = subst(command, token_from, "");
195         // If we have both latex & pdflatex, we set latex_command to latex.
196         // The latex_command is used to update the .aux file when running
197         // a converter that uses it.
198
199         if (it == converterlist_.end()) {
200                 converterlist_.push_back(converter);
201         } else {
202                 converter.From = it->From;
203                 converter.To = it->To;
204                 *it = converter;
205         }
206 }
207
208
209 void Converters::erase(string const & from, string const & to)
210 {
211         ConverterList::iterator it = find_if(converterlist_.begin(),
212                                              converterlist_.end(),
213                                              compare_Converter(from, to));
214         if (it != converterlist_.end())
215                 converterlist_.erase(it);
216 }
217
218
219 // This method updates the pointers From and To in all the converters.
220 // The code is not very efficient, but it doesn't matter as the number
221 // of formats and converters is small.
222 // Furthermore, this method is called only on startup, or after
223 // adding/deleting a format in FormPreferences (the latter calls can be
224 // eliminated if the formats in the Formats class are stored using a map or
225 // a list (instead of a vector), but this will cause other problems).
226 void Converters::update(Formats const & formats)
227 {
228         ConverterList::iterator it = converterlist_.begin();
229         ConverterList::iterator end = converterlist_.end();
230         for (; it != end; ++it) {
231                 it->From = formats.getFormat(it->from);
232                 it->To = formats.getFormat(it->to);
233         }
234 }
235
236
237 // This method updates the pointers From and To in the last converter.
238 // It is called when adding a new converter in FormPreferences
239 void Converters::updateLast(Formats const & formats)
240 {
241         if (converterlist_.begin() != converterlist_.end()) {
242                 ConverterList::iterator it = converterlist_.end() - 1;
243                 it->From = formats.getFormat(it->from);
244                 it->To = formats.getFormat(it->to);
245         }
246 }
247
248
249 void Converters::sort()
250 {
251         std::sort(converterlist_.begin(), converterlist_.end());
252 }
253
254
255 bool Converters::usePdflatex(Graph::EdgePath const & path)
256 {
257         for (Graph::EdgePath::const_iterator cit = path.begin();
258              cit != path.end(); ++cit) {
259                 Converter const & conv = converterlist_[*cit];
260                 if (conv.latex)
261                         return contains(conv.to, "pdf");
262         }
263         return false;
264 }
265
266
267 bool Converters::convert(Buffer const * buffer,
268                          string const & from_file, string const & to_file_base,
269                          string const & from_format, string const & to_format,
270                          string & to_file)
271 {
272         to_file = ChangeExtension(to_file_base,
273                                   formats.extension(to_format));
274
275         if (from_format == to_format)
276                 return move(from_file, to_file, false);
277
278         Graph::EdgePath edgepath = getPath(from_format, to_format);
279         if (edgepath.empty()) {
280                 return false;
281         }
282         LatexRunParams runparams;
283         runparams.flavor = usePdflatex(edgepath) ?
284                 LatexRunParams::PDFLATEX : LatexRunParams::LATEX;
285
286         string path = OnlyPath(from_file);
287         Path p(path);
288
289         bool run_latex = false;
290         string from_base = ChangeExtension(from_file, "");
291         string to_base = ChangeExtension(to_file, "");
292         string infile;
293         string outfile = from_file;
294         for (Graph::EdgePath::const_iterator cit = edgepath.begin();
295              cit != edgepath.end(); ++cit) {
296                 Converter const & conv = converterlist_[*cit];
297                 bool dummy = conv.To->dummy() && conv.to != "program";
298                 if (!dummy)
299                         lyxerr[Debug::FILES] << "Converting from  "
300                                << conv.from << " to " << conv.to << endl;
301                 infile = outfile;
302                 outfile = conv.result_dir.empty()
303                         ? ChangeExtension(from_file, conv.To->extension())
304                         : AddName(subst(conv.result_dir,
305                                         token_base, from_base),
306                                   subst(conv.result_file,
307                                         token_base, OnlyFilename(from_base)));
308
309                 // if input and output files are equal, we use a
310                 // temporary file as intermediary (JMarc)
311                 string real_outfile;
312                 if (outfile == infile) {
313                         real_outfile = infile;
314                         outfile = AddName(buffer->tmppath, "tmpfile.out");
315                 }
316
317                 if (conv.latex) {
318                         run_latex = true;
319                         string command = subst(conv.command, token_from, "");
320                         lyxerr[Debug::FILES] << "Running " << command << endl;
321                         if (!runLaTeX(*buffer, command, runparams))
322                                 return false;
323                 } else {
324                         if (conv.need_aux && !run_latex
325                             && !latex_command_.empty()) {
326                                 lyxerr[Debug::FILES]
327                                         << "Running " << latex_command_
328                                         << " to update aux file"<<  endl;
329                                 runLaTeX(*buffer, latex_command_, runparams);
330                         }
331
332                         string infile2 = (conv.original_dir)
333                                 ? infile : MakeRelPath(infile, path);
334                         string outfile2 = (conv.original_dir)
335                                 ? outfile : MakeRelPath(outfile, path);
336
337                         string command = conv.command;
338                         command = subst(command, token_from, QuoteName(infile2));
339                         command = subst(command, token_base, QuoteName(from_base));
340                         command = subst(command, token_to, QuoteName(outfile2));
341                         command = LibScriptSearch(command);
342
343                         if (!conv.parselog.empty())
344                                 command += " 2> " + QuoteName(infile2 + ".out");
345
346                         if (conv.from == "dvi" && conv.to == "ps")
347                                 command = add_options(command,
348                                                       buffer->params.dvips_options());
349                         else if (conv.from == "dvi" && prefixIs(conv.to, "pdf"))
350                                 command = add_options(command,
351                                                       dvipdfm_options(buffer->params));
352
353                         lyxerr[Debug::FILES] << "Calling " << command << endl;
354                         buffer->message(_("Executing command: ") + command);
355
356                         Systemcall::Starttype type = (dummy)
357                                 ? Systemcall::DontWait : Systemcall::Wait;
358                         Systemcall one;
359                         int res;
360                         if (conv.original_dir) {
361                                 Path p(buffer->filePath());
362                                 res = one.startscript(type, command);
363                         } else
364                                 res = one.startscript(type, command);
365
366                         if (!real_outfile.empty()) {
367                                 if (!rename(outfile, real_outfile))
368                                         res = -1;
369                                 else
370                                         lyxerr[Debug::FILES]
371                                                 << "renaming file " << outfile
372                                                 << " to " << real_outfile
373                                                 << endl;
374                         }
375
376                         if (!conv.parselog.empty()) {
377                                 string const logfile =  infile2 + ".log";
378                                 string const script = LibScriptSearch(conv.parselog);
379                                 string const command2 = script +
380                                         " < " + QuoteName(infile2 + ".out") +
381                                         " > " + QuoteName(logfile);
382                                 one.startscript(Systemcall::Wait, command2);
383                                 if (!scanLog(*buffer, command, logfile))
384                                         return false;
385                         }
386
387                         if (res) {
388                                 if (conv.to == "program") {
389                                         Alert::error(_("Build errors"),
390                                                 _("There were errors during the build process."));
391                                 } else {
392 // FIXME: this should go out of here. For example, here we cannot say if
393 // it is a document (.lyx) or something else. Same goes for elsewhere.
394                                 Alert::error(_("Cannot convert file"),
395                                         bformat(_("An error occurred whilst running %1$s"),
396                                                 command.substr(0, 50)));
397                                 }
398                                 return false;
399                         }
400                 }
401         }
402
403         Converter const & conv = converterlist_[edgepath.back()];
404         if (conv.To->dummy())
405                 return true;
406
407
408         if (!conv.result_dir.empty()) {
409                 to_file = AddName(subst(conv.result_dir, token_base, to_base),
410                                   subst(conv.result_file,
411                                         token_base, OnlyFilename(to_base)));
412                 if (from_base != to_base) {
413                         string from = subst(conv.result_dir,
414                                             token_base, from_base);
415                         string to = subst(conv.result_dir,
416                                           token_base, to_base);
417                         if (!rename(from, to)) {
418                                 Alert::error(_("Cannot convert file"),
419                                         bformat(_("Could not move a temporary file from %1$s to %2$s."),
420                                                 from, to));
421                                 return false;
422                         }
423                 }
424                 return true;
425         } else
426                 return move(outfile, to_file, conv.latex);
427 }
428
429
430 // If from = /path/file.ext and to = /path2/file2.ext2 then this method
431 // moves each /path/file*.ext file to /path2/file2*.ext2'
432 bool Converters::move(string const & from, string const & to, bool copy)
433 {
434         if (from == to)
435                 return true;
436
437         bool no_errors = true;
438         string const path = OnlyPath(from);
439         string const base = OnlyFilename(ChangeExtension(from, ""));
440         string const to_base = ChangeExtension(to, "");
441         string const to_extension = GetExtension(to);
442
443         vector<string> files = DirList(OnlyPath(from), GetExtension(from));
444         for (vector<string>::const_iterator it = files.begin();
445              it != files.end(); ++it)
446                 if (prefixIs(*it, base)) {
447                         string const from2 = path + *it;
448                         string to2 = to_base + it->substr(base.length());
449                         to2 = ChangeExtension(to2, to_extension);
450                         lyxerr[Debug::FILES] << "moving " << from2
451                                              << " to " << to2 << endl;
452                         bool const moved = (copy)
453                                 ? lyx::support::copy(from2, to2)
454                                 : rename(from2, to2);
455                         if (!moved && no_errors) {
456                                 Alert::error(_("Cannot convert file"),
457                                         bformat(_("Could not move a temporary file from %1$s to %2$s."),
458                                                 from2, to2));
459                                 no_errors = false;
460                         }
461                 }
462         return no_errors;
463 }
464
465
466 bool Converters::convert(Buffer const * buffer,
467                          string const & from_file, string const & to_file_base,
468                          string const & from_format, string const & to_format)
469 {
470         string to_file;
471         return convert(buffer, from_file, to_file_base, from_format, to_format,
472                        to_file);
473 }
474
475
476 bool Converters::formatIsUsed(string const & format)
477 {
478         ConverterList::const_iterator cit = converterlist_.begin();
479         ConverterList::const_iterator end = converterlist_.end();
480         for (; cit != end; ++cit) {
481                 if (cit->from == format || cit->to == format)
482                         return true;
483         }
484         return false;
485 }
486
487
488 bool Converters::scanLog(Buffer const & buffer, string const & /*command*/,
489                          string const & filename)
490 {
491         LatexRunParams runparams;
492         runparams.flavor = LatexRunParams::LATEX;
493         LaTeX latex("", runparams, filename, "");
494         TeXErrors terr;
495         int result = latex.scanLogFile(terr);
496
497         if (result & LaTeX::ERRORS)
498                 bufferErrors(buffer, terr);
499
500         return true;
501 }
502
503 namespace {
504
505 class showMessage : public boost::signals::trackable {
506 public:
507         showMessage(Buffer const & b) : buffer_(b) {};
508         void operator()(string const & m)
509         {
510                 buffer_.message(m);
511         }
512 private:
513         Buffer const & buffer_;
514 };
515
516 }
517
518
519 bool Converters::runLaTeX(Buffer const & buffer, string const & command,
520                           LatexRunParams const & runparams)
521 {
522         buffer.busy(true);
523         buffer.message(_("Running LaTeX..."));
524
525         // do the LaTeX run(s)
526         string name = buffer.getLatexName();
527         LaTeX latex(command, runparams, name, buffer.filePath());
528         TeXErrors terr;
529         showMessage show(buffer);
530         latex.message.connect(show);
531         int result = latex.run(terr);
532
533         if (result & LaTeX::ERRORS)
534                 bufferErrors(buffer, terr);
535
536         // check return value from latex.run().
537         if ((result & LaTeX::NO_LOGFILE)) {
538                 string str = bformat(_("LaTeX did not run successfully. "
539                                        "Additionally, LyX could not locate "
540                                        "the LaTeX log %1$s."), name);
541                 Alert::error(_("LaTeX failed"), str);
542         } else if (result & LaTeX::NO_OUTPUT) {
543                 Alert::warning(_("Output is empty"),
544                                _("An empty output file was generated."));
545         }
546
547
548         buffer.busy(false);
549
550         int const ERROR_MASK =
551                         LaTeX::NO_LOGFILE |
552                         LaTeX::ERRORS |
553                         LaTeX::NO_OUTPUT;
554
555         return (result & ERROR_MASK) == 0;
556
557 }
558
559
560
561 void Converters::buildGraph()
562 {
563         G_.init(formats.size());
564         ConverterList::iterator beg = converterlist_.begin();
565         ConverterList::iterator end = converterlist_.end();
566         for (ConverterList::iterator it = beg; it != end ; ++it) {
567                 int const s = formats.getNumber(it->from);
568                 int const t = formats.getNumber(it->to);
569                 G_.addEdge(s,t);
570         }
571 }
572
573
574 std::vector<Format const *> const
575 Converters::intToFormat(std::vector<int> const & input)
576 {
577         vector<Format const *> result(input.size());
578
579         vector<int>::const_iterator it = input.begin();
580         vector<int>::const_iterator end = input.end();
581         vector<Format const *>::iterator rit = result.begin();
582         for ( ; it != end; ++it, ++rit) {
583                 *rit = &formats.get(*it);
584         }
585         return result;
586 }
587
588 vector<Format const *> const
589 Converters::getReachableTo(string const & target, bool clear_visited)
590 {
591         vector<int> const & reachablesto =
592                 G_.getReachableTo(formats.getNumber(target), clear_visited);
593
594         return intToFormat(reachablesto);
595 }
596
597
598 vector<Format const *> const
599 Converters::getReachable(string const & from, bool only_viewable,
600                          bool clear_visited)
601 {
602         vector<int> const & reachables =
603                 G_.getReachable(formats.getNumber(from),
604                                 only_viewable,
605                                 clear_visited);
606
607         return intToFormat(reachables);
608 }
609
610
611 bool Converters::isReachable(string const & from, string const & to)
612 {
613         return G_.isReachable(formats.getNumber(from),
614                               formats.getNumber(to));
615 }
616
617
618 Graph::EdgePath const
619 Converters::getPath(string const & from, string const & to)
620 {
621         return G_.getPath(formats.getNumber(from),
622                           formats.getNumber(to));
623 }
624
625
626 /// The global instance
627 Converters converters;
628
629 // The global copy after reading lyxrc.defaults
630 Converters system_converters;