]> git.lyx.org Git - lyx.git/blob - src/LaTeX.C
b1758d2749634a7e49bd69b3d7dbd7da588a73d2
[lyx.git] / src / LaTeX.C
1 /* This file is part of
2  * ======================================================
3  * 
4  *           LyX, The Document Processor         
5  *           Copyright 1995 Matthias Ettrich
6  *           Copyright 1995-1999 The LyX Team.
7  *
8  *           This file is Copyright 1996-1999
9  *           Lars Gullik Bjønnes
10  *
11  * ======================================================
12  */
13
14 #include <config.h>
15
16 #include <cstdio>
17 #include <cstdlib>
18
19 #ifdef __GNUG__
20 #pragma implementation
21 #endif
22
23 #include "support/filetools.h"
24 #include "LaTeX.h"
25 #include "lyxlex.h"
26 #include "support/FileInfo.h"
27 #include "debug.h"
28 #include "support/lyxlib.h"
29 #include "support/syscall.h"
30 #include "support/syscontr.h"
31 #include "support/path.h"
32 #include "bufferlist.h"
33 #include "minibuffer.h"
34 #include "gettext.h"
35
36 extern BufferList bufferlist;
37
38 struct texfile_struct {
39         LaTeX::TEX_FILES file;
40         char const *extension;
41 };
42
43 static
44 const texfile_struct all_files[] = {
45         { LaTeX::AUX, ".aux"},
46         { LaTeX::BBL, ".bbl"},
47         { LaTeX::DVI, ".dvi"},
48         { LaTeX::GLO, ".glo"},
49         { LaTeX::IDX, ".idx"},
50         { LaTeX::IND, ".ind"},
51         { LaTeX::LOF, ".lof"},
52         { LaTeX::LOA, ".loa"},
53         { LaTeX::LOG, ".log"},
54         { LaTeX::LOT, ".lot"},
55         { LaTeX::TOC, ".toc"},
56         { LaTeX::LTX, ".ltx"},
57         { LaTeX::TEX, ".tex"}
58 };
59
60 // This should perhaps be placed in LyXLex
61 static
62 string readLine(FILE *file)
63 {
64         if (feof(file))
65                 return string();
66
67         int i = 0;
68         char s[512];
69
70         do {
71                 s[i] = fgetc(file);
72                 i++;
73         } while (!feof(file) && s[i-1] != '\n' && i<510);
74         s[i] = '\0';
75         string tmp;
76         if (i == 1 && feof(file))
77                 ;
78         else
79                 tmp = s;
80
81         return tmp;
82 }
83
84
85
86 /*
87  * CLASS TEXERRORS
88  */
89
90 // I did not leave this inlined because DEC cxx does not like
91 // variables declarations in inlined code (JMarc)
92 TeXErrors::~TeXErrors()
93 {
94         Error *tmp;
95         while (errors) {
96                 tmp = errors->next_error;
97                 delete errors;
98                 errors = tmp;
99         }
100 }
101
102
103 void TeXErrors::scanError(LyXLex &lex)
104 {
105         string token = lex.GetString();
106         // Sometimes the error string goes over more than one
107         // line, and we need to get them all.
108         string errstr;
109         string tmp = frontStrip(readLine(lex.getFile()));
110         if (tmp == "\n" || tmp.empty()) {
111                 tmp = frontStrip(readLine(lex.getFile()));
112                 if (contains(tmp, "See the LaTeX manual")) {
113                         do {
114                                 tmp = readLine(lex.getFile());
115                         } while (!tmp.empty() && !contains(tmp, "..."));
116                 }
117                 tmp = frontStrip(readLine(lex.getFile()));
118         }
119
120         while ((tmp != "\n" || !contains(errstr, "l."))
121                 && !prefixIs(tmp, "! ")
122                 && !contains(tmp, "(job aborted")
123                 && !tmp.empty()) {
124                 errstr += tmp;
125                 tmp = frontStrip(readLine(lex.getFile()));
126         }
127         lyxerr.debug() << "tmp: " << errstr << endl;
128         int line = 0;
129         // unfortunately the error line is not always given
130         // by "l.###" in the beginning of the error string
131         // therefore we must search for "l.###" in the error
132         // msg.
133         if (contains(errstr, "l.")) {
134                 // We make a const copy to make [] fast. (Asger)
135                 string const es(errstr);
136                 for (string::size_type i = 2; i < es.length(); ++i) {
137                         if (es[i-2] == 'l' && es[i-1] == '.' &&
138                             (es[i] >= '0' && es[i]<= '9')) {
139                                 line = atoi(es.c_str() + i);
140                                 break;
141                         }
142                 }
143         }
144         insertError(line, token, errstr);
145
146         if (prefixIs(tmp, "! ")) {
147                 scanError(lex);
148         }
149 }
150
151
152 bool TeXErrors::getFirstError(int *line, string *text)
153 {
154         next_error = errors;
155         if (next_error) {
156                 *line = next_error->error_in_line;
157                 *text = next_error->error_desc + "\n" + next_error->error_text;
158                 next_error = next_error->next_error;
159                 return true;
160         }
161         return false;
162 }
163
164
165 bool TeXErrors::getNextError(int *line, string *text)
166 {
167         if (next_error) {
168                 *line = next_error->error_in_line;
169                 *text = next_error->error_desc + "\n" + next_error->error_text;
170                 next_error = next_error->next_error;
171                 return true;
172         }
173         return false;
174 }
175
176
177 void TeXErrors::insertError(int line, string const &error_desc,
178                             string const &error_text)
179 {
180         Error *newerr = new Error(line, error_desc, error_text);
181         if (errors) {
182                 Error *tmperr = errors;
183                 while (tmperr->next_error) tmperr = tmperr->next_error;
184                 tmperr->next_error = newerr;
185         } else {
186                 errors = newerr;
187         }
188 }
189
190
191 void TeXErrors::printErrors()
192 {
193         lyxerr << "Printing errors." << endl;
194         if (errors) {
195                 Error *tmperr = errors;
196                 do {
197                         lyxerr << "Error in line "
198                                << tmperr->error_in_line
199                                << ": " << tmperr->error_desc
200                                << '\n' << tmperr->error_text << endl;
201                         tmperr = tmperr->next_error;
202                 } while (tmperr);
203         }
204 }
205
206
207 void TeXErrors::printWarnings()
208 {
209 }
210
211
212 void TeXErrors::printStatus()
213 {
214         lyxerr << "Error struct:"
215                << "\n   status: " << status
216                << "\n   no err: " << number_of_errors << endl;
217         if (status == LaTeX::NO_ERRORS)  lyxerr << "NO_ERRORS" << endl;
218         if (status & LaTeX::NO_LOGFILE)  lyxerr << "NO_LOGFILE" << endl;
219         if (status & LaTeX::NO_OUTPUT)   lyxerr << "NO_OUTPUT" << endl;
220         if (status & LaTeX::UNDEF_REF)   lyxerr << "UNDEF_REF" << endl;
221         if (status & LaTeX::RERUN)       lyxerr << "RERUN" << endl;
222         if (status & LaTeX::TEX_ERROR)   lyxerr << "TEX_ERROR" << endl;
223         if (status & LaTeX::TEX_WARNING) lyxerr << "TEX_WARNING" << endl;
224         if (status & LaTeX::NO_FILE)     lyxerr << "NO_FILE" << endl;
225 }
226
227
228 /*
229  * CLASS LaTeX
230  */
231
232 LaTeX::LaTeX(string const & latex, string const & f, string const & p)
233                 : cmd(latex), file(f), path(p)
234 {
235         tex_files = NO_FILES;
236         file_count = sizeof(all_files) / sizeof(texfile_struct);
237         num_errors = 0;
238         depfile = file + ".dep";
239 }
240
241
242 int LaTeX::run(TeXErrors &terr, MiniBuffer *minib)
243         // We know that this function will only be run if the lyx buffer
244         // has been changed. We also know that a newly written .tex file
245         // is always different from the previous one because of the date
246         // in it. However it seems safe to run latex (at least) on time each
247         // time the .tex file changes.
248 {
249         int scanres = LaTeX::NO_ERRORS;
250         unsigned int count = 0; // number of times run
251         num_errors = 0; // just to make sure.
252         const unsigned int MAX_RUN = 6;
253         DepTable head; // empty head
254         bool rerun = false; // rerun requested
255         
256         // The class LaTeX does not know the temp path.
257         bufferlist.updateIncludedTeXfiles(GetCWD());
258         
259         // Never write the depfile if an error was encountered.
260         
261         // 0
262         // first check if the file dependencies exist:
263         //     ->If it does exist
264         //             check if any of the files mentioned in it have
265         //             changed (done using a checksum).
266         //                 -> if changed:
267         //                        run latex once and
268         //                        remake the dependency file
269         //                 -> if not changed:
270         //                        just return there is nothing to do for us.
271         //     ->if it doesn't exist
272         //             make it and
273         //             run latex once (we need to run latex once anyway) and
274         //             remake the dependency file.
275         //
276         FileInfo fi(depfile);
277         if (fi.exist()) {
278                 // Read the dep file:
279                 head.read(depfile);
280                 // Update the checksums
281                 head.update();
282                 
283                 lyxerr[Debug::LATEX] << "Dependency file exists" << endl;
284                 if (head.sumchange()) {
285                         ++count;
286                         lyxerr[Debug::LATEX]
287                                 << "Dependency file has changed\n"
288                                 << "Run #" << count << endl; 
289                         minib->Set(string(_("LaTeX run number ")) + tostr(count));
290                         minib->Store();
291                         this->operator()();
292                         scanres = scanLogFile(terr);
293                         if (scanres & LaTeX::ERRORS) return scanres; // return on error
294                 } else {
295                         lyxerr[Debug::LATEX] << "return no_change" << endl;
296                         return LaTeX::NO_CHANGE;
297                 }
298         } else {
299                 ++count;
300                 lyxerr[Debug::LATEX] << "Dependency file does not exist\n"
301                                      << "Run #" << count << endl;
302                 head.insert(file, true);
303                 minib->Set(string(_("LaTeX run number ")) + tostr(count));
304                 minib->Store();
305                 this->operator()();
306                 scanres = scanLogFile(terr);
307                 if (scanres & LaTeX::ERRORS) return scanres; // return on error
308         }
309
310         // update the dependencies.
311         deplog(head); // reads the latex log
312         deptex(head); // checks for latex files
313         head.update();
314
315         // 0.5
316         // At this point we must run external programs if needed.
317         // makeindex will be run if a .idx file changed or was generated.
318         // And if there were undefined citations or changes in references
319         // the .aux file is checked for signs of bibtex. Bibtex is then run
320         // if needed.
321         
322         // run makeindex
323         if (head.haschanged(ChangeExtension(file, ".idx", true))) {
324                 // no checks for now
325                 minib->Set(_("Running MakeIndex."));
326                 minib->Store();
327                 rerun=runMakeIndex(ChangeExtension(file,".idx",true));
328         }
329
330         // run bibtex
331         if (scanres & LaTeX::UNDEF_CIT || scanres & LaTeX::RERUN) {
332                 // Here we must scan the .aux file and look for
333                 // "\bibdata" and/or "\bibstyle". If one of those
334                 // tags is found -> run bibtex and set rerun = true;
335                 // no checks for now
336                 minib->Set(_("Running BibTeX."));
337                 minib->Store();
338                 rerun = runBibTeX(ChangeExtension(file, ".aux", true));
339         }
340         
341         // 1
342         // we know on this point that latex has been run once (or we just
343         // returned) and the question now is to decide if we need to run
344         // it any more. This is done by asking if any of the files in the
345         // dependency file has changed. (remember that the checksum for
346         // a given file is reported to have changed if it just was created)
347         //     -> if changed or rerun == true:
348         //             run latex once more and
349         //             update the dependency structure
350         //     -> if not changed:
351         //             we does nothing at this point
352         //
353         if (rerun || head.sumchange()) {
354                 rerun = false;
355                 ++count;
356                 lyxerr[Debug::LATEX]
357                         << "Dep. file has changed or rerun requested\n"
358                         << "Run #" << count << endl;
359                 minib->Set(string(_("LaTeX run number ")) + tostr(count));
360                 minib->Store();
361                 this->operator()();
362                 scanres = scanLogFile(terr);
363                 if (scanres & LaTeX::ERRORS) return scanres; // return on error
364                 // update the depedencies
365                 deplog(head); // reads the latex log
366                 head.update();
367         } else {
368                 lyxerr[Debug::LATEX] << "Dep. file has NOT changed" << endl;
369         }
370
371         // 1.5
372         // The inclusion of files generated by external programs like
373         // makeindex or bibtex might have done changes to pagenumbereing,
374         // etc. And because of this we must run the external programs
375         // again to make sure everything is redone correctly.
376         // Also there should be no need to run the external programs any
377         // more after this.
378         
379         // run makeindex if the <file>.idx has changed or was generated.
380         if (head.haschanged(ChangeExtension(file, ".idx", true))) {
381                 // no checks for now
382                 minib->Set(_("Running MakeIndex."));
383                 minib->Store();
384                 rerun = runMakeIndex(ChangeExtension(file, ".idx", true));
385         }
386         
387         // 2
388         // we will only run latex more if the log file asks for it.
389         // or if the sumchange() is true.
390         //     -> rerun asked for:
391         //             run latex and
392         //             remake the dependency file
393         //             goto 2 or return if max runs are reached.
394         //     -> rerun not asked for:
395         //             just return (fall out of bottom of func)
396         //
397         while ((head.sumchange() || rerun || (scanres & LaTeX::RERUN)) 
398                && count < MAX_RUN) {
399                 // Yes rerun until message goes away, or until
400                 // MAX_RUNS are reached.
401                 rerun = false;
402                 ++count;
403                 lyxerr[Debug::LATEX] << "Run #" << count << endl;
404                 minib->Set(string(_("LaTeX run number ")) + tostr(count));
405                 minib->Store();
406                 this->operator()();
407                 scanres = scanLogFile(terr);
408                 if (scanres & LaTeX::ERRORS) return scanres; // return on error
409                 // keep this updated
410                 head.update();
411         }
412
413         // Write the dependencies to file.
414         head.write(depfile);
415         lyxerr[Debug::LATEX] << "Done." << endl;
416         return scanres;
417 }
418
419
420 int LaTeX::operator()()
421 {
422 #ifndef __EMX__
423         string tmp = cmd + ' ' + file + " > /dev/null";
424 #else // cmd.exe (OS/2) causes SYS0003 error at "/dev/null"
425         string tmp = cmd + ' ' + file + " > nul";
426 #endif
427         Systemcalls one;
428         return one.startscript(Systemcalls::System, tmp);
429 }
430
431
432 bool LaTeX::runMakeIndex(string const &file)
433 {
434         lyxerr[Debug::LATEX] << "idx file has been made,"
435                 " running makeindex on file "
436                              <<  file << endl;
437
438         // It should be possible to set the switches for makeindex
439         // sorting style and such. It would also be very convenient
440         // to be able to make style files from within LyX. This has
441         // to come for a later time. (0.13 perhaps?)
442         string tmp = "makeindex -c -q ";
443         tmp += file;
444         Systemcalls one;
445         one.startscript(Systemcalls::System, tmp);
446         return true;
447 }
448
449
450 bool LaTeX::runBibTeX(string const &file)
451 {
452         LyXLex lex(0, 0);
453         string token;
454         if (!lex.setFile(file)) {
455                 // unable to open .aux file
456                 // return at once
457                 return false;
458         }
459
460         while (lex.IsOK()) {
461                 if (lex.EatLine())
462                         token=lex.GetString();
463                 else // blank line in the file being read
464                         continue;
465
466                 if (contains(token, "\\bibdata{")) {
467                         // run bibtex and
468                         string tmp="bibtex ";
469                         tmp += ChangeExtension(file, string(), true);
470                         Systemcalls one;
471                         one.startscript(Systemcalls::System, tmp);
472                         return true;
473                 }
474                 
475         }
476         // bibtex was not run.
477         return false;
478 }
479
480
481 int LaTeX::scanLogFile(TeXErrors &terr)
482 {
483         int retval = NO_ERRORS;
484         string tmp = ChangeExtension(file, ".log", true);
485         
486         LyXLex lex(0, 0);
487         if (!lex.setFile(tmp)) {
488                 // unable to open file
489                 // return at once
490                 retval |= NO_LOGFILE;
491                 return retval;
492         }
493
494         string token;
495         while (lex.IsOK()) {
496                 if (lex.EatLine())
497                         token = lex.GetString();
498                 else // blank line in the file being read
499                         continue;
500
501                 lyxerr[Debug::LATEX] << token << endl;
502                 
503                 if (prefixIs(token, "LaTeX Warning:")) {
504                         // Here shall we handle different
505                         // types of warnings
506                         retval |= LATEX_WARNING;
507                         lyxerr[Debug::LATEX] << "LaTeX Warning." << endl;
508                         if (contains(token, "Rerun to get cross-references")) {
509                                 retval |= RERUN;
510                                 lyxerr[Debug::LATEX]
511                                         << "We should rerun." << endl;
512                         } else if (contains(token, "Citation")
513                                    && contains(token, "on page")
514                                    && contains(token, "undefined")) {
515                                 retval |= UNDEF_CIT;
516                         }
517                 } else if (prefixIs(token, "Package")) {
518                         // Package warnings
519                         retval |= PACKAGE_WARNING;
520                         if (contains(token, "natbib Warning:")) {
521                                 // Natbib warnings
522                                 if (contains(token, "Citation")
523                                     && contains(token, "on page")
524                                     && contains(token, "undefined")) {
525                                         retval |= UNDEF_CIT;
526                                 }
527                         } else if (contains(token, "Rerun LaTeX.")) {
528                                 // at least longtable.sty might use this.
529                                 retval |= RERUN;
530                         }
531                 } else if (prefixIs(token, "! LaTeX Error:")) {
532                         // Here shall we handle different
533                         // types of errors
534                         retval |= LATEX_ERROR;
535                         lyxerr[Debug::LATEX] << "LaTeX Error." << endl;
536                         // this is not correct yet
537                         terr.scanError(lex);
538                         num_errors++;
539                 } else if (prefixIs(token, "! ")) {
540                         // Ok, we have something that looks like a TeX Error
541                         // but what do we really have.
542
543                         // Just get the error description:
544                         string desc(token);
545                         desc.erase(0, 2);
546
547                         if (contains(desc, "Undefined control sequence")) {
548                                 retval |= TEX_ERROR;
549                                 lyxerr[Debug::LATEX] << "TeX Error." << endl;
550                                 terr.scanError(lex);
551                                 num_errors++;
552                         } else {
553                                 // get the next line
554                                 lex.next();
555                                 string tmp = lex.GetString();
556                                 if (prefixIs(tmp, "l.")) {
557                                 // we have a latex error
558                                         retval |=  TEX_ERROR;
559                                         lyxerr[Debug::LATEX]
560                                                 <<"TeX Error." << endl;
561                                 // get the line number:
562                                         int line = 0;
563                                         sscanf(tmp.c_str(), "l.%d", &line);
564                                 // get the rest of the message:
565                                         string errstr;
566                                         lex.EatLine();
567                                         tmp = lex.GetString();
568                                         while ((tmp != "\n" || !contains(errstr, "l."))
569                                                && !prefixIs(tmp, "! ")
570                                                && !contains(tmp, "(job aborted")
571                                                && !tmp.empty()) {
572                                                 errstr += tmp;
573                                                 errstr += "\n";
574                                                 lex.EatLine();
575                                                 tmp = lex.GetString();
576                                         }
577                                         terr.insertError(line, desc, errstr);
578                                         num_errors++;
579                                 }
580                         }
581                 } else {
582                         // information messages, TeX warnings and other
583                         // warnings we have not caught earlier.
584                         if (prefixIs(token, "Overfull ")) {
585                                 retval |= TEX_WARNING;
586                         } else if (prefixIs(token, "Underfull ")) {
587                                 retval |= TEX_WARNING;
588                         } else if (contains(token, "Rerun to get citations")) {
589                                 // Natbib seems to use this.
590                                 retval |= RERUN;
591                         } else if (contains(token, "No pages of output")) {
592                                 // A dvi file was not created
593                                 retval |= NO_OUTPUT;
594                         } else if (contains(token, "That makes 100 errors")) {
595                                 // More than 100 errors were reprted
596                                 retval |= TOO_MANY_ERRORS;
597                         }
598                 }
599         }       
600         return retval;
601 }
602
603
604 void LaTeX::deplog(DepTable & head)
605 {
606         // This function reads the LaTeX log file end extracts all the external
607         // files used by the LaTeX run. The files are then entered into the
608         // dependency file.
609
610         string logfile = ChangeExtension(file, ".log", true);
611         FilePtr in(logfile, FilePtr::read);
612         bool not_eof = true;
613         if (in()) while (not_eof) { // We were able to open the file
614                 // Now we read chars until we find a '('
615                 int c;
616                 do {
617                         c = fgetc(in());
618                 } while (c != EOF && c != '(');
619                 if (c == EOF) { 
620                         // Nothing more we can do
621                         not_eof = false; 
622                         continue;
623                 } 
624
625                 // We now have c == '(', we now read the the sequence of
626                 // chars until reaching EOL, or ' ' and put that into a string.
627
628                 string foundfile;
629                 c = fgetc(in());
630                 while (c != '\n' && c != ' ' && c != ')') {
631                         foundfile += char(c);
632                         c = fgetc(in());
633                 }
634                 if (foundfile.empty()) continue;
635
636                 lyxerr[Debug::LATEX] << "Found file: " 
637                                      << foundfile << endl;
638                 // Ok now we found a file.
639                 // Now we should make sure that
640                 // this is a file that we can
641                 // access through the normal
642                 // paths:
643                 // (1) foundfile is an
644                 //     absolute path and should
645                 //     be inserted.
646                 if (AbsolutePath(foundfile)) {
647                         lyxerr[Debug::LATEX] << "AbsolutePath file: " 
648                                              << foundfile << endl;
649                         // On inital insert we want to do the update at once
650                         // since this file can not be a file generated by
651                         // the latex run.
652                         head.insert(foundfile, true);
653                         continue;
654                 }
655
656                 // (2) foundfile is in the tmpdir
657                 //     insert it into head
658                 if (FileInfo(OnlyFilename(foundfile)).exist()) {
659                         if (suffixIs(foundfile, ".aux")) {
660                                 lyxerr[Debug::LATEX] << "We don't want "
661                                                      << OnlyFilename(foundfile)
662                                                      << " in the dep file"
663                                                      << endl;
664                         } else if (suffixIs(foundfile, ".tex")) {
665                                 // This is a tex file generated by LyX
666                                 // and latex is not likely to change this
667                                 // during its runs.
668                                 lyxerr[Debug::LATEX] << "Tmpdir TeX file: "
669                                                      << OnlyFilename(foundfile)
670                                                      << endl;
671                                 head.insert(foundfile, true);
672                         } else {
673                                 lyxerr[Debug::LATEX] << "In tmpdir file:"
674                                                      << OnlyFilename(foundfile)
675                                                      << endl;
676                                 head.insert(OnlyFilename(foundfile));
677                         }
678                         continue;
679                 }
680
681                 // (3) the foundfile can be
682                 //     found in the same dir
683                 //     as the .lyx file and
684                 //     should be inserted.
685                 Path p(path);
686                 if (FileInfo(foundfile).exist()) {
687                         lyxerr << "LyX Strange: this should actually never"
688                                 " happen anymore, this it should be"
689                                 " handled by the Absolute check."
690                                << endl;
691                         lyxerr[Debug::LATEX] << "Same Directory file: " 
692                                              << foundfile << endl;
693                         head.insert(foundfile);
694                         continue;
695                 }
696                 
697                 lyxerr[Debug::LATEX]
698                         << "Not a file or we are unable to find it."
699                         << endl;
700         }
701 }
702
703
704 void LaTeX::deptex(DepTable &head)
705 {
706         int except = AUX|LOG|DVI|BBL|IND|GLO; 
707         string tmp;
708         FileInfo fi;
709         for (int i = 0; i < file_count; i++) {
710                 if (!(all_files[i].file & except)) {
711                         tmp = ChangeExtension(file,
712                                               all_files[i].extension,
713                                               true);
714                         lyxerr[Debug::LATEX] << "deptex: " << tmp << endl;
715                         if (fi.newFile(tmp).exist())
716                                 head.insert(tmp);
717                 }
718         }
719 }