]> git.lyx.org Git - lyx.git/blob - src/converter.C
fix crash after removing a table row (again due to uncorrected cursor
[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
24 #include "frontends/Alert.h"
25
26 #include "support/filetools.h"
27 #include "support/lyxlib.h"
28 #include "support/path.h"
29 #include "support/systemcall.h"
30
31 using lyx::support::AddName;
32 using lyx::support::bformat;
33 using lyx::support::ChangeExtension;
34 using lyx::support::compare_ascii_no_case;
35 using lyx::support::contains;
36 using lyx::support::DirList;
37 using lyx::support::GetExtension;
38 using lyx::support::LibScriptSearch;
39 using lyx::support::MakeRelPath;
40 using lyx::support::OnlyFilename;
41 using lyx::support::OnlyPath;
42 using lyx::support::Path;
43 using lyx::support::prefixIs;
44 using lyx::support::QuoteName;
45 using lyx::support::rename;
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_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                                 if (!rename(outfile, real_outfile))
378                                         res = -1;
379                                 else
380                                         lyxerr[Debug::FILES]
381                                                 << "renaming file " << outfile
382                                                 << " to " << real_outfile
383                                                 << endl;
384                         }
385
386                         if (!conv.parselog.empty()) {
387                                 string const logfile =  infile2 + ".log";
388                                 string const script = LibScriptSearch(conv.parselog);
389                                 string const command2 = script +
390                                         " < " + QuoteName(infile2 + ".out") +
391                                         " > " + QuoteName(logfile);
392                                 one.startscript(Systemcall::Wait, command2);
393                                 if (!scanLog(*buffer, command, logfile))
394                                         return false;
395                         }
396
397                         if (res) {
398                                 if (conv.to == "program") {
399                                         Alert::error(_("Build errors"),
400                                                 _("There were errors during the build process."));
401                                 } else {
402 // FIXME: this should go out of here. For example, here we cannot say if
403 // it is a document (.lyx) or something else. Same goes for elsewhere.
404                                 Alert::error(_("Cannot convert file"),
405                                         bformat(_("An error occurred whilst running %1$s"),
406                                                 command.substr(0, 50)));
407                                 }
408                                 return false;
409                         }
410                 }
411         }
412
413         Converter const & conv = converterlist_[edgepath.back()];
414         if (conv.To->dummy())
415                 return true;
416
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                         if (!rename(from, to)) {
428                                 Alert::error(_("Cannot convert file"),
429                                         bformat(_("Could not move a temporary file from %1$s to %2$s."),
430                                                 from, to));
431                                 return false;
432                         }
433                 }
434                 return true;
435         } else
436                 return move(outfile, to_file, conv.latex);
437 }
438
439
440 // If from = /path/file.ext and to = /path2/file2.ext2 then this method
441 // moves each /path/file*.ext file to /path2/file2*.ext2'
442 bool Converters::move(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                         bool const moved = (copy)
463                                 ? lyx::support::copy(from2, to2)
464                                 : rename(from2, to2);
465                         if (!moved && no_errors) {
466                                 Alert::error(_("Cannot convert file"),
467                                         bformat(_("Could not move a temporary file from %1$s to %2$s."),
468                                                 from2, to2));
469                                 no_errors = false;
470                         }
471                 }
472         return no_errors;
473 }
474
475
476 bool Converters::convert(Buffer const * buffer,
477                          string const & from_file, string const & to_file_base,
478                          string const & from_format, string const & to_format)
479 {
480         string to_file;
481         return convert(buffer, from_file, to_file_base, from_format, to_format,
482                        to_file);
483 }
484
485
486 bool Converters::formatIsUsed(string const & format)
487 {
488         ConverterList::const_iterator cit = converterlist_.begin();
489         ConverterList::const_iterator end = converterlist_.end();
490         for (; cit != end; ++cit) {
491                 if (cit->from == format || cit->to == format)
492                         return true;
493         }
494         return false;
495 }
496
497
498 bool Converters::scanLog(Buffer const & buffer, string const & /*command*/,
499                          string const & filename)
500 {
501         OutputParams runparams;
502         runparams.flavor = OutputParams::LATEX;
503         LaTeX latex("", runparams, filename, "");
504         TeXErrors terr;
505         int result = latex.scanLogFile(terr);
506
507         if (result & LaTeX::ERRORS)
508                 bufferErrors(buffer, terr);
509
510         return true;
511 }
512
513 namespace {
514
515 class showMessage : public std::unary_function<string, void>, public boost::signals::trackable {
516 public:
517         showMessage(Buffer const & b) : buffer_(b) {};
518         void operator()(string const & m) const
519         {
520                 buffer_.message(m);
521         }
522 private:
523         Buffer const & buffer_;
524 };
525
526 }
527
528
529 bool Converters::runLaTeX(Buffer const & buffer, string const & command,
530                           OutputParams const & runparams)
531 {
532         buffer.busy(true);
533         buffer.message(_("Running LaTeX..."));
534         
535         runparams.document_language = buffer.params().language->babel();
536
537         // do the LaTeX run(s)
538         string name = buffer.getLatexName();
539         LaTeX latex(command, runparams, name, buffer.filePath());
540         TeXErrors terr;
541         showMessage show(buffer);
542         latex.message.connect(show);
543         int result = latex.run(terr);
544
545         if (result & LaTeX::ERRORS)
546                 bufferErrors(buffer, terr);
547
548         // check return value from latex.run().
549         if ((result & LaTeX::NO_LOGFILE)) {
550                 string str = bformat(_("LaTeX did not run successfully. "
551                                        "Additionally, LyX could not locate "
552                                        "the LaTeX log %1$s."), name);
553                 Alert::error(_("LaTeX failed"), str);
554         } else if (result & LaTeX::NO_OUTPUT) {
555                 Alert::warning(_("Output is empty"),
556                                _("An empty output file was generated."));
557         }
558
559
560         buffer.busy(false);
561
562         int const ERROR_MASK =
563                         LaTeX::NO_LOGFILE |
564                         LaTeX::ERRORS |
565                         LaTeX::NO_OUTPUT;
566
567         return (result & ERROR_MASK) == 0;
568
569 }
570
571
572
573 void Converters::buildGraph()
574 {
575         G_.init(formats.size());
576         ConverterList::iterator beg = converterlist_.begin();
577         ConverterList::iterator end = converterlist_.end();
578         for (ConverterList::iterator it = beg; it != end ; ++it) {
579                 int const s = formats.getNumber(it->from);
580                 int const t = formats.getNumber(it->to);
581                 G_.addEdge(s,t);
582         }
583 }
584
585
586 std::vector<Format const *> const
587 Converters::intToFormat(std::vector<int> const & input)
588 {
589         vector<Format const *> result(input.size());
590
591         vector<int>::const_iterator it = input.begin();
592         vector<int>::const_iterator end = input.end();
593         vector<Format const *>::iterator rit = result.begin();
594         for ( ; it != end; ++it, ++rit) {
595                 *rit = &formats.get(*it);
596         }
597         return result;
598 }
599
600 vector<Format const *> const
601 Converters::getReachableTo(string const & target, bool clear_visited)
602 {
603         vector<int> const & reachablesto =
604                 G_.getReachableTo(formats.getNumber(target), clear_visited);
605
606         return intToFormat(reachablesto);
607 }
608
609
610 vector<Format const *> const
611 Converters::getReachable(string const & from, bool only_viewable,
612                          bool clear_visited)
613 {
614         vector<int> const & reachables =
615                 G_.getReachable(formats.getNumber(from),
616                                 only_viewable,
617                                 clear_visited);
618
619         return intToFormat(reachables);
620 }
621
622
623 bool Converters::isReachable(string const & from, string const & to)
624 {
625         return G_.isReachable(formats.getNumber(from),
626                               formats.getNumber(to));
627 }
628
629
630 Graph::EdgePath const
631 Converters::getPath(string const & from, string const & to)
632 {
633         return G_.getPath(formats.getNumber(from),
634                           formats.getNumber(to));
635 }
636
637
638 /// The global instance
639 Converters converters;
640
641 // The global copy after reading lyxrc.defaults
642 Converters system_converters;