]> git.lyx.org Git - lyx.git/blob - src/converter.C
add support for xml, now just for docbook
[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 "LaTeX.h"
22
23 #include "frontends/Alert.h"
24
25 #include "support/filetools.h"
26 #include "support/lyxlib.h"
27 #include "support/path.h"
28 #include "support/systemcall.h"
29
30 using lyx::support::AddName;
31 using lyx::support::bformat;
32 using lyx::support::ChangeExtension;
33 using lyx::support::compare_ascii_no_case;
34 using lyx::support::contains;
35 using lyx::support::DirList;
36 using lyx::support::GetExtension;
37 using lyx::support::LibScriptSearch;
38 using lyx::support::MakeRelPath;
39 using lyx::support::OnlyFilename;
40 using lyx::support::OnlyPath;
41 using lyx::support::Path;
42 using lyx::support::prefixIs;
43 using lyx::support::QuoteName;
44 using lyx::support::rename;
45 using lyx::support::split;
46 using lyx::support::subst;
47 using lyx::support::Systemcall;
48
49 using std::endl;
50 using std::find_if;
51 using std::string;
52 using std::vector;
53 using std::distance;
54
55
56 namespace {
57
58 string const token_from("$$i");
59 string const token_base("$$b");
60 string const token_to("$$o");
61 string const token_path("$$p");
62
63
64
65 string const add_options(string const & command, string const & options)
66 {
67         string head;
68         string const tail = split(command, head, ' ');
69         return head + ' ' + options + ' ' + tail;
70 }
71
72
73 string const dvipdfm_options(BufferParams const & bp)
74 {
75         string result;
76
77         if (bp.papersize2 != VM_PAPER_CUSTOM) {
78                 string const paper_size = bp.paperSizeName();
79                 if (paper_size != "b5" && paper_size != "foolscap")
80                         result = "-p "+ paper_size;
81
82                 if (bp.orientation == ORIENTATION_LANDSCAPE)
83                         result += " -l";
84         }
85
86         return result;
87 }
88
89
90 class ConverterEqual : public std::binary_function<string, string, bool> {
91 public:
92         ConverterEqual(string const & from, string const & to)
93                 : from_(from), to_(to) {}
94         bool operator()(Converter const & c) const {
95                 return c.from == from_ && c.to == to_;
96         }
97 private:
98         string const from_;
99         string const to_;
100 };
101
102 } // namespace anon
103
104
105 Converter::Converter(string const & f, string const & t, string const & c,
106           string const & l): from(f), to(t), command(c), flags(l),
107                              From(0), To(0), latex(false), xml(false),
108                              original_dir(false), need_aux(false)
109 {}
110
111
112 void Converter::readFlags()
113 {
114         string flag_list(flags);
115         while (!flag_list.empty()) {
116                 string flag_name, flag_value;
117                 flag_list = split(flag_list, flag_value, ',');
118                 flag_value = split(flag_value, flag_name, '=');
119                 if (flag_name == "latex")
120                         latex = true;
121                 else if (flag_name == "xml")
122                         xml = true;
123                 else if (flag_name == "originaldir")
124                         original_dir = true;
125                 else if (flag_name == "needaux")
126                         need_aux = true;
127                 else if (flag_name == "resultdir")
128                         result_dir = (flag_value.empty())
129                                 ? token_base : flag_value;
130                 else if (flag_name == "resultfile")
131                         result_file = flag_value;
132                 else if (flag_name == "parselog")
133                         parselog = flag_value;
134         }
135         if (!result_dir.empty() && result_file.empty())
136                 result_file = "index." + formats.extension(to);
137         //if (!contains(command, token_from))
138         //      latex = true;
139 }
140
141
142 bool operator<(Converter const & a, Converter const & b)
143 {
144         // use the compare_ascii_no_case instead of compare_no_case,
145         // because in turkish, 'i' is not the lowercase version of 'I',
146         // and thus turkish locale breaks parsing of tags.
147         int const i = compare_ascii_no_case(a.From->prettyname(),
148                                             b.From->prettyname());
149         if (i == 0)
150                 return compare_ascii_no_case(a.To->prettyname(),
151                                              b.To->prettyname()) < 0;
152         else
153                 return i < 0;
154 }
155
156
157 Converter const * Converters::getConverter(string const & from,
158                                             string const & to) const
159 {
160         ConverterList::const_iterator cit =
161                 find_if(converterlist_.begin(), converterlist_.end(),
162                         ConverterEqual(from, to));
163         if (cit != converterlist_.end())
164                 return &(*cit);
165         else
166                 return 0;
167 }
168
169
170 int Converters::getNumber(string const & from, string const & to) const
171 {
172         ConverterList::const_iterator cit =
173                 find_if(converterlist_.begin(), converterlist_.end(),
174                         ConverterEqual(from, to));
175         if (cit != converterlist_.end())
176                 return distance(converterlist_.begin(), cit);
177         else
178                 return -1;
179 }
180
181
182 void Converters::add(string const & from, string const & to,
183                      string const & command, string const & flags)
184 {
185         formats.add(from);
186         formats.add(to);
187         ConverterList::iterator it = find_if(converterlist_.begin(),
188                                              converterlist_.end(),
189                                              ConverterEqual(from , to));
190
191         Converter converter(from, to, command, flags);
192         if (it != converterlist_.end() && !flags.empty() && flags[0] == '*') {
193                 converter = *it;
194                 converter.command = command;
195                 converter.flags = flags;
196         }
197         converter.readFlags();
198
199         if (converter.latex && (latex_command_.empty() || to == "dvi"))
200                 latex_command_ = subst(command, token_from, "");
201         // If we have both latex & pdflatex, we set latex_command to latex.
202         // The latex_command is used to update the .aux file when running
203         // a converter that uses it.
204
205         if (it == converterlist_.end()) {
206                 converterlist_.push_back(converter);
207         } else {
208                 converter.From = it->From;
209                 converter.To = it->To;
210                 *it = converter;
211         }
212 }
213
214
215 void Converters::erase(string const & from, string const & to)
216 {
217         ConverterList::iterator it = find_if(converterlist_.begin(),
218                                              converterlist_.end(),
219                                              ConverterEqual(from, to));
220         if (it != converterlist_.end())
221                 converterlist_.erase(it);
222 }
223
224
225 // This method updates the pointers From and To in all the converters.
226 // The code is not very efficient, but it doesn't matter as the number
227 // of formats and converters is small.
228 // Furthermore, this method is called only on startup, or after
229 // adding/deleting a format in FormPreferences (the latter calls can be
230 // eliminated if the formats in the Formats class are stored using a map or
231 // a list (instead of a vector), but this will cause other problems).
232 void Converters::update(Formats const & formats)
233 {
234         ConverterList::iterator it = converterlist_.begin();
235         ConverterList::iterator end = converterlist_.end();
236         for (; it != end; ++it) {
237                 it->From = formats.getFormat(it->from);
238                 it->To = formats.getFormat(it->to);
239         }
240 }
241
242
243 // This method updates the pointers From and To in the last converter.
244 // It is called when adding a new converter in FormPreferences
245 void Converters::updateLast(Formats const & formats)
246 {
247         if (converterlist_.begin() != converterlist_.end()) {
248                 ConverterList::iterator it = converterlist_.end() - 1;
249                 it->From = formats.getFormat(it->from);
250                 it->To = formats.getFormat(it->to);
251         }
252 }
253
254
255 void Converters::sort()
256 {
257         std::sort(converterlist_.begin(), converterlist_.end());
258 }
259
260
261 OutputParams::FLAVOR Converters::getFlavor(Graph::EdgePath const & path)
262 {
263         for (Graph::EdgePath::const_iterator cit = path.begin();
264              cit != path.end(); ++cit) {
265                 Converter const & conv = converterlist_[*cit];
266                 if (conv.latex)
267                         if (contains(conv.to, "pdf"))
268                                 return OutputParams::PDFLATEX;
269                 if (conv.xml)
270                         return OutputParams::XML;
271         }
272         return OutputParams::LATEX;
273 }
274
275
276 bool Converters::convert(Buffer const * buffer,
277                          string const & from_file, string const & to_file_base,
278                          string const & from_format, string const & to_format,
279                          string & to_file)
280 {
281         to_file = ChangeExtension(to_file_base,
282                                   formats.extension(to_format));
283
284         if (from_format == to_format)
285                 return move(from_file, to_file, false);
286
287         Graph::EdgePath edgepath = getPath(from_format, to_format);
288         if (edgepath.empty()) {
289                 return false;
290         }
291         OutputParams runparams;
292         runparams.flavor = getFlavor(edgepath);
293
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 boost::signals::trackable {
516 public:
517         showMessage(Buffer const & b) : buffer_(b) {};
518         void operator()(string const & m)
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         // do the LaTeX run(s)
536         string name = buffer.getLatexName();
537         LaTeX latex(command, runparams, name, buffer.filePath());
538         TeXErrors terr;
539         showMessage show(buffer);
540         latex.message.connect(show);
541         int result = latex.run(terr);
542
543         if (result & LaTeX::ERRORS)
544                 bufferErrors(buffer, terr);
545
546         // check return value from latex.run().
547         if ((result & LaTeX::NO_LOGFILE)) {
548                 string str = bformat(_("LaTeX did not run successfully. "
549                                        "Additionally, LyX could not locate "
550                                        "the LaTeX log %1$s."), name);
551                 Alert::error(_("LaTeX failed"), str);
552         } else if (result & LaTeX::NO_OUTPUT) {
553                 Alert::warning(_("Output is empty"),
554                                _("An empty output file was generated."));
555         }
556
557
558         buffer.busy(false);
559
560         int const ERROR_MASK =
561                         LaTeX::NO_LOGFILE |
562                         LaTeX::ERRORS |
563                         LaTeX::NO_OUTPUT;
564
565         return (result & ERROR_MASK) == 0;
566
567 }
568
569
570
571 void Converters::buildGraph()
572 {
573         G_.init(formats.size());
574         ConverterList::iterator beg = converterlist_.begin();
575         ConverterList::iterator end = converterlist_.end();
576         for (ConverterList::iterator it = beg; it != end ; ++it) {
577                 int const s = formats.getNumber(it->from);
578                 int const t = formats.getNumber(it->to);
579                 G_.addEdge(s,t);
580         }
581 }
582
583
584 std::vector<Format const *> const
585 Converters::intToFormat(std::vector<int> const & input)
586 {
587         vector<Format const *> result(input.size());
588
589         vector<int>::const_iterator it = input.begin();
590         vector<int>::const_iterator end = input.end();
591         vector<Format const *>::iterator rit = result.begin();
592         for ( ; it != end; ++it, ++rit) {
593                 *rit = &formats.get(*it);
594         }
595         return result;
596 }
597
598 vector<Format const *> const
599 Converters::getReachableTo(string const & target, bool clear_visited)
600 {
601         vector<int> const & reachablesto =
602                 G_.getReachableTo(formats.getNumber(target), clear_visited);
603
604         return intToFormat(reachablesto);
605 }
606
607
608 vector<Format const *> const
609 Converters::getReachable(string const & from, bool only_viewable,
610                          bool clear_visited)
611 {
612         vector<int> const & reachables =
613                 G_.getReachable(formats.getNumber(from),
614                                 only_viewable,
615                                 clear_visited);
616
617         return intToFormat(reachables);
618 }
619
620
621 bool Converters::isReachable(string const & from, string const & to)
622 {
623         return G_.isReachable(formats.getNumber(from),
624                               formats.getNumber(to));
625 }
626
627
628 Graph::EdgePath const
629 Converters::getPath(string const & from, string const & to)
630 {
631         return G_.getPath(formats.getNumber(from),
632                           formats.getNumber(to));
633 }
634
635
636 /// The global instance
637 Converters converters;
638
639 // The global copy after reading lyxrc.defaults
640 Converters system_converters;