]> git.lyx.org Git - lyx.git/blob - src/converter.C
ac91f7cf7f3039ad8e0469ad8c557481d4af00c3
[lyx.git] / src / converter.C
1 /* This file is part of
2  * ====================================================== 
3  * 
4  *           LyX, The Document Processor
5  *        
6  *           Copyright 1995 Matthias Ettrich
7  *           Copyright 1995-2000 The LyX Team.
8  *
9  * ====================================================== */
10
11 #include <config.h>
12
13 #ifdef __GNUG__
14 #pragma implementation
15 #endif
16
17 #include <queue>
18
19 #include "converter.h"
20 #include "lyxrc.h"
21 #include "support/syscall.h"
22 #include "support/path.h"
23 #include "buffer.h"
24 #include "bufferview_funcs.h"
25 #include "LaTeX.h"
26 #include "LyXView.h"
27 #include "minibuffer.h"
28 #include "lyx_gui_misc.h"
29 #include "lyx_cb.h" // ShowMessage()
30
31 using std::map;
32 using std::vector;
33 using std::queue;
34 using std::pair;
35 using std::endl;
36 using std::find;
37 using std::find_if;
38
39 //////////////////////////////////////////////////////////////////////////////
40
41 vector<Command> Converter::commands;
42 string Converter::latex_command;
43
44 inline
45 string const add_options(string const & command, string const & options)
46 {
47         string head;
48         string tail = split(command, head, ' ');
49         return head + ' ' + options + ' ' + tail;
50 }
51
52 //////////////////////////////////////////////////////////////////////////////
53
54
55 bool Format::dummy() const
56 {
57         return extension.empty();
58 }
59
60
61 void Formats::Add(string const & name)
62 {
63         if (formats.find(name) == formats.end())
64                 formats[name] = Format(name, name, name, "", "");
65 }
66
67
68 void Formats::Add(string const & name, string const & extension, 
69                   string const & prettyname, string const & shortcut)
70 {
71
72         if (prettyname.empty()) {
73                 FormatList::iterator it = formats.find(name);
74                 if (it != formats.end())
75                         formats.erase(it);
76                 return;
77         }
78
79         string old_viewer = formats[name].viewer;
80         formats[name] = Format(name, extension, prettyname, shortcut,
81                                old_viewer);
82 }
83
84
85 void Formats::SetViewer(string const & name, string const & command)
86 {
87         string command2 = command;
88         if (!command2.empty() && !contains(command2,"$$FName"))
89                 command2 += " $$FName";
90
91         Add(name);
92         GetFormat(name)->viewer = command2;
93 }
94
95
96 bool Formats::View(Buffer const * buffer, string const & filename,
97                    string const & format_name)
98 {
99         if (filename.empty())
100                 return false;
101
102         Format const * format = GetFormat(format_name);
103         if (!format || format->viewer.empty()) {
104                 WriteAlert(_("Can not view file"),
105                            _("No information for viewing ")
106                            + PrettyName(format_name));
107                            return false;
108         }
109
110         string command = format->viewer;
111
112         if (format_name == "dvi" &&
113             !lyxrc.view_dvi_paper_option.empty()) {
114                 string options = lyxrc.view_dvi_paper_option;
115                 options += " " + Converter::dvi_papersize(buffer);
116                 if (buffer->params.orientation 
117                     == BufferParams::ORIENTATION_LANDSCAPE)
118                         options += 'r';
119                 command = add_options(command, options);
120         }
121
122         string command2 = subst(command, "$$FName", OnlyFilename(filename));
123         lyxerr << "Executing command: " << command2 << endl;
124         ShowMessage(buffer, _("Executing command:"), command2);
125
126         command = subst(command, "$$FName", QuoteName(filename));
127         Systemcalls one;
128         int res = one.startscript(Systemcalls::SystemDontWait, command);
129
130         if (res) {
131                 WriteAlert(_("Can not view file"),
132                            _("Error while executing"),
133                            command.substr(0, 50));
134                 return false;
135         }
136         return true;
137 }
138
139
140 Format * Formats::GetFormat(string const & name)
141 {
142         FormatList::iterator it = formats.find(name);
143         if (it != formats.end())
144                 return &it->second;
145         else
146                 return 0;
147 }
148
149
150 string const Formats::PrettyName(string const & name)
151 {
152         Format const * format = GetFormat(name);
153         if (format)
154                 return format->prettyname;
155         else
156                 return name;
157 }
158
159
160 string const Formats::Extension(string const & name)
161 {
162         Format const * format = GetFormat(name);
163         if (format)
164                 return format->extension;
165         else
166                 return name;
167 }
168
169
170 vector<Format> 
171 const Formats::GetAllFormats()
172 {
173         vector<Format> result;
174         for (FormatList::iterator it = formats.begin(); 
175              it != formats.end(); ++it)
176                 result.push_back(it->second);
177         return result;
178 }
179
180
181 //////////////////////////////////////////////////////////////////////////////
182
183 class compare_Command {
184 public:
185         compare_Command(Command const & c) : com(c) {}
186         bool operator()(Command const & c) {
187                 return c.from == com.from && c.to == com.to;
188         }
189 private:
190         Command com;
191 };
192
193
194 void Converter::Add(string const & from, string const & to,
195                     string const & command, string const & flags)
196 {
197         formats.Add(from);
198         formats.Add(to);
199         Command Com(formats.GetFormat(from), formats.GetFormat(to), command);
200         vector<Command>::iterator it = find_if(commands.begin(),
201                                                commands.end(),
202                                                compare_Command(Com));
203
204         if (command.empty() || command == "none") {
205                 if (it != commands.end())
206                         commands.erase(it);
207                 return;
208         }
209
210
211         // Read the flags
212         string flag_list(flags);
213         while (!flag_list.empty()) {
214                 string flag_name, flag_value;
215                 flag_list = split(flag_list, flag_value, ',');
216                 flag_value = split(flag_value, flag_name, '=');
217                 if (flag_name == "*") {
218                         if (it != commands.end()) {
219                                 Com = *it;
220                                 Com.command = command;
221                         }
222                 } 
223                 else if (flag_name == "importer")
224                         Com.importer = true;
225                 else if (flag_name == "latex")
226                         Com.latex = true;
227                 else if (flag_name == "originaldir")
228                         Com.original_dir = true;
229                 else if (flag_name == "needaux")
230                         Com.need_aux = true;
231                 else if (flag_name == "resultdir")
232                         Com.result_dir = (flag_value.empty())
233                                 ? "$$BaseName" : flag_value;
234                 else if (flag_name == "resultfile")
235                         Com.result_file = flag_value;
236                 else if (flag_name == "parselog")
237                         Com.parselog = flag_value;
238                 else if (flag_name == "disable") {
239                         while (!flag_value.empty()) {
240                                 string tmp;
241                                 flag_value = split(flag_value, tmp, '&');
242                                 Com.disable.push_back(tmp);
243                         }
244                 }
245         }
246         if (!Com.result_dir.empty() && Com.result_file.empty())
247                 Com.result_file = "index." + to;
248         //if (!contains(command, "$$FName"))
249         //      Com.latex = true;
250
251         if (Com.latex && (latex_command.empty() || to == "dvi"))
252                 latex_command = command;
253         // If we have both latex & pdflatex, we set latex_command to latex.
254         // The latex_command is used to update the .aux file when running
255         // a converter that uses it.
256
257         if (it != commands.end()) {
258                 *it = Com;
259                 return;
260         }
261         commands.push_back(Com);
262 }
263
264
265 inline
266 bool enable(vector<Command>::iterator it, string const & from)
267 {
268         return find(it->disable.begin(), it->disable.end(), from)
269                 == it->disable.end();
270 }
271
272
273 vector<FormatPair> const
274 Converter::GetReachableTo(string const & target)
275 {
276         vector<FormatPair> result;
277
278         queue< vector<Command>::iterator > Q;
279         for (vector<Command>::iterator it = commands.begin();
280              it != commands.end(); ++it)
281                 if (it->to->name == target && it->importer) {
282                         Q.push(it);
283                         it->visited = true;
284                 } else
285                         it->visited = false;
286
287         while (!Q.empty()) {
288                 vector<Command>::iterator it = Q.front();
289                 Q.pop();
290                 result.push_back(FormatPair(it->from, 0, ""));
291                 for (vector<Command>::iterator it2 = commands.begin();
292                      it2 != commands.end(); ++it2)
293                         if (!it2->visited && it->from == it2->to &&
294                             it2->importer) {
295                                 Q.push(it2);
296                                 it2->visited = true;
297                         }
298         }
299
300         return result;
301 }
302
303
304 vector<FormatPair> const
305 Converter::GetReachable(string const & from, bool only_viewable)
306 {
307         vector<FormatPair> result;
308         Format const * format = formats.GetFormat(from);
309         if (!format)
310                 return result;
311
312         if (!only_viewable || !format->viewer.empty())
313                 result.push_back(FormatPair(format, 0, ""));
314
315         queue< vector<Command>::iterator > Q;
316         for (vector<Command>::iterator it = commands.begin();
317              it != commands.end(); ++it)
318                 if (it->from->name == from && enable(it, from) 
319                     && !it->importer) {
320                         Q.push(it);
321                         it->visited = true;
322                 } else
323                         it->visited = false;
324
325         while (!Q.empty()) {
326                 vector<Command>::iterator it = Q.front();
327                 Q.pop();
328                 if (!only_viewable || !it->to->viewer.empty())
329                         result.push_back(FormatPair(it->to, it->from,
330                                                     it->command));
331                 for (vector<Command>::iterator it2 = commands.begin();
332                      it2 != commands.end(); ++it2)
333                         if (!it2->visited && it->to == it2->from &&
334                             enable(it2, from) && !it2->importer) {
335                                 Q.push(it2);
336                                 it2->visited = true;
337                         }
338         }
339
340         return result;
341 }
342
343
344 bool Converter::IsReachable(string const & from, string const & to)
345 {
346         if (from == to)
347                 return true;
348
349         queue< vector<Command>::iterator > Q;
350         for (vector<Command>::iterator it = commands.begin();
351              it != commands.end(); ++it)
352                 if (it->from->name == from && enable(it, from)) {
353                         Q.push(it);
354                         it->visited = true;
355                 } else
356                         it->visited = false;
357
358         while (!Q.empty()) {
359                 vector<Command>::iterator it = Q.front();
360                 Q.pop();
361                 if (it->to->name == to)
362                         return true;
363                 for (vector<Command>::iterator it2 = commands.begin();
364                      it2 != commands.end(); ++it2)
365                         if (!it2->visited && it->to == it2->from &&
366                             enable(it2, from)) {
367                                 Q.push(it2);
368                                 it2->visited = true;
369                         }
370         }
371         return false;
372 }
373
374
375 bool Converter::Convert(Buffer const * buffer,
376                         string const & from_file, string const & to_file_base,
377                         string const & from_format, string const & to_format,
378                         string const & using_format, string & to_file)
379 {
380         to_file = ChangeExtension(to_file_base,
381                                   formats.Extension(to_format));
382
383         if (from_format == to_format)
384                 if (from_file != to_file)
385                         return lyx::rename(from_file, to_file);
386                 else
387                         return true;
388
389         queue< vector<Command>::iterator > Q;
390         for (vector<Command>::iterator it = commands.begin();
391              it != commands.end(); ++it)
392                 if (it->from->name == from_format && enable(it, from_format)) {
393                         Q.push(it);
394                         it->visited = true;
395                         it->previous = commands.end();
396                 } else
397                         it->visited = false;
398
399         if (Q.empty()) {
400                 WriteAlert(_("Can not convert file"),
401                            ("Unknown format ") + from_format);
402                 return false;
403         }
404
405         bool found = false;
406         vector<Command>::iterator it;
407         while (!Q.empty()) {
408                 it = Q.front();
409                 if (it->to->name == to_format &&
410                     (using_format.empty() || using_format == it->from->name)) {
411                         found = true;
412                         break;
413                 }
414                 Q.pop();
415                 for (vector<Command>::iterator it2 = commands.begin();
416                      it2 != commands.end(); ++it2)
417                         if (!it2->visited && it->to == it2->from &&
418                             enable(it2, from_format)) {
419                                 Q.push(it2);
420                                 it2->visited = true;
421                                 it2->previous = it;
422                         }
423         }
424
425         if (!found) {
426                 WriteAlert(_("Can not convert file"),
427                            _("No information for converting from ")
428                            + formats.PrettyName(from_format) + _(" to ")
429                            + formats.PrettyName(to_format));
430                 return false;
431         }
432
433         vector< vector<Command>::iterator > S;
434         while (it != commands.end()) {
435                 S.push_back(it);
436                 it = it->previous;
437         }
438
439         string path = OnlyPath(from_file);
440         Path p(path);
441
442         bool run_latex = false;
443         string from_base = ChangeExtension(from_file, "");
444         string to_base = ChangeExtension(to_file, "");
445         string infile;
446         string outfile = from_file;
447         for (vector< vector<Command>::iterator >::reverse_iterator rit =
448                      S.rbegin(); rit != S.rend(); ++rit) {
449                 it = *rit;
450                 bool dummy = it->to->dummy() && it->to->name != "program";
451                 if (!dummy)
452                         lyxerr << "Converting from  "
453                                << it->from->name << " to " << it->to->name << endl;
454                 infile = outfile;
455                 outfile = it->result_dir.empty()
456                         ? ChangeExtension(from_file, it->to->extension)
457                         : AddName(subst(it->result_dir,
458                                         "$$BaseName", from_base),
459                                   subst(it->result_file,
460                                         "$$BaseName", OnlyFilename(from_base)));
461
462                 if (it->latex) {
463                         lyxrc.pdf_mode = it->to->name == "pdf";
464                         lyxerr << "Running " << it->command << endl;
465                         run_latex = true;
466                         if (!runLaTeX(buffer, it->command))
467                                 return false;
468                 } else {
469                         if (it->need_aux && !run_latex
470                             && !latex_command.empty()) {
471                                 lyxerr << "Running " << latex_command 
472                                        << " to update aux file"<<  endl;
473                                 runLaTeX(buffer, latex_command);
474                         }
475
476                         string infile2 = (it->original_dir)
477                                 ? infile : MakeRelPath(infile, path);
478                         string outfile2 = (it->original_dir)
479                                 ? outfile : MakeRelPath(outfile, path);
480
481                         string command = it->command;
482                         command = subst(command, "$$FName", QuoteName(infile2));
483                         command = subst(command, "$$BaseName", QuoteName(from_base));
484                         command = subst(command, "$$OutName", QuoteName(outfile2));
485
486                         if (!it->parselog.empty())
487                                 command += " 2> " + QuoteName(infile2 + ".out");
488
489                         if (it->from->name == "dvi" && it->to->name == "ps")
490                                 command = add_options(command,
491                                                       dvips_options(buffer));
492
493                         lyxerr << "Calling " << command << endl;
494                         if (buffer)
495                                 ShowMessage(buffer, _("Executing command:"), command);
496
497                         Systemcalls::Starttype type = (dummy)
498                                 ? Systemcalls::SystemDontWait : Systemcalls::System;
499                         Systemcalls one;
500                         int res;
501                         if (it->original_dir && buffer) {
502                                 Path p(buffer->filepath);
503                                 res = one.startscript(type, command);
504                         } else
505                                 res = one.startscript(type, command);
506
507                         if (!it->parselog.empty()) {
508                                 string const logfile =  infile2 + ".log";
509                                 string const command2 = it->parselog +
510                                         " < " + QuoteName(infile2 + ".out") +
511                                         " > " + QuoteName(logfile);
512                                 one.startscript(Systemcalls::System, command2);
513                                 if (!scanLog(buffer, command, logfile))
514                                         return false;
515                         }
516
517                         if (res) {
518                                 if (it->to->name == "program")
519                                         WriteAlert(_("There were errors during the Build process."),
520                                                    _("You should try to fix them."));
521                                 else
522                                         WriteAlert(_("Can not convert file"),
523                                                    "Error while executing",
524                                                    command.substr(0, 50));
525                                 return false;
526                         }
527                 }
528         }
529
530         if (it->to->dummy())
531                 return true;
532
533
534         if (!it->result_dir.empty()) {
535                 to_file = AddName(subst(it->result_dir,
536                                         "$$BaseName", to_base),
537                                   subst(it->result_file,
538                                         "$$BaseName", OnlyFilename(to_base)));
539                 if (from_base != to_base) {
540                         string from = subst(it->result_dir,
541                                             "$$BaseName", from_base);
542                         string to = subst(it->result_dir,
543                                           "$$BaseName", to_base);
544                         if (!lyx::rename(from, to)) {
545                                 WriteAlert(_("Error while trying to move directory:"),
546                                            from, ("to ") + to);
547                                 return false;
548                         }
549                 }
550         } else if (outfile != to_file) {
551                 bool moved = (it->latex)
552                         ? lyx::copy(outfile, to_file)
553                         : lyx::rename(outfile, to_file);
554                 if (!moved) {
555                         WriteAlert(_("Error while trying to move file:"),
556                                    outfile, _("to ") + to_file);
557                         return false;
558                 }
559         }
560
561         return true;
562 }
563
564
565 bool Converter::Convert(Buffer const * buffer,
566                         string const & from_file, string const & to_file_base,
567                         string const & from_format, string const & to_format,
568                         string const & using_format)
569 {
570         string to_file;
571         return Convert(buffer, from_file, to_file_base, from_format, to_format,
572                 using_format, to_file);
573 }
574
575
576 string const Converter::SplitFormat(string const & str, string & format)
577 {
578         string using_format = split(str, format, ':');
579         if (format.empty())
580                 format = "dvi";
581         return using_format;
582 }
583
584
585 bool Converter::scanLog(Buffer const * buffer, string const & command,
586                         string const & filename)
587 {
588         if (!buffer)
589                 return false;
590
591         BufferView * bv = buffer->getUser();
592         bool need_redraw = false;
593         if (bv) {
594                 ProhibitInput(bv);
595                 // Remove all error insets
596                 need_redraw = bv->removeAutoInsets();
597         }
598
599         LaTeX latex("", filename, "");
600         TeXErrors terr;
601         int result = latex.scanLogFile(terr);
602         if (bv) {
603                 if ((result & LaTeX::ERRORS)) {
604                         // Insert all errors as errors boxes
605                         bv->insertErrors(terr);
606                         need_redraw = true;
607                 }
608                 if (need_redraw) {
609                         bv->redraw();
610                         bv->fitCursor(bv->text);
611                 }
612                 AllowInput(bv);
613         }
614
615         if ((result & LaTeX::ERRORS)) {
616                 int num_errors = latex.getNumErrors();
617                 string s;
618                 string t;
619                 if (num_errors == 1) {
620                         s = _("One error detected");
621                         t = _("You should try to fix it.");
622                 } else {
623                         s = tostr(num_errors);
624                         s += _(" errors detected.");
625                         t = _("You should try to fix them.");
626                 }
627                 string head;
628                 split(command, head, ' ');
629                 WriteAlert(_("There were errors during running of ") + head,
630                            s, t);
631                 return false;
632         } else if (result & LaTeX::NO_OUTPUT) {
633                 string const s = _("The operation resulted in");
634                 string const t = _("an empty file.");
635                 WriteAlert(_("Resulting file is empty"), s, t);
636                 return false;
637         }
638         return true;
639 }
640
641
642 bool Converter::runLaTeX(Buffer const * buffer, string const & command)
643 {
644         if (!buffer)
645                 return false;
646
647         BufferView * bv = buffer->getUser();
648         string name = buffer->getLatexName();
649         bool need_redraw = false;
650
651         if (bv) {
652                 ProhibitInput(bv);
653                 bv->owner()->getMiniBuffer()->Set(_("Running LaTeX..."));
654                 // Remove all error insets
655                 need_redraw = bv->removeAutoInsets();
656         }
657
658
659         // do the LaTex run(s)
660         TeXErrors terr;
661         LaTeX latex(command, name, buffer->filepath);
662         int result = latex.run(terr,
663                                bv ? bv->owner()->getMiniBuffer() : 0);
664         
665
666         if (bv) {
667                 if ((result & LaTeX::ERRORS)) {
668                         // Insert all errors as errors boxes
669                         bv->insertErrors(terr);
670                         need_redraw = true;
671                 }
672
673                 // if we removed error insets before we ran LaTeX or if we inserted
674                 // error insets after we ran LaTeX this must be run:
675                 if (need_redraw) {
676                         bv->redraw();
677                         bv->fitCursor(bv->text);
678                 }
679         }
680
681         // check return value from latex.run().
682         if ((result & LaTeX::NO_LOGFILE)) {
683                 WriteAlert(_("LaTeX did not work!"),
684                            _("Missing log file:"), name);
685         } else if ((result & LaTeX::ERRORS)) {
686                 int num_errors = latex.getNumErrors();
687                 string s;
688                 string t;
689                 if (num_errors == 1) {
690                         s = _("One error detected");
691                         t = _("You should try to fix it.");
692                 } else {
693                         s = tostr(num_errors);
694                         s += _(" errors detected.");
695                         t = _("You should try to fix them.");
696                 }
697                 WriteAlert(_("There were errors during the LaTeX run."),
698                            s, t);
699         }  else if (result & LaTeX::NO_OUTPUT) {
700                 string const s = _("The operation resulted in");
701                 string const t = _("an empty file.");
702                 WriteAlert(_("Resulting file is empty"), s, t);
703         }
704
705         if (bv)
706                 AllowInput(bv);
707  
708         int const ERROR_MASK = 
709                         LaTeX::NO_LOGFILE |
710                         LaTeX::ERRORS |
711                         LaTeX::NO_OUTPUT;
712         
713         return (result & ERROR_MASK) == 0;
714
715 }
716
717
718 string const Converter::dvi_papersize(Buffer const * buffer)
719 {
720         char real_papersize = buffer->params.papersize;
721         if (real_papersize == BufferParams::PAPER_DEFAULT)
722                 real_papersize = lyxrc.default_papersize;
723
724         switch (real_papersize) {
725         case BufferParams::PAPER_A3PAPER:
726                 return "a3";
727         case BufferParams::PAPER_A4PAPER:
728                 return "a4";
729         case BufferParams::PAPER_A5PAPER:
730                 return "a5";
731         case BufferParams::PAPER_B5PAPER:
732                 return "b5";
733         case BufferParams::PAPER_EXECUTIVEPAPER:
734                 return "foolscap";
735         case BufferParams::PAPER_LEGALPAPER:
736                 return "legal";
737         case BufferParams::PAPER_USLETTER:
738         default:
739                 return "us";
740         }
741 }
742
743
744 string const Converter::dvips_options(Buffer const * buffer)
745 {
746         string result;
747         if (!buffer)
748                 return result;
749
750         if (buffer->params.use_geometry
751             && buffer->params.papersize2 == BufferParams::VM_PAPER_CUSTOM
752             && !lyxrc.print_paper_dimension_flag.empty()
753             && !buffer->params.paperwidth.empty()
754             && !buffer->params.paperheight.empty()) {
755                 // using a custom papersize
756                 result = lyxrc.print_paper_dimension_flag;
757                 result += ' ' + buffer->params.paperwidth;
758                 result += ',' + buffer->params.paperheight;
759         } else {
760                 string paper_option = dvi_papersize(buffer);
761                 if (paper_option == "us")
762                         paper_option = "letter";
763                 if (paper_option != "letter" ||
764                     buffer->params.orientation != BufferParams::ORIENTATION_LANDSCAPE) {
765                         // dvips won't accept -t letter -t landscape.  In all other
766                         // cases, include the paper size explicitly.
767                         result = lyxrc.print_paper_flag;
768                         result += ' ' + paper_option;
769                 }
770         }
771         if (buffer->params.orientation == BufferParams::ORIENTATION_LANDSCAPE)
772                 result += ' ' + lyxrc.print_landscape_flag;
773         return result;
774 }
775
776
777 void Converter::init()
778 {
779 }
780
781 /// The global instance
782 Formats formats;
783
784 // The global copy of the system lyxrc entries (everything except preferences)
785 Formats system_formats;