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