]> git.lyx.org Git - lyx.git/blob - src/converter.C
Oops...
[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 "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::alert(_("There were errors during the Build process."),
365                                                    _("You should try to fix them."));
366                                 else
367                                         Alert::alert(_("Cannot convert file"),
368                                                    _("Error while executing"),
369                                                    command.substr(0, 50));
370                                 return false;
371                         }
372                 }
373         }
374
375         Converter const & conv = converterlist_[edgepath.back()];
376         if (conv.To->dummy())
377                 return true;
378
379
380         if (!conv.result_dir.empty()) {
381                 to_file = AddName(subst(conv.result_dir, token_base, to_base),
382                                   subst(conv.result_file,
383                                         token_base, OnlyFilename(to_base)));
384                 if (from_base != to_base) {
385                         string from = subst(conv.result_dir,
386                                             token_base, from_base);
387                         string to = subst(conv.result_dir,
388                                           token_base, to_base);
389                         if (!lyx::rename(from, to)) {
390 #if USE_BOOST_FORMAT
391                                 Alert::alert(_("Error while trying to move directory:"),
392                                            from, boost::io::str(boost::format(_("to %1$s")) % to));
393 #else
394                                 Alert::alert(_("Error while trying to move directory:"),
395                                            from, _("to ") + to);
396 #endif
397                                 return false;
398                         }
399                 }
400                 return true;
401         } else
402                 return move(outfile, to_file, conv.latex);
403 }
404
405
406 // If from = /path/file.ext and to = /path2/file2.ext2 then this method
407 // moves each /path/file*.ext file to /path2/file2*.ext2'
408 bool Converters::move(string const & from, string const & to, bool copy)
409 {
410         if (from == to)
411                 return true;
412
413         bool no_errors = true;
414         string const path = OnlyPath(from);
415         string const base = OnlyFilename(ChangeExtension(from, ""));
416         string const to_base = ChangeExtension(to, "");
417         string const to_extension = GetExtension(to);
418
419         vector<string> files = DirList(OnlyPath(from), GetExtension(from));
420         for (vector<string>::const_iterator it = files.begin();
421              it != files.end(); ++it)
422                 if (prefixIs(*it, base)) {
423                         string const from2 = path + *it;
424                         string to2 = to_base + it->substr(base.length());
425                         to2 = ChangeExtension(to2, to_extension);
426                         lyxerr[Debug::FILES] << "moving " << from2
427                                              << " to " << to2 << endl;
428                         bool const moved = (copy)
429                                 ? lyx::copy(from2, to2)
430                                 : lyx::rename(from2, to2);
431                         if (!moved && no_errors) {
432 #if USE_BOOST_FORMAT
433                                 Alert::alert(_("Error while trying to move file:"),
434                                            from2, boost::io::str(boost::format(_("to %1$s")) % to2));
435 #else
436                                 Alert::alert(_("Error while trying to move file:"),
437                                            from2, _("to ") + to2);
438 #endif
439                                 no_errors = false;
440                         }
441                 }
442         return no_errors;
443 }
444
445
446 bool Converters::convert(Buffer const * buffer,
447                         string const & from_file, string const & to_file_base,
448                         string const & from_format, string const & to_format)
449 {
450         string to_file;
451         return convert(buffer, from_file, to_file_base, from_format, to_format,
452                        to_file);
453 }
454
455
456 bool Converters::formatIsUsed(string const & format)
457 {
458         ConverterList::const_iterator cit = converterlist_.begin();
459         ConverterList::const_iterator end = converterlist_.end();
460         for (; cit != end; ++cit) {
461                 if (cit->from == format || cit->to == format)
462                         return true;
463         }
464         return false;
465 }
466
467
468 bool Converters::scanLog(Buffer const * buffer, string const & command,
469                         string const & filename)
470 {
471         if (!buffer)
472                 return false;
473
474         BufferView * bv = buffer->getUser();
475         if (bv) {
476                 bv->owner()->busy(true);
477                 // all error insets should have been removed by now
478         }
479
480         LaTeX latex("", filename, "");
481         TeXErrors terr;
482         int result = latex.scanLogFile(terr);
483         if (bv) {
484                 if ((result & LaTeX::ERRORS)) {
485                         // Insert all errors as errors boxes
486                         bv->insertErrors(terr);
487 #warning repaint() or update() or nothing ?
488                         bv->repaint();
489                         bv->fitCursor();
490                 }
491                 bv->owner()->busy(false);
492         }
493
494         if ((result & LaTeX::ERRORS)) {
495                 int num_errors = latex.getNumErrors();
496                 string s;
497                 string t;
498                 if (num_errors == 1) {
499                         s = _("One error detected");
500                         t = _("You should try to fix it.");
501                 } else {
502                         s = tostr(num_errors);
503                         s += _(" errors detected.");
504                         t = _("You should try to fix them.");
505                 }
506                 string head;
507                 split(command, head, ' ');
508 #if USE_BOOST_FORMAT
509                 Alert::alert(boost::io::str(boost::format(_("There were errors during running of %1$s")) % head),
510                            s, t);
511 #else
512                 Alert::alert(_("There were errors during running of ") + head,
513                            s, t);
514 #endif
515                 return false;
516         } else if (result & LaTeX::NO_OUTPUT) {
517                 string const s = _("The operation resulted in");
518                 string const t = _("an empty file.");
519                 Alert::alert(_("Resulting file is empty"), s, t);
520                 return false;
521         }
522         return true;
523 }
524
525
526 bool Converters::runLaTeX(Buffer const * buffer, string const & command)
527 {
528         if (!buffer)
529                 return false;
530
531         BufferView * bv = buffer->getUser();
532
533         if (bv) {
534                 bv->owner()->busy(true);
535                 bv->owner()->message(_("Running LaTeX..."));
536                 // all the autoinsets have already been removed
537         }
538
539         // do the LaTeX run(s)
540         string name = buffer->getLatexName();
541         LaTeX latex(command, name, buffer->filePath());
542         TeXErrors terr;
543         int result = latex.run(terr,
544                                bv ? &bv->owner()->getLyXFunc() : 0);
545
546         if (bv) {
547                 if ((result & LaTeX::ERRORS)) {
548                         // Insert all errors as errors boxes
549                         bv->insertErrors(terr);
550 #warning repaint() or update() or nothing ?
551                         bv->repaint();
552                         bv->fitCursor();
553                 }
554         }
555
556         // check return value from latex.run().
557         if ((result & LaTeX::NO_LOGFILE)) {
558                 Alert::alert(_("LaTeX did not work!"),
559                            _("Missing log file:"), name);
560         } else if ((result & LaTeX::ERRORS)) {
561                 int num_errors = latex.getNumErrors();
562                 string s;
563                 string t;
564                 if (num_errors == 1) {
565                         s = _("One error detected");
566                         t = _("You should try to fix it.");
567                 } else {
568                         s = tostr(num_errors);
569                         s += _(" errors detected.");
570                         t = _("You should try to fix them.");
571                 }
572                 Alert::alert(_("There were errors during the LaTeX run."),
573                            s, t);
574         }  else if (result & LaTeX::NO_OUTPUT) {
575                 string const s = _("The operation resulted in");
576                 string const t = _("an empty file.");
577                 Alert::alert(_("Resulting file is empty"), s, t);
578         }
579
580         if (bv)
581                 bv->owner()->busy(false);
582
583         int const ERROR_MASK =
584                         LaTeX::NO_LOGFILE |
585                         LaTeX::ERRORS |
586                         LaTeX::NO_OUTPUT;
587
588         return (result & ERROR_MASK) == 0;
589
590 }
591
592
593 string const Converters::dvips_options(Buffer const * buffer)
594 {
595         string result;
596         if (!buffer)
597                 return result;
598
599         if (buffer->params.use_geometry
600             && buffer->params.papersize2 == BufferParams::VM_PAPER_CUSTOM
601             && !lyxrc.print_paper_dimension_flag.empty()
602             && !buffer->params.paperwidth.empty()
603             && !buffer->params.paperheight.empty()) {
604                 // using a custom papersize
605                 result = lyxrc.print_paper_dimension_flag;
606                 result += ' ' + buffer->params.paperwidth;
607                 result += ',' + buffer->params.paperheight;
608         } else {
609                 string const paper_option = papersize(buffer);
610                 if (paper_option != "letter" ||
611                     buffer->params.orientation != BufferParams::ORIENTATION_LANDSCAPE) {
612                         // dvips won't accept -t letter -t landscape.  In all other
613                         // cases, include the paper size explicitly.
614                         result = lyxrc.print_paper_flag;
615                         result += ' ' + paper_option;
616                 }
617         }
618         if (buffer->params.orientation == BufferParams::ORIENTATION_LANDSCAPE &&
619             buffer->params.papersize2 != BufferParams::VM_PAPER_CUSTOM)
620                 result += ' ' + lyxrc.print_landscape_flag;
621         return result;
622 }
623
624
625 string const Converters::dvipdfm_options(Buffer const * buffer)
626 {
627         string result;
628         if (!buffer)
629                 return result;
630
631         if (buffer->params.papersize2 != BufferParams::VM_PAPER_CUSTOM) {
632                 string const paper_size = papersize(buffer);
633                 if (paper_size != "b5" && paper_size != "foolscap")
634                         result = "-p "+ paper_size;
635
636                 if (buffer->params.orientation == BufferParams::ORIENTATION_LANDSCAPE)
637                         result += " -l";
638         }
639
640         return result;
641 }
642
643 void Converters::buildGraph()
644 {
645         G_.init(formats.size());
646         ConverterList::iterator beg = converterlist_.begin();
647         ConverterList::iterator end = converterlist_.end();
648         for (ConverterList::iterator it = beg; it != end ; ++it) {
649                 int const s = formats.getNumber(it->from);
650                 int const t = formats.getNumber(it->to);
651                 G_.addEdge(s,t);
652         }
653 }
654
655 vector<Format const *> const
656 Converters::intToFormat(std::vector<int> const & input)
657 {
658         vector<Format const *> result(input.size());
659
660         vector<int>::const_iterator it = input.begin();
661         vector<int>::const_iterator end = input.end();
662         vector<Format const *>::iterator rit = result.begin();
663         for ( ; it != end; ++it, ++rit) {
664                 *rit = &formats.get(*it);
665         }
666         return result;
667 }
668
669 vector<Format const *> const
670 Converters::getReachableTo(string const & target, bool clear_visited)
671 {
672         vector<int> const & reachablesto =
673                 G_.getReachableTo(formats.getNumber(target), clear_visited);
674
675         return intToFormat(reachablesto);
676 }
677
678 vector<Format const *> const
679 Converters::getReachable(string const & from, bool only_viewable,
680              bool clear_visited)
681 {
682         vector<int> const & reachables =
683                 G_.getReachable(formats.getNumber(from),
684                                 only_viewable,
685                                 clear_visited);
686
687         return intToFormat(reachables);
688 }
689
690 bool Converters::isReachable(string const & from, string const & to)
691 {
692         return G_.isReachable(formats.getNumber(from),
693                               formats.getNumber(to));
694 }
695
696 Graph::EdgePath const
697 Converters::getPath(string const & from, string const & to)
698 {
699         return G_.getPath(formats.getNumber(from),
700                           formats.getNumber(to));
701 }
702
703 /// The global instance
704 Converters converters;
705
706 // The global copy after reading lyxrc.defaults
707 Converters system_converters;