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