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