]> git.lyx.org Git - lyx.git/blob - src/converter.C
Make lyx2lyx output the new external inset format.
[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         LatexRunParams runparams;
260         runparams.flavor = usePdflatex(edgepath) ?
261                 LatexRunParams::PDFLATEX : LatexRunParams::LATEX;
262
263         string path = OnlyPath(from_file);
264         Path p(path);
265
266         bool run_latex = false;
267         string from_base = ChangeExtension(from_file, "");
268         string to_base = ChangeExtension(to_file, "");
269         string infile;
270         string outfile = from_file;
271         for (Graph::EdgePath::const_iterator cit = edgepath.begin();
272              cit != edgepath.end(); ++cit) {
273                 Converter const & conv = converterlist_[*cit];
274                 bool dummy = conv.To->dummy() && conv.to != "program";
275                 if (!dummy)
276                         lyxerr[Debug::FILES] << "Converting from  "
277                                << conv.from << " to " << conv.to << endl;
278                 infile = outfile;
279                 outfile = conv.result_dir.empty()
280                         ? ChangeExtension(from_file, conv.To->extension())
281                         : AddName(subst(conv.result_dir,
282                                         token_base, from_base),
283                                   subst(conv.result_file,
284                                         token_base, OnlyFilename(from_base)));
285
286                 // if input and output files are equal, we use a
287                 // temporary file as intermediary (JMarc)
288                 string real_outfile;
289                 if (outfile == infile) {
290                         real_outfile = infile;
291                         outfile = AddName(buffer->tmppath, "tmpfile.out");
292                 }
293
294                 if (conv.latex) {
295                         run_latex = true;
296                         string command = subst(conv.command, token_from, "");
297                         lyxerr[Debug::FILES] << "Running " << command << endl;
298                         if (!runLaTeX(buffer, command, runparams))
299                                 return false;
300                 } else {
301                         if (conv.need_aux && !run_latex
302                             && !latex_command_.empty()) {
303                                 lyxerr[Debug::FILES]
304                                         << "Running " << latex_command_
305                                         << " to update aux file"<<  endl;
306                                 runLaTeX(buffer, latex_command_, runparams);
307                         }
308
309                         string infile2 = (conv.original_dir)
310                                 ? infile : MakeRelPath(infile, path);
311                         string outfile2 = (conv.original_dir)
312                                 ? outfile : MakeRelPath(outfile, path);
313
314                         string command = conv.command;
315                         command = subst(command, token_from, QuoteName(infile2));
316                         command = subst(command, token_base, QuoteName(from_base));
317                         command = subst(command, token_to, QuoteName(outfile2));
318                         command = LibScriptSearch(command);
319
320                         if (!conv.parselog.empty())
321                                 command += " 2> " + QuoteName(infile2 + ".out");
322
323                         if (conv.from == "dvi" && conv.to == "ps")
324                                 command = add_options(command,
325                                                       dvips_options(buffer));
326                         else if (conv.from == "dvi" && prefixIs(conv.to, "pdf"))
327                                 command = add_options(command,
328                                                       dvipdfm_options(buffer));
329
330                         lyxerr[Debug::FILES] << "Calling " << command << endl;
331                         if (buffer)
332                                 ShowMessage(buffer, _("Executing command:"), command);
333
334                         Systemcall::Starttype type = (dummy)
335                                 ? Systemcall::DontWait : Systemcall::Wait;
336                         Systemcall one;
337                         int res;
338                         if (conv.original_dir && buffer) {
339                                 Path p(buffer->filePath());
340                                 res = one.startscript(type, command);
341                         } else
342                                 res = one.startscript(type, command);
343
344                         if (!real_outfile.empty()) {
345                                 if (!lyx::rename(outfile, real_outfile))
346                                         res = -1;
347                                 else
348                                         lyxerr[Debug::FILES]
349                                                 << "renaming file " << outfile
350                                                 << " to " << real_outfile
351                                                 << endl;
352                         }
353
354                         if (!conv.parselog.empty()) {
355                                 string const logfile =  infile2 + ".log";
356                                 string const script = LibScriptSearch(conv.parselog);
357                                 string const command2 = script +
358                                         " < " + QuoteName(infile2 + ".out") +
359                                         " > " + QuoteName(logfile);
360                                 one.startscript(Systemcall::Wait, command2);
361                                 if (!scanLog(buffer, command, logfile))
362                                         return false;
363                         }
364
365                         if (res) {
366                                 if (conv.to == "program") {
367                                         Alert::error(_("Build errors"),
368                                                 _("There were errors during the build process."));
369                                 } else {
370 // FIXME: this should go out of here. For example, here we cannot say if
371 // it is a document (.lyx) or something else. Same goes for elsewhere.
372                                 Alert::error(_("Cannot convert file"),
373                                         bformat(_("An error occurred whilst running %1$s"),
374                                                 command.substr(0, 50)));
375                                 }
376                                 return false;
377                         }
378                 }
379         }
380
381         Converter const & conv = converterlist_[edgepath.back()];
382         if (conv.To->dummy())
383                 return true;
384
385
386         if (!conv.result_dir.empty()) {
387                 to_file = AddName(subst(conv.result_dir, token_base, to_base),
388                                   subst(conv.result_file,
389                                         token_base, OnlyFilename(to_base)));
390                 if (from_base != to_base) {
391                         string from = subst(conv.result_dir,
392                                             token_base, from_base);
393                         string to = subst(conv.result_dir,
394                                           token_base, to_base);
395                         if (!lyx::rename(from, to)) {
396                                 Alert::error(_("Cannot convert file"),
397                                         bformat(_("Could not move a temporary file from %1$s to %2$s."),
398                                                 from, to));
399                                 return false;
400                         }
401                 }
402                 return true;
403         } else
404                 return move(outfile, to_file, conv.latex);
405 }
406
407
408 // If from = /path/file.ext and to = /path2/file2.ext2 then this method
409 // moves each /path/file*.ext file to /path2/file2*.ext2'
410 bool Converters::move(string const & from, string const & to, bool copy)
411 {
412         if (from == to)
413                 return true;
414
415         bool no_errors = true;
416         string const path = OnlyPath(from);
417         string const base = OnlyFilename(ChangeExtension(from, ""));
418         string const to_base = ChangeExtension(to, "");
419         string const to_extension = GetExtension(to);
420
421         vector<string> files = DirList(OnlyPath(from), GetExtension(from));
422         for (vector<string>::const_iterator it = files.begin();
423              it != files.end(); ++it)
424                 if (prefixIs(*it, base)) {
425                         string const from2 = path + *it;
426                         string to2 = to_base + it->substr(base.length());
427                         to2 = ChangeExtension(to2, to_extension);
428                         lyxerr[Debug::FILES] << "moving " << from2
429                                              << " to " << to2 << endl;
430                         bool const moved = (copy)
431                                 ? lyx::copy(from2, to2)
432                                 : lyx::rename(from2, to2);
433                         if (!moved && no_errors) {
434                                 Alert::error(_("Cannot convert file"),
435                                         bformat(_("Could not move a temporary file from %1$s to %2$s."),
436                                                 from2, to2));
437                                 no_errors = false;
438                         }
439                 }
440         return no_errors;
441 }
442
443
444 bool Converters::convert(Buffer const * buffer,
445                         string const & from_file, string const & to_file_base,
446                         string const & from_format, string const & to_format)
447 {
448         string to_file;
449         return convert(buffer, from_file, to_file_base, from_format, to_format,
450                        to_file);
451 }
452
453
454 bool Converters::formatIsUsed(string const & format)
455 {
456         ConverterList::const_iterator cit = converterlist_.begin();
457         ConverterList::const_iterator end = converterlist_.end();
458         for (; cit != end; ++cit) {
459                 if (cit->from == format || cit->to == format)
460                         return true;
461         }
462         return false;
463 }
464
465
466 bool Converters::scanLog(Buffer const * buffer, string const & /*command*/,
467                          string const & filename)
468 {
469         if (!buffer)
470                 return false;
471
472         BufferView * bv = buffer->getUser();
473         LatexRunParams runparams;
474         runparams.flavor = LatexRunParams::LATEX;
475         LaTeX latex("", runparams, filename, "");
476         TeXErrors terr;
477         int result = latex.scanLogFile(terr);
478
479         if (bv && (result & LaTeX::ERRORS)) {
480                 ErrorList el(*buffer, terr);
481                 bv->setErrorList(el);
482                 bv->showErrorList(_("LaTeX"));
483         }
484
485         return true;
486 }
487
488
489 bool Converters::runLaTeX(Buffer const * buffer, string const & command,
490                           LatexRunParams const & runparams)
491 {
492         if (!buffer)
493                 return false;
494
495         BufferView * bv = buffer->getUser();
496
497         if (bv) {
498                 bv->owner()->busy(true);
499                 bv->owner()->message(_("Running LaTeX..."));
500                 // all the autoinsets have already been removed
501         }
502
503         // do the LaTeX run(s)
504         string name = buffer->getLatexName();
505         LaTeX latex(command, runparams, name, buffer->filePath());
506         TeXErrors terr;
507         int result = latex.run(terr,
508                                bv ? &bv->owner()->getLyXFunc() : 0);
509
510         if (bv && (result & LaTeX::ERRORS)) {
511                 //show errors
512                 ErrorList el(*buffer, terr);
513                 bv->setErrorList(el);
514                 bv->showErrorList(_("LaTeX"));
515         }
516
517         // check return value from latex.run().
518         if ((result & LaTeX::NO_LOGFILE)) {
519                 string str = bformat(_("LaTeX did not run successfully. Additionally, LyX "
520                         "could not locate the LaTeX log %1$s."), name);
521                 Alert::error(_("LaTeX failed"), str);
522         } else if (result & LaTeX::NO_OUTPUT) {
523                 Alert::warning(_("Output is empty"),
524                         _("An empty output file was generated."));
525         }
526
527         if (bv)
528                 bv->owner()->busy(false);
529
530         int const ERROR_MASK =
531                         LaTeX::NO_LOGFILE |
532                         LaTeX::ERRORS |
533                         LaTeX::NO_OUTPUT;
534
535         return (result & ERROR_MASK) == 0;
536
537 }
538
539
540 string const Converters::dvips_options(Buffer const * buffer)
541 {
542         string result;
543         if (!buffer)
544                 return result;
545
546         if (buffer->params.use_geometry
547             && buffer->params.papersize2 == BufferParams::VM_PAPER_CUSTOM
548             && !lyxrc.print_paper_dimension_flag.empty()
549             && !buffer->params.paperwidth.empty()
550             && !buffer->params.paperheight.empty()) {
551                 // using a custom papersize
552                 result = lyxrc.print_paper_dimension_flag;
553                 result += ' ' + buffer->params.paperwidth;
554                 result += ',' + buffer->params.paperheight;
555         } else {
556                 string const paper_option = papersize(buffer);
557                 if (paper_option != "letter" ||
558                     buffer->params.orientation != BufferParams::ORIENTATION_LANDSCAPE) {
559                         // dvips won't accept -t letter -t landscape.  In all other
560                         // cases, include the paper size explicitly.
561                         result = lyxrc.print_paper_flag;
562                         result += ' ' + paper_option;
563                 }
564         }
565         if (buffer->params.orientation == BufferParams::ORIENTATION_LANDSCAPE &&
566             buffer->params.papersize2 != BufferParams::VM_PAPER_CUSTOM)
567                 result += ' ' + lyxrc.print_landscape_flag;
568         return result;
569 }
570
571
572 string const Converters::dvipdfm_options(Buffer const * buffer)
573 {
574         string result;
575         if (!buffer)
576                 return result;
577
578         if (buffer->params.papersize2 != BufferParams::VM_PAPER_CUSTOM) {
579                 string const paper_size = papersize(buffer);
580                 if (paper_size != "b5" && paper_size != "foolscap")
581                         result = "-p "+ paper_size;
582
583                 if (buffer->params.orientation == BufferParams::ORIENTATION_LANDSCAPE)
584                         result += " -l";
585         }
586
587         return result;
588 }
589
590 void Converters::buildGraph()
591 {
592         G_.init(formats.size());
593         ConverterList::iterator beg = converterlist_.begin();
594         ConverterList::iterator end = converterlist_.end();
595         for (ConverterList::iterator it = beg; it != end ; ++it) {
596                 int const s = formats.getNumber(it->from);
597                 int const t = formats.getNumber(it->to);
598                 G_.addEdge(s,t);
599         }
600 }
601
602 vector<Format const *> const
603 Converters::intToFormat(std::vector<int> const & input)
604 {
605         vector<Format const *> result(input.size());
606
607         vector<int>::const_iterator it = input.begin();
608         vector<int>::const_iterator end = input.end();
609         vector<Format const *>::iterator rit = result.begin();
610         for ( ; it != end; ++it, ++rit) {
611                 *rit = &formats.get(*it);
612         }
613         return result;
614 }
615
616 vector<Format const *> const
617 Converters::getReachableTo(string const & target, bool clear_visited)
618 {
619         vector<int> const & reachablesto =
620                 G_.getReachableTo(formats.getNumber(target), clear_visited);
621
622         return intToFormat(reachablesto);
623 }
624
625 vector<Format const *> const
626 Converters::getReachable(string const & from, bool only_viewable,
627              bool clear_visited)
628 {
629         vector<int> const & reachables =
630                 G_.getReachable(formats.getNumber(from),
631                                 only_viewable,
632                                 clear_visited);
633
634         return intToFormat(reachables);
635 }
636
637 bool Converters::isReachable(string const & from, string const & to)
638 {
639         return G_.isReachable(formats.getNumber(from),
640                               formats.getNumber(to));
641 }
642
643 Graph::EdgePath const
644 Converters::getPath(string const & from, string const & to)
645 {
646         return G_.getPath(formats.getNumber(from),
647                           formats.getNumber(to));
648 }
649
650 /// The global instance
651 Converters converters;
652
653 // The global copy after reading lyxrc.defaults
654 Converters system_converters;