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