]> git.lyx.org Git - lyx.git/blob - src/converter.C
missing bit
[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 "errorlist.h"
20 #include "LaTeX.h"
21 #include "lyx_cb.h" // ShowMessage()
22 #include "gettext.h"
23 #include "BufferView.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/path.h"
32 #include "support/tostr.h"
33 #include "support/systemcall.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 // FIXME: this should go out of here. For example, here we cannot say if
368 // it is a document (.lyx) or something else. Same goes for elsewhere.
369                                 Alert::error(_("Cannot convert file"),
370                                         bformat(_("An error occurred whilst running %1$s"),
371                                                 command.substr(0, 50)));
372                                 }
373                                 return false;
374                         }
375                 }
376         }
377
378         Converter const & conv = converterlist_[edgepath.back()];
379         if (conv.To->dummy())
380                 return true;
381
382
383         if (!conv.result_dir.empty()) {
384                 to_file = AddName(subst(conv.result_dir, token_base, to_base),
385                                   subst(conv.result_file,
386                                         token_base, OnlyFilename(to_base)));
387                 if (from_base != to_base) {
388                         string from = subst(conv.result_dir,
389                                             token_base, from_base);
390                         string to = subst(conv.result_dir,
391                                           token_base, to_base);
392                         if (!lyx::rename(from, to)) {
393                                 Alert::error(_("Cannot convert file"),
394                                         bformat(_("Could not move a temporary file from %1$s to %2$s."),
395                                                 from, to));
396                                 return false;
397                         }
398                 }
399                 return true;
400         } else
401                 return move(outfile, to_file, conv.latex);
402 }
403
404
405 // If from = /path/file.ext and to = /path2/file2.ext2 then this method
406 // moves each /path/file*.ext file to /path2/file2*.ext2'
407 bool Converters::move(string const & from, string const & to, bool copy)
408 {
409         if (from == to)
410                 return true;
411
412         bool no_errors = true;
413         string const path = OnlyPath(from);
414         string const base = OnlyFilename(ChangeExtension(from, ""));
415         string const to_base = ChangeExtension(to, "");
416         string const to_extension = GetExtension(to);
417
418         vector<string> files = DirList(OnlyPath(from), GetExtension(from));
419         for (vector<string>::const_iterator it = files.begin();
420              it != files.end(); ++it)
421                 if (prefixIs(*it, base)) {
422                         string const from2 = path + *it;
423                         string to2 = to_base + it->substr(base.length());
424                         to2 = ChangeExtension(to2, to_extension);
425                         lyxerr[Debug::FILES] << "moving " << from2
426                                              << " to " << to2 << endl;
427                         bool const moved = (copy)
428                                 ? lyx::copy(from2, to2)
429                                 : lyx::rename(from2, to2);
430                         if (!moved && no_errors) {
431                                 Alert::error(_("Cannot convert file"),
432                                         bformat(_("Could not move a temporary file from %1$s to %2$s."),
433                                                 from2, to2));
434                                 no_errors = false;
435                         }
436                 }
437         return no_errors;
438 }
439
440
441 bool Converters::convert(Buffer const * buffer,
442                         string const & from_file, string const & to_file_base,
443                         string const & from_format, string const & to_format)
444 {
445         string to_file;
446         return convert(buffer, from_file, to_file_base, from_format, to_format,
447                        to_file);
448 }
449
450
451 bool Converters::formatIsUsed(string const & format)
452 {
453         ConverterList::const_iterator cit = converterlist_.begin();
454         ConverterList::const_iterator end = converterlist_.end();
455         for (; cit != end; ++cit) {
456                 if (cit->from == format || cit->to == format)
457                         return true;
458         }
459         return false;
460 }
461
462
463 bool Converters::scanLog(Buffer const * buffer, string const & command,
464                          string const & filename)
465 {
466         if (!buffer)
467                 return false;
468
469         BufferView * bv = buffer->getUser();
470         LaTeX latex("", filename, "");
471         TeXErrors terr;
472         int result = latex.scanLogFile(terr);
473
474         if (bv && (result & LaTeX::ERRORS)) {
475                 ErrorList el(*buffer, terr);
476                 bv->setErrorList(el);
477                 bv->showErrorList(_("LaTeX"));
478         }
479
480         return true;
481 }
482
483
484 bool Converters::runLaTeX(Buffer const * buffer, string const & command)
485 {
486         if (!buffer)
487                 return false;
488
489         BufferView * bv = buffer->getUser();
490
491         if (bv) {
492                 bv->owner()->busy(true);
493                 bv->owner()->message(_("Running LaTeX..."));
494                 // all the autoinsets have already been removed
495         }
496
497         // do the LaTeX run(s)
498         string name = buffer->getLatexName();
499         LaTeX latex(command, name, buffer->filePath());
500         TeXErrors terr;
501         int result = latex.run(terr,
502                                bv ? &bv->owner()->getLyXFunc() : 0);
503
504         if (bv && (result & LaTeX::ERRORS)) {
505                 //show errors
506                 ErrorList el(*buffer, terr);
507                 bv->setErrorList(el);
508                 bv->showErrorList(_("LaTeX"));
509         }
510
511         // check return value from latex.run().
512         if ((result & LaTeX::NO_LOGFILE)) {
513                 string str = bformat(_("LaTeX did not run successfully. Additionally, LyX "
514                         "could not locate the LaTeX log %1$s."), name);
515                 Alert::error(_("LaTeX failed"), str);
516         } else if (result & LaTeX::NO_OUTPUT) {
517                 Alert::warning(_("Output is empty"),
518                         _("An empty output file was generated."));
519         }
520
521         if (bv)
522                 bv->owner()->busy(false);
523
524         int const ERROR_MASK =
525                         LaTeX::NO_LOGFILE |
526                         LaTeX::ERRORS |
527                         LaTeX::NO_OUTPUT;
528
529         return (result & ERROR_MASK) == 0;
530
531 }
532
533
534 string const Converters::dvips_options(Buffer const * buffer)
535 {
536         string result;
537         if (!buffer)
538                 return result;
539
540         if (buffer->params.use_geometry
541             && buffer->params.papersize2 == BufferParams::VM_PAPER_CUSTOM
542             && !lyxrc.print_paper_dimension_flag.empty()
543             && !buffer->params.paperwidth.empty()
544             && !buffer->params.paperheight.empty()) {
545                 // using a custom papersize
546                 result = lyxrc.print_paper_dimension_flag;
547                 result += ' ' + buffer->params.paperwidth;
548                 result += ',' + buffer->params.paperheight;
549         } else {
550                 string const paper_option = papersize(buffer);
551                 if (paper_option != "letter" ||
552                     buffer->params.orientation != BufferParams::ORIENTATION_LANDSCAPE) {
553                         // dvips won't accept -t letter -t landscape.  In all other
554                         // cases, include the paper size explicitly.
555                         result = lyxrc.print_paper_flag;
556                         result += ' ' + paper_option;
557                 }
558         }
559         if (buffer->params.orientation == BufferParams::ORIENTATION_LANDSCAPE &&
560             buffer->params.papersize2 != BufferParams::VM_PAPER_CUSTOM)
561                 result += ' ' + lyxrc.print_landscape_flag;
562         return result;
563 }
564
565
566 string const Converters::dvipdfm_options(Buffer const * buffer)
567 {
568         string result;
569         if (!buffer)
570                 return result;
571
572         if (buffer->params.papersize2 != BufferParams::VM_PAPER_CUSTOM) {
573                 string const paper_size = papersize(buffer);
574                 if (paper_size != "b5" && paper_size != "foolscap")
575                         result = "-p "+ paper_size;
576
577                 if (buffer->params.orientation == BufferParams::ORIENTATION_LANDSCAPE)
578                         result += " -l";
579         }
580
581         return result;
582 }
583
584 void Converters::buildGraph()
585 {
586         G_.init(formats.size());
587         ConverterList::iterator beg = converterlist_.begin();
588         ConverterList::iterator end = converterlist_.end();
589         for (ConverterList::iterator it = beg; it != end ; ++it) {
590                 int const s = formats.getNumber(it->from);
591                 int const t = formats.getNumber(it->to);
592                 G_.addEdge(s,t);
593         }
594 }
595
596 vector<Format const *> const
597 Converters::intToFormat(std::vector<int> const & input)
598 {
599         vector<Format const *> result(input.size());
600
601         vector<int>::const_iterator it = input.begin();
602         vector<int>::const_iterator end = input.end();
603         vector<Format const *>::iterator rit = result.begin();
604         for ( ; it != end; ++it, ++rit) {
605                 *rit = &formats.get(*it);
606         }
607         return result;
608 }
609
610 vector<Format const *> const
611 Converters::getReachableTo(string const & target, bool clear_visited)
612 {
613         vector<int> const & reachablesto =
614                 G_.getReachableTo(formats.getNumber(target), clear_visited);
615
616         return intToFormat(reachablesto);
617 }
618
619 vector<Format const *> const
620 Converters::getReachable(string const & from, bool only_viewable,
621              bool clear_visited)
622 {
623         vector<int> const & reachables =
624                 G_.getReachable(formats.getNumber(from),
625                                 only_viewable,
626                                 clear_visited);
627
628         return intToFormat(reachables);
629 }
630
631 bool Converters::isReachable(string const & from, string const & to)
632 {
633         return G_.isReachable(formats.getNumber(from),
634                               formats.getNumber(to));
635 }
636
637 Graph::EdgePath const
638 Converters::getPath(string const & from, string const & to)
639 {
640         return G_.getPath(formats.getNumber(from),
641                           formats.getNumber(to));
642 }
643
644 /// The global instance
645 Converters converters;
646
647 // The global copy after reading lyxrc.defaults
648 Converters system_converters;