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