View Javadoc
1   /*
2    * The akquinet maven-latex-plugin project
3    *
4    * Copyright (c) 2011 by akquinet tech@spree GmbH
5    *
6    * Licensed under the Apache License, Version 2.0 (the "License");
7    * you may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    *
10   * http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  package eu.simuline.m2latex.core;
20  
21  import java.io.File;
22  import java.io.FileFilter;
23  import java.io.IOException;
24  import java.io.InputStream;
25  import java.io.PrintStream;
26  import java.time.Instant;
27  import java.time.ZonedDateTime;
28  import java.util.ArrayList;
29  import java.util.Collection;
30  import java.util.List;
31  import java.util.Map;
32  import java.util.TreeMap;
33  import java.util.Optional;
34  import java.util.Set;
35  import java.util.SortedSet;
36  import java.util.TreeSet;
37  
38  import com.florianingerl.util.regex.Matcher;
39  import com.florianingerl.util.regex.Pattern;
40  
41  //import java.util.regex.Pattern;
42  //import java.util.regex.Matcher;
43  
44  import eu.simuline.m2latex.core.CommandExecutor.CmdResult;
45  // TBD: all this must be eliminated.
46  // import eu.simuline.m2latex.antTask.LatexCfgTask;
47  // import eu.simuline.m2latex.antTask.LatexClrTask;
48  import eu.simuline.m2latex.mojo.CfgLatexMojo;
49  // import eu.simuline.m2latex.mojo.ChkMojo;
50  // import eu.simuline.m2latex.mojo.ClearMojo;
51  // import eu.simuline.m2latex.mojo.GraphicsMojo;
52  
53  // idea: use latex2rtf and unoconv
54  // idea: targets for latex2html, latex2man, latex2png and many more.
55  
56  // TBD: rework
57  /**
58   * The latex processor creates various output from latex sources
59   * including also preprocessing of graphic files in several formats.
60   * This is the core class of this piece of software.
61   * The main method is {@link #create(SortedSet<Target>)} which is executed by
62   * the ant task
63   * and by the maven plugin given by { @link CfgLatexMojo}.
64   * Also important are {@link #clearAll()} which is executed by
65   * the maven plugin given by { @link ClearMojo}.
66   * also {@link #processGraphics()} which is executed by
67   * the maven plugin given by { @link GraphicsMojo}
68   * which is helpful for information development.
69   * <p>
70   * This class delegates preprocessing of the graphic files,
71   * selection of the latex main files and deleting their targets
72   * to {@link LatexPreProcessor}.
73   * Processing of the latex main files is done in
74   * {@link #create(SortedSet<Target>)}
75   * according to the target(s) given by the parameters.
76   * The elements of the enumeration {@link Target} use methods
77   * {@link #processLatex2rtf(LatexMainDesc)},
78   * {@link #processLatex2dvi(LatexMainDesc)},
79   * {@link #processLatex2pdf(LatexMainDesc)},
80   * {@link #processLatex2html(LatexMainDesc)},
81   * {@link #processLatex2odt(LatexMainDesc)},
82   * {@link #processLatex2docx(LatexMainDesc)}
83   * and {@link #processLatex2txt(LatexMainDesc)}.
84   */
85  public class LatexProcessor extends AbstractLatexProcessor {
86  
87  
88  
89  
90    // Note that two \\ represent a single \ in the string.
91    // Thus \\\\ represents '\\' in the pattern,
92    // which in turn represents a single \.
93    static final String PATTERN_OUFULL_HVBOX = "^(Ov|Und)erfull \\\\[hv]box \\(";
94  
95    // LaTeX (notably, .tex is not needed )
96    final static String SUFFIX_TOC = ".toc";
97    final static String SUFFIX_LOF = ".lof";
98    final static String SUFFIX_LOT = ".lot";
99    final static String SUFFIX_LOL = ".lol";
100 
101   final static String SUFFIX_AUX = ".aux";
102   final static String SUFFIX_DVI = ".dvi";
103   final static String SUFFIX_XDV = ".xdv";
104 
105   // bibtex
106   final static String SUFFIX_BLG = ".blg";
107   final static String SUFFIX_BBL = ".bbl";
108 
109   // makeindex for index
110   // unsorted and not unified index created by latex
111   final static String SUFFIX_IDX = TexFileUtils.SUFFIX_IDX;
112   // sorted and unified index created by makeindex
113   final static String SUFFIX_IND = TexFileUtils.SUFFIX_IND;
114   // log file created by makeindex
115   final static String SUFFIX_ILG = TexFileUtils.SUFFIX_ILG;
116 
117   // sorted and unified glossary created by makeindex
118   final static String SUFFIX_GLS = ".gls";
119   // logging file for makeindex used with glossaries
120   final static String SUFFIX_GLG = ".glg";
121 
122 
123   // logging file for pythontex written by pythontexW
124   final static String SUFFIX_PLG = ".plg";
125 
126 
127   // file in PREFIX_PYTEX_OUT_FOLDER 
128   // created by auxiliary program pythontex
129   private final static String SUFFIX_PYTXMCR = ".pytxmcr";
130 
131   // latex2rtf
132   private final static String SUFFIX_RTF = ".rtf";
133 
134   // odt2doc
135   private final static String SUFFIX_ODT = ".odt";
136 
137   // tex4ht
138   // FIXME: works for unix only
139   final static String SUFFIX_HTML = ".html";
140 
141   // pdftotext
142   private final static String SUFFIX_TXT = ".txt";
143 
144   // ChkTeX: log file
145   private final static String SUFFIX_CLG = ".clg";
146 
147   /**
148    * The implicit default identifier of an index
149    * hardcoded into the package <code>splitidx</code>
150    * and into the program <code>splitindex</code>.
151    */
152   private final static String IMPL_IDENT_IDX = "idx";
153 
154   /**
155    * Separator <code>-</code> of the index identifier
156    * used in files <code>xxx-yy.idx</code>, <code>xxx-yy.ind</code>
157    * and <code>xxx-yy.ilg</code>.
158    * This is hardcoded by the package <code>splitidx</code>
159    * when reading <code>xxx-yy.ind</code> files
160    * but is configurable as an option in the program <code>splitindex</code>.
161    */
162   private final static String SEP_IDENT_IDX = "-";
163 
164   private final ParameterAdapter paramAdapt;
165 
166   /**
167    * The graphics preprocessor used by this processor.
168    */
169   private final LatexPreProcessor preProc;
170 
171   /**
172    * The meta info provider used by this processor.
173    */
174   private final MetaInfo metaInfo;
175 
176   /**
177    * An <code>Optional</code> which contains the name of a latex2pdf converter 
178    * while processing a latex file specifying such a converter 
179    * in a magic comment. 
180    * Else this <code>Optional</code> is empty. 
181    */
182   private Optional<String> latex2PdfCmdMagic = Optional.empty();
183 
184   /**
185    * Index of the group in {@link Settings#patternMultiIndex}
186    * containing the string <code>\indexentry</code>. 
187    * The default is 3 and this may hardly change. 
188    */
189   final static int GRP_IDX_KEYPAGE = 3;
190 
191   /**
192    * Index of the group in {@link Settings#patternMultiIndex}
193    * containing the identifier of the index. 
194    * The default is 2 and this may hardly change. 
195    */
196   final static int GRP_IDX_IDENT = 2;
197 
198   /**
199    * Index of the group in {@link Settings#patternMultiIndex}
200    * containing the string <code>\indexentry</code>. 
201    * The default is 1 and this may hardly change. 
202    */
203   final static int GRP_IDX_IDXENTRY = 1;
204 
205 
206   // also for tests
207   LatexProcessor(Settings settings, CommandExecutor executor, LogWrapper log,
208       TexFileUtils fileUtils, ParameterAdapter paramAdapt) {
209     super(settings, executor, log, fileUtils);
210     this.paramAdapt = paramAdapt;
211     this.preProc = new LatexPreProcessor(this.settings, this.executor, this.log,
212         this.fileUtils);
213     this.metaInfo = new MetaInfo(this.executor, this.log);
214     this.latex2PdfCmdMagic = Optional.empty();
215   }
216 
217   /**
218    * Creates a LatexProcessor with parameters given by <code>settings</code>
219    * which logs onto <code>log</code> and used by <code>paramAdapt</code>.
220    *
221    * @param settings
222    *    the settings controlling latex processing
223    * @param log
224    *    the logger to write on events while processing
225    * @param paramAdapt
226    *    the parameter adapter, refers to maven-plugin or ant-task.
227    */
228   public LatexProcessor(Settings settings, LogWrapper log,
229       ParameterAdapter paramAdapt) {
230     this(settings, new CommandExecutor(log), log, new TexFileUtils(log),
231         paramAdapt);
232   }
233 
234   /**
235    * Returns the set of targets to be built. 
236    * If <code>desc</code> indicates 
237    * that the TEX file specifies targets via a magic comment, 
238    * then this is the build target to be returned. 
239    * <p>
240    * Else, the target set from the configuration, <code>targetSet/code> 
241    * is the base. 
242    * If <code>docClasses2Targets</code> excludes targets 
243    * for the document class specified by <code>desc</code>, 
244    * this must be taken into account also. 
245    * @param desc
246    *    the description of the main file to be built. 
247    *    In this context, the document class is relevant 
248    *    and optionally the targets. 
249    * @param docClasses2Targets
250    *    reflects the according setting. 
251    * @param targetSet
252    *    reflects the setting 'targets' for goal <code>cfg</code> 
253    *    or the one element target set given by the goal directly. 
254    * @return 
255    *    the set of targets to be built. 
256    * @throws BuildFailureException
257    */
258   Set<Target> getTargetsForBuild(LatexMainDesc desc,
259                                   Map<String, Set<Target>> docClasses2Targets,
260                                   SortedSet<Target> targetSet)
261       throws BuildFailureException {
262 
263     Optional<String> targetsMagic =
264         desc.groupMatch(LatexMainParameterNames.targetsMagic);
265     if (targetsMagic.isPresent()) {
266       this.log.info("Magic comment 'targets=" + targetsMagic.get() + "' overrides settings. ");
267       return Settings.getTargets(targetsMagic.get(),
268           TargetsContext.targetsMagic);
269     }
270     // Here, the targets are not given by a magic comment 
271     // Thus they come from the setting targets 
272     // combined with the targets giben by docClasses2Targets and docClass 
273 
274     //String docClassOpt = desc.getDocClass();
275     // if (docClassOpt.isEmpty()) {
276     //   this.log.warn("WLPXX: For file '" + desc.texFile
277     //       + "' targets are not restricted by unknown document class. ");
278     //   reachableTargets = targetSet;
279     // } else {
280 
281     String docClass = desc.getDocClass();
282     Set<Target> possibleTargets = docClasses2Targets.get(docClass);
283     if (possibleTargets == null) {
284       // Here, to docClass no restriction is attached 
285       this.log.warn("WLP09: For file '" + desc.texFile
286           + "' targets are neither specified by magic comment nor restricted by document class '" + docClass
287           + "'. ");
288       return targetSet;
289     }
290     // Here, targetSet must be intersected with the targets possible for the docClass 
291     Set<Target> unreachableTargets = new TreeSet<Target>(targetSet);
292     unreachableTargets.removeAll(possibleTargets);
293     if (!unreachableTargets.isEmpty()) {
294       this.log.info("Skipping targets " + unreachableTargets + ". ");
295     }
296     Set<Target> reachableTargets = new TreeSet<Target>(targetSet);
297     reachableTargets.retainAll(possibleTargets);
298     return reachableTargets;
299 
300     //}
301   }
302 
303   // TBD: rework 
304   /**
305    * Defines creational ant-task defined in { @link LatexCfgTask} 
306    * and the according goals in { @link CfgLatexMojo} 
307    * and subclasses of the maven plugin. 
308    * <p>
309    * This consists in reading the parameters 
310    * via {@link ParameterAdapter#initialize()} 
311    * processing graphic-files 
312    * via {@link LatexPreProcessor#processGraphicsSelectMain(File, DirNode, boolean)} 
313    * and processing the tex main files 
314    * via {@link Target#processSource(LatexProcessor, LatexMainDesc)}. 
315    * If a diff-tool for the target format is available
316    * and if check by diff is specified, 
317    * the resulting file is checked to be equal to a specified file. 
318    * A {@link BuildFailureException} is thrown 
319    * if the files are not equal according to the diff tool. 
320    * The resulting files are identified by its suffixes 
321    * via  {@link Target#getPatternOutputFiles(Settings)}. 
322    * If no exception occurs before, they are copied to the target folder. 
323    * Finally, by default a cleanup is performed 
324    * invoking {@link TexFileUtils#cleanUp(DirNode, File, String)}. 
325    * <p>
326    * Logging: 
327    * <ul>
328    * <li> WFU01: Cannot read directory... 
329    * <li> WFU03: cannot close file 
330    * <li> EFU05: Cannot delete file 
331    * <li> EFU07, EFU08, EFU09: if filtering a file fails. 
332    * <li> WPP02: tex file may be latex main file 
333    * <li> WPP03: Skipped processing of files with suffixes ... 
334    * <li> EEX01, EEX02, EEX03, WEX04, WEX05: 
335    *      applications for preprocessing graphic files 
336    *      or processing a latex main file fails. 
337    * </ul>
338    *
339    * FIXME: exceptions not really clear. 
340    * 
341    * @param targetSet
342    *    the set of targets to be created. 
343    *    The target 'chk' does not create artifacts except the log file clg. 
344    *    The target set may have a single entry coming from the goal 
345    *    or, for goal {@link CfgLatexMojo} 
346    *    may be given by {@link Settings#getTargets()}. 
347    * @throws BuildFailureException 
348    *    <ul>
349    *    <li> TSS01 if 
350    *    the tex source directory does either not exist 
351    *    or is not a directory. 
352    *    <li> TSS02 if 
353    *    the tex source processing directory does either not exist 
354    *    or is not a directory. 
355    *    <li> TSS03 if 
356    *    the output directory exists and is no directory. 
357    *    <li> TSS04 if
358    *    the target set is not a subset of the set given by {@link Target}.
359    *    <li> TEX01 if 
360    *    invocation of applications for preprocessing graphic files 
361    *    or processing a latex main file fails 
362    *    <li> TFU01 if 
363    *    the target directory that would be returned 
364    *    exists already as a regular file. 
365    *    <li> TFU03, TFU04, TFU05, TFU06 if 
366    *    copy of output files to target folder fails. 
367    *    For details see 
368    *    {@link TexFileUtils#copyOutputToTargetFolder(File, FileFilter, File)}
369    *    <li>TLP01 if difference check is specified in settings and if 
370    *    the artifact could not be reproduced (currently for pdf only). 
371    *    <li>WPP05: Included tex files which are no latex main files 
372    *    <li>WPP06: Included tex files which are no latex main files 
373    *    <li>WPP07: inluded/excluded files not identified by their names.
374    *    <li>TBD: complete. 
375    *    </ul>
376    */
377   public void create(SortedSet<Target> targetSet) throws BuildFailureException {
378 
379     // TBD: clarify whether this is superfluous 
380     this.paramAdapt.initialize();
381     this.log.info("-----------create-------------");
382     this.log.debug("Settings: " + this.settings.toString());
383 
384     // may emit WSS01, WSS02 TBD: correct 
385     // may throw BuildFailureException TSS04, TSS11, TBD: complete. 
386     Map<String, Set<Target>> docClasses2Targets =
387         this.settings.getDocClassesToTargets();
388 
389     // may throw BuildFailureException TSS01
390     File texDir = this.settings.getTexSrcDirectoryFile();
391     assert texDir.exists()
392         && texDir.isDirectory() : "Expected existing tex folder " + texDir;
393 
394     // may throw BuildFailureException TSS02
395     File texProcDir = this.settings.getTexSrcProcDirectoryFile();
396     assert texProcDir.exists() && texProcDir
397         .isDirectory() : "Expected existing tex processing folder " + texDir;
398 
399     // constructor DirNode may log warning WFU01 Cannot read directory
400     DirNode node = new DirNode(texProcDir, this.fileUtils);
401     try {
402       // does two things at the same time 
403       // - determine latexMainFiles 
404       // - process graphics if preProcessInternally()
405       // may throw BuildFailureException TEX01,
406       // log warning WFU03, WPP02, WPP03,
407       // EEX01, EEX02, EEX03, WEX04, WEX05, EFU07, EFU08, 
408       //    EFU09: if filtering a file fails.
409       Collection<LatexMainDesc> latexMainDescs =
410           this.preProc.processGraphicsSelectMain(texProcDir, node, 
411           this.settings.getLatexmkUsage().preProcessInternally());
412 
413       for (LatexMainDesc desc : latexMainDescs) {
414         File texFile = desc.texFile;
415         this.log.info("Processing LaTeX file '" + desc.texFile + "'. ");
416 
417         // throws BuildFailureException TFU01
418         // if targetDir would be an existing non-directory
419         File targetDir = this.fileUtils.getTargetDirectory(texFile, texDir,
420             // throws BuildFailureException TSS03
421             // if exists and is no dir
422             this.settings.getOutputDirectoryFile());
423         assert !targetDir.exists()
424             || targetDir.isDirectory() : "Expected target folder " + targetDir
425                 + " folder if exists. ";
426 
427         Set<Target> targetsForBuild =
428           getTargetsForBuild(desc, docClasses2Targets, targetSet);
429         // currently, this is the only magic comment applying to all targets 
430         // even that is not really true: htlatex vs xhtlatex 
431         // and targets like txt 
432         // but chkDiff and latexmk are specific for target pdf 
433         // and targets is more globally and a special case. 
434         this.latex2PdfCmdMagic = desc
435           .groupMatch(LatexMainParameterNames.programMagic);
436         if (this.latex2PdfCmdMagic.isPresent()) {
437           // TBD: this comes before message converting to pdf: file. 
438           // Better: 
439           // - processing file.. 
440           // - then targets 
441           if (!this.latex2PdfCmdMagic.get().equals(this.settings.getCommand(ConverterCategory.LaTeX))) {
442             this.log.info("Magic comment 'program=" + this.latex2PdfCmdMagic.get() + "' overrides settings.");
443           }
444         }
445 
446         // may throw BuildFailureException TSS04
447         for (Target target : targetsForBuild) {
448           Optional<File> pdfFileCmpOpt = Optional.empty();
449           // Do it really, if an original artifact exists
450 
451           // Pattern patStd = Pattern.compile("^.*pdfstandard.*$");
452           // boolean claimsStd = patStd.matcher(docMetadataString).matches();
453           MetaData docMeta = docMetadata(desc);
454           if (!docMeta.isSpecMetadata()) {
455             this.log.debug("No DocumentMetadata.");
456           }
457           Optional<String> stdX = docMeta.claimedStandard(StdType.x);
458           if (stdX.isPresent()) {
459             this.log.warn("Specified standard " + stdX.get() + " which cannot be verified. ");// TBD: identifier 
460           }
461           Optional<String> stdA = docMeta.claimedStandard(StdType.a);
462           if (stdA.isPresent()) {
463             this.log.info("Specified standard " + stdA.get() + " to be verified later. ");
464           }
465           Optional<String> stdU = docMeta.claimedStandard(StdType.ua);
466           if (stdU.isPresent()) {
467             if (docMeta.tagging == TaggingState.on) {
468               this.log.info("Specified standard " + stdU.get() + " to be verified later. ");
469             } else {
470               this.log.warn("Specified standard " + stdU.get() + " without tagging; verification will fail. ");// TBD: identifier 
471             }
472           }
473 
474           //boolean claimsStd = docMeta.claimsSomeStandard();
475           // if (claimsStd) {
476           //   this.log.info("DocumenMetadata specifies standard.");
477           // } else {
478           //   this.log.info("DocumenMetadata does not specify standard.");
479           // }
480 
481           if ((stdX.isPresent() || stdA.isPresent() || stdU.isPresent()) && !docMeta.withXMP) {
482             this.log.warn("Specified standard without XMP. ");
483           }
484 
485 
486 
487 
488           boolean doTryVeri = target.hasVerificationTool() && (stdA.isPresent() || stdU.isPresent());
489           boolean doTryDiff = target.hasDiffTool() && isChkDiff(desc);
490           if (doTryDiff) {
491             File pdfFileCmp = TexFileUtils.getPdfFileDiff(desc.pdfFile,
492                 this.settings.getTexSrcDirectoryFile(),
493                 this.settings.getDiffDirectoryFile().getAbsoluteFile());
494             this.log.debug(String.format("cmp file %s", pdfFileCmp));
495             pdfFileCmpOpt = Optional.of(pdfFileCmp);
496 
497             // assert pdfFileCmp.exists();
498             if (pdfFileCmp.exists()) {
499               //runPdfInfo(desc.pdfFile);
500               long timestampSec = runPdfInfo(pdfFileCmp);
501 
502               this.log.info("Process with timestamp "
503                     + Instant.ofEpochSecond(timestampSec)
504                     + " (" + timestampSec + "sec)");
505 
506               assert pdfFileCmpOpt.isPresent();
507               this.executor.envSetTimestampAndTZutc(timestampSec);
508             } else {
509               // just a placeholder to signify that a reproducible artifact must be created 
510               // but there is no original 
511               this.executor.envUtc();
512               this.log.info("Process with time zone UTC. ");
513             }
514           } else {
515             this.executor.envReset();
516             // process with empty environment: neither timestamp nor timezone 
517           }
518           // Here, metaDataDescOpt is either 
519           // - empty meaning no diff requested 
520           // - not empty but without timestamp 
521           //   meaning shall be reproducible, i.e. env=TZ
522           // - not empty with timestamp 
523           //   meaning reproducible with timestamp 
524           //   i.e. env=TZ, SOURCE_DATE_EPOCH, FORCE_SOURCE_DATE 
525           //   Strictly speaking, FORCE_SOURCE_DATE not needed for DVI2PDF 
526 
527           // may throw BuildFailureException TEX01,
528           // log warning EEX01, EEX02, EEX03, WEX04, WEX05
529           //target.processSource(this, desc, timestampOpt);
530 
531           // perform the proper processing 
532           target.processSource(this, desc);
533 
534           // copy the resulting artifacts to the target folder 
535           FileFilter fileFilter = TexFileUtils.getFileFilter(texFile,
536               target.getPatternOutputFiles(this.settings), false);
537           // may throw BuildFailureException
538           // TFU03, TFU04, TFU05, TFU06
539           // may log warning WFU01 Cannot read directory
540           Set<File> targetFiles = this.fileUtils
541               .copyOutputToTargetFolder(texFile, fileFilter, targetDir);
542 
543           // Here, we assume that targets viable for verification and comparison
544           // have a single file as an artifact
545           if (targetFiles.size() != 1) {
546             continue;
547           }
548 
549           File pdfFileAct = targetFiles.iterator().next();
550           this.log.debug(String.format("act file %s", pdfFileAct));
551           if (!pdfFileAct.exists()) {
552             // Here, the logging is done already above
553             continue;
554           }
555 
556           // // but this shall be clear also above before trying to copy to target folder
557           // boolean coincideChecked = runDiffPdf(pdfFileCmpOpt.get(), pdfFileAct);
558           // null: not checked, else the result of the check as a boolean. 
559           Boolean coincideChecked = diffByNeedAndReturnEqual(doTryDiff, pdfFileAct, pdfFileCmpOpt);
560 
561           // Of course, verification can be done only if supported by the target. 
562           // If so, for !this.settings.getVerifyByCmp(), validation must be run. 
563           // else, it must only be done if no check, i.e. coincideChecked==null:
564           // or comparison did not yiels confirmation that the files coincide 
565           if (doTryVeri 
566             && (!this.settings.getVerifyByCmp() || coincideChecked == null || !coincideChecked)) {
567             runValidatePdf(desc);
568           }
569 
570           // for coincideChecked == null diffByNeedAndReturnEqual emitted an according warnind already
571           if (coincideChecked != null && !coincideChecked) {
572             throw new BuildFailureException(
573                 "TLP01: Artifact '" + pdfFileAct.getName() +
574                     "' from '" + texFile + "' could not be savely reproduced. ");
575           }
576         } // target
577       } // texFile
578     } finally {
579       if (this.settings.isCleanUp()) {
580         // may log warning WFU01, EFU05
581         this.fileUtils.cleanUp(node, texProcDir, this.settings.getPrefixPytexOutFolder());
582       }
583       this.log.debug(this.settings.isCleanUp() ? ("cleanup: " + texProcDir)
584           : "No cleanup");
585       this.latex2PdfCmdMagic = Optional.empty();// superfluous
586     }
587   }
588 
589   /**
590    * Runs {@link #runDiffPdf(File, File)} by need 
591    * and returns whether the comparison was not executed, passed for failed. 
592    * Note that currently, a diff is performed for PDF files only. 
593    * 
594    * @param doTryDiff
595    *    whether a diff check was resquested, i.e. possible by target (PDF only) 
596    *    and requested globally or particularly for the actual document. 
597    * @param pdfFileAct
598    *    The actual PDF file 
599    * @param pdfFileCmpOpt
600    *    An Optional carrying another PDF file for comparison, if available. 
601    * @return 
602    *    <code>null</code> if it was not tried to compare. 
603    *    This is if <code>doTryDiff</code> is false 
604    *    or if <code>pdfFileCmpOpt</code> contains not original PDF file for comparison. 
605    *    Else it returns whether the trial succeeded and showed equality. 
606    * @throws BuildFailureException
607    *      TEX01 if invocation of the diff command failed. 
608    */
609   private Boolean diffByNeedAndReturnEqual(boolean doTryDiff,
610       File pdfFileAct,
611       Optional<File> pdfFileCmpOpt)
612       throws BuildFailureException {
613     if (!doTryDiff) {
614       this.log.debug("No artifact diff specified.");
615       return null;
616     }
617 
618     File pdfFileCmp = pdfFileCmpOpt.get();
619     if (!pdfFileCmp.exists()) {
620       // TBD: adapt identifier of warning
621       // THis shall occur only if a newly created or changed file shall be
622       // reproducible
623       // in the message: 'Modification of reproducible file?' - add artifact as
624       // original file '
625       // TBD: pdfFileCmp is absolute but shall be relative to project base directory
626       this.log.warn("TLP02: Add file '" + pdfFileCmp
627           + "' to compare with artifact '" + pdfFileAct + "'! ");
628       return null;
629     }
630     this.log.debug("Prepare verification by diffing: ");
631 
632     // but this shall be clear also above before trying to copy to target folder
633     // may throw BuildFailureException 
634     boolean coincideChecked = runDiffPdf(pdfFileCmpOpt.get(), pdfFileAct);
635     if (coincideChecked) {
636       this.log.info("Checked result: coincides with expected artifact. ");
637     }
638     return coincideChecked;
639   }
640 
641   // TBD: rework documentation 
642   /**
643    * Returns whether the pdf file under construction 
644    * is going to be checked whether an original is reproduced. 
645    * If the magic comment <code>% !LMP chkDiff...</code> is set, 
646    * it takes precedence over {@link Settings#isChkDiff()} 
647    * which determines whether checks are performed 
648    * only if there is no magic comment. 
649    * The magic comment has two variants: 
650    * besides the one with boolean values, 
651    * also one without value which defaults to boolean true value. 
652    * <p>
653    * This applies to all kinds of compilation, 
654    * except for latexmk 
655    * 
656    * @param desc
657    *   description of a TEX file. 
658    * @return
659    *   whether the PDF file under construction  
660    *   is checked to reproduced the original  
661    *   This is determined via magic comment or, 
662    *   if not present, via settings. 
663   * @see #buildLatexmkArguments(Settings, LatexMainDesc)
664    */
665   private boolean isChkDiff(LatexMainDesc desc) {
666     boolean chkDiffSetting = this.settings.isChkDiff();
667     if (!desc.groupMatches(LatexMainParameterNames.chkDiffMagic)) {
668       // Here, result is according to setting 
669       return chkDiffSetting;
670     }
671     // Here, the magic comment overrides the settings 
672     Optional<String> chkDiffValue = 
673     desc.groupMatch(LatexMainParameterNames.chkDiffMagicVal);
674     boolean chkDiff = chkDiffValue.isEmpty() 
675     ? true 
676     : Boolean.parseBoolean(chkDiffValue.get());
677     if (chkDiff != chkDiffSetting) {
678       this.log.info("Magic comment 'chkDiff=" + chkDiff + "' overrides setting.");
679     }
680     return chkDiff;
681   }
682 
683   /**
684    * Represents the values of tagging in DocumentMetadata. 
685    */
686   static enum TaggingState {
687     /**
688      * Tagging is active. 
689      */
690     on,
691     /**
692      * Tagging is not active. 
693      */
694     off,
695     /**
696      * Tagging commands are active emitting warnings by need, 
697      * but finally the tagging structure is not written to the final output. 
698      */
699     draft;
700   } // enum TaggingState 
701 
702   static enum StdType {
703     ua, a, x;
704   } // enum StdType 
705 
706   /**
707    * Represents the settings in DocumentMetadata. 
708    * This means that all data is a request not reality. 
709    * For example, standards are only claimed, not validated. 
710    * Some aspects, as compression should be quite reliable, 
711    * whereas for example the pdfversion can be manipulated. 
712    */
713   static class MetaData {
714 
715     // TBD: value must admit also nested braces
716     private static final Pattern PATTERN_METADATA = Pattern
717         .compile("(\\s*,\\s*)?(?<key>[-a-z]+)(?:\\s*=\\s*(?<value>[^{}, ]+|\\{[^{}]*\\}))?");
718 
719     private static final String KEY_PDFVERSION = "pdfversion";
720     private static final String KEY_UNCOMP = "uncompress";
721     private static final String KEY_STD = "pdfstandard";
722     private static final String KEY_XMP = "xmp";
723     private static final String KEY_TAGGING = "tagging";
724 
725 
726     final static MetaData NO_METADATA = new MetaData(KEY_XMP + "=" +false);
727     /**
728      * The version as an optional which is empty if the version is not given explicitly. 
729      */
730     final Optional<String> pdfversion;
731 
732     final boolean isCompressed;
733 
734     //EnumSet<PdfStandard> standards;
735 
736     final boolean withXMP;
737     final TaggingState tagging;
738 
739 
740 
741     private final Map<String, String> key2value;
742 
743     private static String unwrap(String str) {
744       str = str.trim();
745       return  (str.startsWith("{") && str.endsWith("}"))
746         ? str.substring(1,str.length()-1)
747         : str;
748     }
749 
750     MetaData(String docMetadataString) {
751       this.key2value = new TreeMap<String, String>();
752       Matcher matcher = PATTERN_METADATA.matcher(docMetadataString);
753       String key, value;
754       while (matcher.find()) {
755         key = matcher.group("key");
756         value = matcher.group("value");
757         System.out.println("key: |" + key + "| value: |" + value + "|");
758         switch (key) {
759           case KEY_STD:
760             String org = this.key2value.get(KEY_STD);
761             value = unwrap(value).toLowerCase();
762             if (org == null) {
763               this.key2value.put(KEY_STD, value);
764 
765             } else {
766               this.key2value.put(KEY_STD, org + "," + value);
767             }
768             continue;
769           case KEY_UNCOMP:
770             assert value == null;
771             // continue; fallthrough
772           default:
773             this.key2value.put(key, value);
774         } // switch
775       } // while 
776 
777       // no action for 'backend'
778       // pdfversion may be given explicitly or not 
779       this.pdfversion = this.key2value.containsKey(KEY_PDFVERSION)
780       ? Optional.of(this.key2value.get(KEY_PDFVERSION))
781       : Optional.empty();
782       // uncompression TBD: there is a second way to reach this. 
783       this.isCompressed = !this.key2value.containsKey(KEY_UNCOMP);
784 
785 
786       // xmp 
787       this.withXMP = this.key2value.containsKey(KEY_XMP)
788           ? Boolean.parseBoolean(this.key2value.get(KEY_XMP))
789           : true; // the default value.
790 
791       // tagging 
792       this.tagging = this.key2value.containsKey(KEY_TAGGING)
793         ? TaggingState.valueOf(this.key2value.get(KEY_TAGGING))
794         : TaggingState.off;
795  
796       // value may be well null but only for uncompress,
797       // currently, this is not checked.
798       // for pdfstandard, the key may repeat.
799       // currently this is just overwritten.
800 
801     } // MetaData 
802 
803     // Set<String> getClaimedStandard(String xaua) {
804 
805     // }
806 
807     // boolean claimsSomeStandard() {
808     //   return this.key2value.containsKey(KEY_STD);
809     // }
810 
811     boolean isSpecMetadata() {
812       return this != NO_METADATA;
813     }
814 
815     Optional<String> claimedStandard(StdType stdType) {
816       String stdsStr = this.key2value.get(KEY_STD);
817       //System.out.println("stdsStr: "+stdsStr);
818       if (stdsStr == null) {
819         return Optional.empty();
820       }
821       Matcher matcher = Pattern
822         .compile(String.format("^(.*,)?(?<std>%s-\\d+[a-z]*)", stdType.toString()))
823         .matcher(stdsStr);
824       return matcher.find()
825         ? Optional.of(matcher.group("std"))
826         : Optional.empty();
827       //boolean found = matcher.find();
828       // if (found) {
829       //   return Optional.of(matcher.group("std"));
830       // } else {
831       //   return Optional.empty();
832       // }
833     }
834 
835   } // class MetaData 
836 
837 
838 
839   private MetaData docMetadata(LatexMainDesc desc) {
840     //boolean chkDiffSetting = this.settings.isChkDiff();
841     if (!desc.groupMatches(LatexMainParameterNames.docMetadata)) {
842       // Here, no metadata at all, so no claim 
843       //this.log.info("No DocumentMetadata.");
844       return MetaData.NO_METADATA;
845     }
846     // Here, metadata are given 
847     // TBD: make this more precise later. 
848     Optional<String> docMetadataValue = 
849     desc.groupMatch(LatexMainParameterNames.docMetadata);
850     String docMetadataString = docMetadataValue.get();
851 
852 System.out.println("docMetadata: |"+docMetadataString+"|");
853      return new MetaData(docMetadataString);
854   }
855 
856   /**
857    * Returns whether the tex file under consideration 
858    * is going to be compiled using {@link Converter#Latexmk}. 
859    * If the magic comment <code>% !LMP latexmk..</code> is set, 
860    * it takes precedence over {@link Settings#getLatexmkUsage()} 
861    * which determines among other whether latexmk is used. 
862    * The magic comment has two variants: 
863    * besides the one with boolean values, 
864    * also one without value which defaults to boolean true value. 
865    * <p>
866    * This applies to all kinds of compilation, 
867    * except for latexmk 
868    * 
869    * @param desc
870    *   description of a TEX file. 
871    * @return
872    *   whether the tex file under consideration 
873    *   is going to be compiled using {@link Converter#Latexmk}. 
874    *   This is determined via magic comment or, 
875    *   if not present, via settings. 
876    * @see #processLatex2pdf(LatexMainDesc)
877    */
878   // TBD: generalize together with isChkDiff
879   private boolean isCompileWithLatexmk(LatexMainDesc desc) {
880     boolean runLatexmkSetting = this.settings.getLatexmkUsage().runLatexmk();
881     if (!desc.groupMatches(LatexMainParameterNames.latexmkMagic)) {
882       return runLatexmkSetting;
883     }
884     // Here, the magic comment overrides the settings 
885     Optional<String> runLatexmkValue = 
886     desc.groupMatch(LatexMainParameterNames.latexmkMagicVal);
887     boolean runLatexmk = (runLatexmkValue.isEmpty())
888     ? true
889     : Boolean.parseBoolean(runLatexmkValue.get());
890     if (runLatexmk != runLatexmkSetting) {
891       this.log.info("Magic comment 'latexmk="+runLatexmk+"' overrides settings. ");
892     }
893     return runLatexmk;
894   }
895 
896   // TBD: rework 
897   /*
898    * Defines check goal of the maven plugin in { @link ChkMojo}.
899    * This includes also creation of graphic files.
900    * <p>
901    * TBD: logging
902    * <ul>
903    *    <li>WPP05: Included tex files which are no latex main files 
904    *    <li>WPP06: Included tex files which are no latex main files 
905    *    <li>WPP07: inluded/excluded files not identified by their names.
906    * </ul>
907    * 
908    * @throws BuildFailureException
909    *    <ul>
910    *    <li>
911    *    TSS02 if the tex source processing directory
912    *    does either not existor is not a directory.
913    *    <li>
914    *    TEX01 invoking FIXME
915    *    </ul>
916    */
917   // used in ChkMojo.execute() only
918   // FIXME: maybe sufficient not to create graphics, if no \input.
919   // Also, maybe good not to remove log file
920   // maybe good to make suffix configurable rather than hardcoded.
921   // public void checkAll() throws BuildFailureException {
922 
923   //     this.paramAdapt.initialize();
924   //     this.log.debug("Settings: " + this.settings.toString());
925 
926   //     // may throw BuildFailureException TSS02
927   //     File texProcDir = this.settings.getTexSrcProcDirectoryFile();
928   //     assert texProcDir.exists() && texProcDir.isDirectory()
929   //             : "Expected existing tex processing folder " + texProcDir;
930 
931   //     // constructor DirNode may log warning WFU01 Cannot read directory
932   //     DirNode node = new DirNode(texProcDir, this.fileUtils);
933 
934   //     try {
935   //         // TBD: eliminate processing graphics. 
936   //         // may throw BuildFailureException TEX01,
937   //         // log warning WFU03, WPP02, WPP03,
938   //         // EEX01, EEX02, EEX03, WEX04, WEX05, EFU07, EFU08, EFU09
939   //         Collection<File> latexMainFiles = this.preProc
940   //                 .processGraphicsSelectMain(texProcDir, node);
941 
942   //         for (File latexMain : latexMainFiles) {
943   //             processCheck(getLatexMainDesc(latexMain));
944   //         }
945   //     } finally {
946   //         // FIXME: also removes the clg-files
947   //         if (this.settings.isCleanUp()) {
948   //             // may log warning WFU01, EFU05
949   //             this.fileUtils.cleanUp(node, texProcDir);
950   //         }
951   //     }
952   // }
953 
954   // TBD: rework 
955   /**
956    * Defines graphics goal of the maven plugin in { @link GraphicsMojo}.
957    * <p>
958    * Logging:
959    * <ul>
960    * <li>WFU01: Cannot read directory
961    * <li>WFU03: cannot close file
962    * <li>EFU07, EFU08, EFU09: if filtering a file fails.
963    * <li>WPP02: tex file may be latex main file
964    * <li>WPP03: Skipped processing of files with suffixes ...
965    * <li>EEX01, EEX02, EEX03, WEX04, WEX05:
966    * if running graphic processors failed.
967    * <li>WPP05: Included tex files which are no latex main files 
968      * <li>WPP06: Included tex files which are no latex main files 
969      * <li>WPP07: inluded/excluded files not identified by their names.
970    * </ul>
971    *
972    * @throws BuildFailureException
973    *    <ul>
974    *    <li>
975    *    TSS02 if the tex source processing directory
976    *    does either not exist or is not a directory.
977    *    <li>
978    *    TEX01 invoking FIXME
979    *    </ul>
980    */
981   // used in GraphicsMojo.execute() only
982   public void processGraphics() throws BuildFailureException {
983     // may throw BuildFailureException TSS02
984     File texProcDir = this.settings.getTexSrcProcDirectoryFile();
985     assert texProcDir.exists()
986         && texProcDir.isDirectory() : "Expected existing tex processing folder "
987             + texProcDir;
988 
989     // constructor DirNode may log warning WFU01 Cannot read directory
990     DirNode node = new DirNode(texProcDir, this.fileUtils);
991     // TBD: eliminate here selecting the main files. 
992     // may throw BuildFailureException TEX01,
993     // log warning WFU03, WPP02, WPP03,
994     // EEX01, EEX02, EEX03, WEX04, WEX05, EFU07, EFU08, EFU09
995     this.preProc.processGraphicsSelectMain(texProcDir, node, true);
996   }
997 
998   // TBD: rework 
999   /**
1000    * Defines clearing ant-task defined in { @link LatexClrTask}
1001    * and the according goal in { @link ClearMojo} of the maven plugin.
1002    * Consists in clearing created graphic files
1003    * and created files derived from latex main file.
1004    * <p>
1005    * The parameters this method depends on are (currently):
1006    * <ul>
1007    * <li>
1008    * {@link Settings#getTexSrcProcDirectoryFile()}
1009    * <li>
1010    * {@link Settings#getPatternLatexMainFile()}
1011    * <li>
1012    * {@link Settings#getPatternCreatedFromLatexMain()}
1013    * </ul>
1014    * <p>
1015    * Logging:
1016    * <ul>
1017    * <li>WPP02: tex file may be latex main file
1018    * <li>WFU01: Cannot read directory...
1019    * <li>WFU03: cannot close tex file
1020    * <li>EFU05: Failed to delete file
1021    * <li> WFU10, WFU11: if a config file is not written by this software 
1022    * or it is not clear or the reader cannot close. 
1023    * </ul>
1024    *
1025    * @throws BuildFailureException
1026    *   <ul>
1027    *   <li> TSS02 if the tex source processing directory
1028    *    does either not exist or is not a directory.</li>
1029    *   <li> TLP04 if a config file cannot be decided whether to be cleared 
1030    *    or could not be cleared. </li>
1031    *   </ul>
1032    *   TBD: synchronize: Really a throwable? 
1033    */
1034   public void clearAll() throws BuildFailureException {
1035     this.paramAdapt.initialize();
1036     this.log.debug("Settings: " + this.settings.toString());
1037 
1038     // may throw BuildFailureException TSS02
1039     File texProcDir = this.settings.getTexSrcProcDirectoryFile();
1040     assert texProcDir.exists()
1041         && texProcDir.isDirectory() : "Expected existing tex processing folder "
1042             + texProcDir;
1043 
1044     // constructor DirNode may log warning WFU01 Cannot read directory
1045     // clearCreated may log warnings WPP02, WFU01, WFU03, EFU05
1046     this.preProc.clearCreated(texProcDir);
1047     // may log WFU10, WFU11, EFU05 
1048     clearInjFiles();
1049   }
1050 
1051   // FIXME: use the -recorder option to resolve dependencies.
1052   // With that option, a file xxx.fls is generated with form
1053   // PWD
1054   // /home/ernst/OpenSource/maven-latex-plugin/maven-latex-plugin.git/trunk/maven-latex-plugin/src/site/tex
1055   // INPUT /usr/local/texlive/2014/texmf.cnf
1056   // INPUT /usr/local/texlive/2014/texmf-dist/web2c/texmf.cnf
1057   // INPUT /usr/local/texlive/2014/texmf-var/web2c/pdftex/pdflatex.fmt
1058   // INPUT manualLatexMavenPlugin.tex
1059   // OUTPUT manualLatexMavenPlugin.log
1060   // INPUT /usr/local/texlive/2014/texmf-dist/tex/latex/base/article.cls
1061   // INPUT /usr/local/texlive/2014/texmf-dist/tex/latex/base/article.cls
1062   // INPUT /usr/local/texlive/2014/texmf-dist/tex/latex/base/size12.clo
1063   //
1064   // The first line starts has the form 'PWD <working directory>'
1065   // The other lines have the form '(INPUT|OUTPUT) <file>'
1066   // We distinguishe those in the installation,
1067   // here '/usr/local/texlive/2014/...' which do not change ever
1068   // and others.
1069   // In this example, the others are (unified and sorted):
1070 
1071   // INPUT manualLatexMavenPlugin.tex
1072 
1073   // OUTPUT manualLatexMavenPlugin.log
1074   // INPUT manualLatexMavenPlugin.aux
1075   // OUTPUT manualLatexMavenPlugin.aux
1076   // INPUT manualLatexMavenPlugin.out
1077   // OUTPUT manualLatexMavenPlugin.out
1078   // INPUT manualLatexMavenPlugin.toc
1079   // OUTPUT manualLatexMavenPlugin.toc
1080   // INPUT manualLatexMavenPlugin.lof
1081   // OUTPUT manualLatexMavenPlugin.lof
1082   // INPUT manualLatexMavenPlugin.lot
1083   // OUTPUT manualLatexMavenPlugin.lot
1084 
1085   // OUTPUT manualLatexMavenPlugin.idx
1086   // INPUT manualLatexMavenPlugin.ind
1087 
1088   // OUTPUT manualLatexMavenPlugin.pdf
1089 
1090   // INPUT 1fig2dev.ptx
1091   // INPUT 1fig2dev.pdf
1092   // INPUT 2plt2pdf.ptx
1093   // INPUT 2plt2pdf.pdf
1094   // INPUT 4tex2pdf.ptx
1095   // INPUT 5aux2bbl.ptx
1096   // INPUT 5aux2bbl.pdf
1097   // INPUT 6idx2ind.ptx
1098   // INPUT 6idx2ind.pdf
1099   // INPUT 7tex2xml.ptx
1100   // INPUT 7tex2xml.pdf
1101   // what is missing is all to do with bibliography, i.e. the bib-file.
1102 
1103   // FIXME: determine whether to use latexmk makes sense
1104 
1105 
1106   public String getLatex2pdfCommand() throws BuildFailureException {
1107     return this.latex2PdfCmdMagic
1108       .orElse(this.settings.getCommand(ConverterCategory.LaTeX));
1109   }
1110 
1111   private String getDvi2pdfCommand() throws BuildFailureException {
1112     return this.settings.getCommand(ConverterCategory.Dvi2Pdf);
1113   }
1114 
1115   /**
1116    * Runs LaTeX on on the latex main file <code>texFile</code>
1117    * described by <code>desc</code> once,
1118    * runs BibTeX, MakeIndex and MakeGlossaries by need
1119    * and returns whether a second LaTeX run is required.
1120    * The latter also holds, if a table of contents, a list of figures
1121    * or a list of tables is specified.
1122    * The output format of the LaTeX run is given by <code>dev</code>,
1123    * to be more precise by {@link LatexDev#getLatexOutputFormat()}.
1124    * <p>
1125    * A warning is logged if the LaTeX, a BibTeX run a MakeIndex
1126    * or a MakeGlossaries run fails
1127    * or if a BibTeX run or a MakeIndex or a MakeGlossary run issues a warning
1128    * in the according methods
1129    * {@link #runLatex2dev(LatexMainDesc, LatexDev)} and 
1130    * {@link Auxiliary#process(LatexMainDesc, LatexProcessor)}. 
1131    * <p>
1132    * Logging:
1133    * <ul>
1134    * <li>EAP01: Running <code>command</code> failed. For details...
1135    * <li>EAP02: Running <code>command</code> failed. No log file
1136    * <li>WAP04: if <code>logFile</code> is not readable.
1137    * <li>WLP04: Cannot read idx file; skip creation of index
1138    * <li>WLP05: Use package 'splitidx' without option 'split'
1139    * <li>WLP02: Cannot read blg file: BibTeX run required?
1140    * <li>WFU03: cannot close log file
1141    * <li>EEX01, EEX02, EEX03, WEX04, WEX05:
1142    * if one of the commands mentioned in the throws-tag fails
1143    * </ul>
1144    *
1145    * @param desc
1146    *    the description of a latex main file <code>texFile</code>
1147    *    to be processed.
1148    * @param dev
1149    *    the device describing the output format which is either pdf or
1150    *    dvi.
1151    *    See {@link LatexDev#getLatexOutputFormat()}.
1152    * @return
1153    *    the number of LaTeX runs required after invocation of this method 
1154    *    because bibtex, makeindex or makeglossaries had been run
1155    *    or to update a table of contents or a list figures or tables.
1156    *    <ul>
1157    *    <li>
1158    *    If neither of these are present, no rerun is required.
1159    *    <li>
1160    *    If a bibliography, an index and a glossary is included
1161    *    and a table of contents,
1162    *    we assume that these are in the table of contents.
1163    *    Thus two reruns are required:
1164    *    one to include the bibliography or that like
1165    *    and the second one to make it appear in the table of contents.
1166    *    <li>
1167    *    In all other cases, a single rerun suffices
1168    *    </ul>
1169    * @throws BuildFailureException
1170    *    TEX01 if invocation one of the following commands fails: the
1171    *    <ul>
1172    *    <li>latex2pdf command from
1173    *    {@link #getLatex2pdfCommand()}
1174    *    <li>BibTeX command from
1175    *    {@link Settings#getBibtexCommand()}
1176    *    <li>makeindex command from
1177    *    {@link Settings#getMakeIndexCommand()}
1178    *    <li>makeglossaries command
1179    *    from {@link Settings#getMakeGlossariesCommand()}
1180    *    </ul>
1181    * @see #processLatex2devCore(LatexMainDesc, LatexDev)
1182    * @see #processLatex2html(LatexMainDesc)
1183    * @see #processLatex2odt(LatexMainDesc)
1184    * @see #processLatex2docx(LatexMainDesc)
1185    */
1186   private int preProcessLatex2dev(LatexMainDesc desc, LatexDev dev)
1187       throws BuildFailureException {
1188 
1189     // initial latex run
1190     // may throw BuildFailureException TEX01
1191     // may log warnings EEX01, EEX02, EEX03, WEX04, WEX05,
1192     // EAP01, EAP02, WAP04, WFU03
1193     runLatex2dev(desc, dev);
1194     //File texFile = desc.texFile;
1195 
1196     // need not be a file with ending aux, but is source for an auxiliary process 
1197     File auxFile;
1198     boolean posterioryEntryInToc = false;
1199     int minNumRunsAfter = 0;
1200     for (Auxiliary aux : Auxiliary.values()) {
1201       auxFile = desc.withSuffix(aux.extension());
1202       assert !auxFile.isDirectory();
1203       if (!aux.doesFitAuxiliary(auxFile)) {
1204         continue;
1205       }
1206       // TBC: can additional keys occur later? 
1207       desc.aux2fileId.put(aux, getIdent(aux, auxFile));
1208 
1209       posterioryEntryInToc = aux.mayBeEntryInToc();
1210       this.log.debug("Running auxiliary " + aux + ". ");
1211       aux.process(desc, this);
1212       minNumRunsAfter = Math.max(minNumRunsAfter,aux.numRunsAfter());
1213     } // for 
1214     assert minNumRunsAfter >= 0 && minNumRunsAfter <= 2;
1215 
1216     // // create bibliography, index and glossary by need
1217     // // may throw BuildFailureException TEX01
1218     // // may log warnings EEX01, EEX02, EEX03, WEX04, WEX05,
1219     // // EAP01, EAP02, WAP03, WAP04, WLP02, WFU03
1220     // boolean hasBib = runBibtex(desc);
1221     // // may both throw BuildFailureException, both TEX01
1222     // // may both log warnings EEX01, EEX02, EEX03, WEX04, WEX05,
1223     // // EAP01, EAP02, WLP04, WLP05, WAP03, WAP04, WFU03
1224     // boolean hasIdxGls = makeIndex(desc) | runMakeGlossary(desc);
1225     // boolean hasPyCode = runPythontex(desc);
1226 
1227 
1228     boolean hasToc = desc.withSuffix(SUFFIX_TOC).exists();
1229     if (hasToc && posterioryEntryInToc) {
1230       minNumRunsAfter = Math.max(minNumRunsAfter,2);
1231     }
1232 
1233     if (hasToc
1234     || desc.withSuffix(SUFFIX_LOF).exists()
1235     || desc.withSuffix(SUFFIX_LOT).exists()
1236     || desc.withSuffix(SUFFIX_LOL).exists()) {
1237       minNumRunsAfter = Math.max(minNumRunsAfter,1);
1238     }
1239 
1240     return minNumRunsAfter;
1241   }
1242 
1243   private static final FileId EMPTY_FILE_ID = new FileId().finalizFileId();
1244 
1245   /**
1246    * Wraps {@link Auxiliary#getIdent(File)} 
1247    * catching the IOException 
1248    * and transforming it into WLP10 
1249    * indicating that rerun check is degraded. 
1250    * 
1251    * @param aux
1252    *    the auxiliary which determines the aspects of 
1253    * @param file
1254    *    the file for which an identifier is requested. 
1255    * @return
1256    *    the identifier for the file <code>file</code> 
1257    *    tied to the auxiliary <code>aux</code> 
1258    *    according to {@link Auxiliary#getIdent(File)}, 
1259    *    except if the latter throws an exception. 
1260    *    In that case, {@link #EMPTY_FILE_ID} is returned. 
1261    */
1262   private FileId getIdent(Auxiliary aux, File file) {
1263     try {
1264       return aux.getIdent(file).finalizFileId();
1265     } catch(IOException ioe) {
1266       this.log.warn("WLP10: Degraded identifier for '" +file + 
1267       "'; augmented risk not to rerun although necessary. ");
1268       return EMPTY_FILE_ID;
1269     }
1270   }
1271 
1272   /**
1273    * Runs LaTeX on the latex main file <code>texFile</code>
1274    * described by <code>desc</code> once,
1275    * runs BibTeX, MakeIndex and MakeGlossaries by need
1276    * according to {@link #preProcessLatex2dev(LatexMainDesc,LatexDev)}
1277    * and reruns MakeIndex, MakeGlossaries and LaTeX
1278    * as often as needed to get all links satisfied
1279    * or as threshold {@link Settings#maxNumReRunsLatex} specifies.
1280    * <p>
1281    * Note that still no logging of warnings from a latex run is done.
1282    * This is done
1283    * in {@link #processLatex2dev(LatexMainDesc, LatexDev)}.
1284    * The exclusion of logging of warnings is indicated by the name part
1285    * 'Core'.
1286    * Processing without logging of warnings
1287    * is required by {@link #processLatex2txt(LatexMainDesc)}.
1288    * <p>
1289    * The output format of the LaTeX run is given by <code>dev</code>,
1290    * to be more precise by {@link LatexDev#getLatexOutputFormat()}.
1291    * <p>
1292    * Logging:
1293    * WLP01: if another rerun is required but the maximum number of runs
1294    * {@link Settings#getMaxNumReRunsLatex()} is reached.
1295    * Further logging is inherited by invoked methods:
1296    * <ul>
1297    * <li>WLP04: Cannot read idx file; skip creation of index
1298    * <li>WLP05: Use package 'splitidx' without option 'split'
1299    * <li>EAP01: Running <code>command</code> failed. For details...
1300    * <li>EAP02: Running <code>command</code> failed. No log file
1301    * <li>WAP04: if <code>logFile</code> is not readable.
1302    * <li>WLP02: Cannot read log file: run BibTeX/
1303    * (re)run MakeIndex/LaTeX required?
1304    * <li>WFU03: cannot close
1305    * <li>EEX01, EEX02, EEX03, WEX04, WEX05: as for
1306    * {@link #preProcessLatex2dev(LatexMainDesc, LatexDev)}
1307    * maybe caused by subsequent runs.
1308    * </ul>
1309    *
1310    * @param desc
1311    *    the description of a latex main file <code>texFile</code>
1312    *    to be processed.
1313    * @param dev
1314    *    the device describing the output format 
1315    *    which is either pdf or dvi.
1316    *    See {@link LatexDev#getLatexOutputFormat()}.
1317    * @throws BuildFailureException
1318    *    TEX01 as for
1319    *    {@link #preProcessLatex2dev(LatexMainDesc, LatexDev)}
1320    *    maybe caused by subsequent runs.
1321    * @see #processLatex2dvi(LatexMainDesc)
1322    * @see #processLatex2txt(LatexMainDesc)
1323    */
1324   private void processLatex2devCore(LatexMainDesc desc, LatexDev dev)
1325       throws BuildFailureException {
1326 
1327     // may throw BuildFailureException TEX01,
1328     // log warning WLP04, WLP05, EAP01, EAP02, WAP04, WLP02, WFU03, 
1329     // EEX01, EEX02, EEX03, WEX04, WEX05
1330     int numLatexReRuns = preProcessLatex2dev(desc, dev);
1331 
1332     String latexCmd = getLatex2pdfCommand();
1333 
1334     assert numLatexReRuns == 0 || numLatexReRuns == 1 || numLatexReRuns == 2;
1335     if (numLatexReRuns > 0) {
1336       // rerun LaTeX without makeindex and makeglossaries
1337       this.log.debug("Rerun " + latexCmd + " to update table of contents, ... "
1338           + "bibliography, index, or that like. ");
1339       // may throw BuildFailureException TEX01
1340       // may log warnings EEX01, EEX02, EEX03, WEX04, WEX05,
1341       // EAP01, EAP02, WAP04, WFU03
1342       runLatex2dev(desc, dev);
1343       numLatexReRuns--;
1344     }
1345     assert numLatexReRuns == 0 || numLatexReRuns == 1;
1346 
1347     boolean needLatexReRun = (numLatexReRuns == 1) 
1348     || needRun(true, latexCmd,
1349         desc.logFile, this.settings.getPatternReRunLatex());
1350 
1351     int maxNumReruns = this.settings.getMaxNumReRunsLatex();
1352     for (int num = 0; maxNumReruns == -1 || num < maxNumReruns; num++) {
1353       FileId fileId;
1354       for (Auxiliary aux : desc.aux2fileId.keySet()) {
1355         fileId = getIdent(aux, desc.withSuffix(aux.extension()));
1356 
1357         if (desc.aux2fileId.get(aux).equals(fileId)) {
1358           continue;
1359         }
1360         this.log.debug("Rerunning auxiliary " + aux + ". ");
1361         desc.aux2fileId.put(aux, fileId);
1362         aux.process(desc, this);
1363         needLatexReRun = true;
1364       }
1365 
1366       if (!needLatexReRun) {
1367         return;
1368       }
1369       this.log.debug("Latex must be rerun. ");
1370 
1371 
1372       // may throw BuildFailureException TEX01
1373       // may log warnings EEX01, EEX02, EEX03, WEX04, WEX05,
1374       // EAP01, EAP02, WAP04, WFU03
1375       runLatex2dev(desc, dev);
1376       needLatexReRun = needRun(true, latexCmd, desc.logFile,
1377           this.settings.getPatternReRunLatex());
1378     } // for 
1379     this.log.warn("WLP01: LaTeX requires rerun but maximum number "
1380         + maxNumReruns + " reached. ");
1381   }
1382 
1383   /**
1384    * Returns whether a(n other) run (see <code>another</code>)
1385    * of the application <code>application</code> is necessary
1386    * based on a pattern <code>pattern</code>
1387    * matching in the log file <code>logFile</code>.
1388    * Note that only <code>logFile</code> and <code>pattern</code>
1389    * are required unless a warning needs to be issued.
1390    * <p>
1391    * Logging:
1392    * <ul>
1393    * <li>WLP02: Cannot read log file: (re)run required?
1394    * <li>WFU03: cannot close log file
1395    * </ul>
1396    *
1397    * @param another
1398    *    whether it is requested whether another run 
1399    *    (a 'rerun') is required.
1400    *    If false, just a run is required
1401    * @param cmdStr
1402    *    Determines the command string of the application to be rerun.
1403    *    This may be of category {@link ConverterCategory#LaTeX},
1404    *    {@link ConverterCategory#MakeIndex}
1405    *    but also {@link ConverterCategory#BibTeX}.
1406    * @param logAuxFile
1407    *    the log file or aux file which determines
1408    *    whether to rerun <code>cmdStr</code>.
1409    * @param pattern
1410    *    the pattern in the <code>logFile</code>
1411    *    which determines whether to rerun <code>cmdStr</code>.
1412    * @return
1413    *    whether <code>cmdStr</code> needs to be rerun
1414    *    based on a pattern <code>pattern</code>
1415    *    matching in the log file <code>logFile</code>.
1416    * @see TexFileUtils#matchInFile(File, String)
1417    */
1418   // used in processLatex2devCore only
1419   // TBD: eliminate Converter again and replace by ConverterCategory
1420   // including also the rerun pattern.
1421   boolean needRun(boolean another, String cmdStr, File logAuxFile,
1422       String pattern) {
1423     // may log warning WFU03: cannot close
1424     FileMatch fileMatch = this.fileUtils.getMatchInFile(logAuxFile, pattern);
1425     if (fileMatch.isFileReadable()) {
1426       return fileMatch.doesExprMatch();
1427     }
1428     this.log
1429         .warn("WLP02: Cannot read " 
1430             + TexFileUtils.getSuffix(logAuxFile, false)
1431             + " file '" + logAuxFile.getName() + "'; " + cmdStr
1432             + " may require " + (another ? "re" : "") + "run. ");
1433     return false;
1434   }
1435 
1436   /**
1437    * Runs LaTeX, BibTeX, MakeIndex and MakeGlossaries
1438    * on the latex main file <code>texFile</code>
1439    * described by <code>desc</code>
1440    * repeatedly as described for
1441    * {@link #processLatex2devCore(LatexMainDesc,LatexDev)}
1442    * and issue a warning if the last LaTeX run issued a warning.
1443    * </ul>
1444    * <p>
1445    * Logging:
1446    * <ul>
1447    * <li>WFU03: cannot close
1448    * <li>WAP04: if <code>logFile</code> is not readable.
1449    * <li>WLP01: if another rerun is required
1450    * but the maximum number of runs is reached.
1451    * <li>WLP03: <code>command</code> created bad boxes
1452    * <li>WLP04: <code>command</code> emitted warnings
1453    * <li>WLP04: Cannot read idx file; skip creation of index
1454    * <li>WLP05: Use package 'splitidx' without option 'split'
1455    * <li>EEX01, EEX02, EEX03, WEX04, WEX05:
1456    * {@link #processLatex2devCore(LatexMainDesc, LatexDev)}
1457    * if running an exernal command fails.
1458    * </ul>
1459    *
1460    * @param desc
1461    *    the description of a latex main file <code>texFile</code>
1462    *    to be processed.
1463    * @param dev
1464    *    the device describing the output format which is either pdf or dvi.
1465    *    See {@link LatexDev#getLatexOutputFormat()}.
1466    * @throws BuildFailureException
1467    *    TEX01 as for
1468    *    {@link #processLatex2devCore(LatexMainDesc, LatexDev)}.
1469    * @see #needRun(boolean, String, File, String)
1470    * @see Target#pdf
1471    */
1472   private void processLatex2dev(LatexMainDesc desc, LatexDev dev)
1473       throws BuildFailureException {
1474 
1475     // invocation of latexmk here is suitable if used not only for pdf but also for dvi in future 
1476     // // TBD: improve this in several respect. 
1477     // if (dev == LatexDev.pdf && this.settings.getLatexmkUsage() != LatexmkUsage.NotAtAll) {
1478     //   this.log.info("Running latexmk bypassing direct compilation. ");
1479     //   runLatexmk(desc);
1480     //   return;
1481     // }
1482     // this.log.info("No latexmk because target=" + dev + " and usage=" + this.settings.getLatexmkUsage());
1483 
1484     // may throw BuildFailureException TEX01,
1485     // log warning EAP01, EAP02, WAP04, WLP02, WFU03, WLP04, WLP05,
1486     // EEX01, EEX02, EEX03, WEX04, WEX05
1487     processLatex2devCore(desc, dev);
1488 
1489     // emit warnings (errors are emitted by runLatex2dev and that like.)
1490     // may log warnings WFU03, WAP04, WLP03, WLP04
1491     logWarns(desc.logFile, getLatex2pdfCommand());
1492   }
1493 
1494   void processLatex2dvi(LatexMainDesc desc)
1495       throws BuildFailureException {
1496     this.log.info("Converting into dvi/xdv format. ");
1497     // may throw BuildFailureException TEX01,
1498     // log warning EEX01, EEX02, EEX03, WEX04, WEX05
1499     // WFU03, WAP04, WLP03, WLP04
1500     processLatex2dev(desc, LatexDev.dvips);
1501   }
1502 
1503   void processLatex2pdf(LatexMainDesc desc)
1504       throws BuildFailureException {
1505     this.log.info("Converting into pdf format. ");
1506 
1507     // TBD: improve this in several respect. 
1508     if (isCompileWithLatexmk(desc)) {
1509       // CAUTION: timestamp is not used directly 
1510       runLatexmk(desc);
1511       return;
1512     }
1513 
1514     LatexDev dev = this.settings.getPdfViaDvi();
1515 
1516     // may throw BuildFailureException TEX01,
1517     // log warning EEX01, EEX02, EEX03, WEX04, WEX05, WLP04, WLP05
1518     processLatex2dev(desc, dev);
1519     // FIXME: certain figures are invisible in the intermediate dvi file,
1520     // but converstion to pdf shows that the figures are present.
1521 
1522     if (dev.isViaDvi()) {
1523       // may throw BuildFailureException TEX01,
1524       // may log warning EEX01, EEX02, EEX03, WEX04, WEX05
1525       runDvi2pdf(desc);
1526     }
1527   }
1528 
1529   /**
1530    * Logs errors detected in the according log file:
1531    * The log file is by replacing the ending of <code>texFile</code>
1532    * by <code>log</code>.
1533    * If the log file exists, a <em>warning</em> is logged
1534    * if the error pattern given by {@link Settings#getPatternErrLatex()}
1535    * occurs in the log file.
1536    * If the log file does not exist, an <em>error</em> is logged.
1537    * In both cases, the message logged refers to the <code>command</code>
1538    * which failed.
1539    * <p>
1540    * Logging:
1541    * <ul>
1542    * <li>EAP01: Running <code>command</code> failed. For details...
1543    * <li>EAP02: Running <code>command</code> failed. No log file
1544    * <li>WAP04: if <code>logFile</code> is not readable.
1545    * <li>WFU03: cannot close
1546    * </ul>
1547    */
1548   private void logErrs(File logFile, String command) {
1549     // may log warnings WFU03, EAP01, EAP02, WAP04
1550     logErrs(logFile, command, this.settings.getPatternErrLatex());
1551   }
1552 
1553   /**
1554    * Logs warnings detected in the according log-file <code>logFile</code>:
1555    * Before logging warnings,
1556    * errors are logged via {@link #logErrs(File, String)}.
1557    * So, if the log-file does not exist,
1558    * an error was already shown and so nothing is to be done here.
1559    * If the log-file exists, a <em>warning</em> is logged if
1560    * <ul>
1561    * <li>
1562    * another LaTeX rerun is required
1563    * beyond {@link Settings#maxNumReRunsLatex},
1564    * <li>
1565    * bad boxes occurred and shall be logged
1566    * according to {@link Settings#getDebugBadBoxes()}.
1567    * <li>
1568    * warnings occurred and shall be logged
1569    * according to {@link Settings#getDebugWarnings()}.
1570    * </ul>
1571    * Both criteria are based on pattern recognized in the log file:
1572    * {@link #PATTERN_OUFULL_HVBOX} for bad boxes is fixed,
1573    * whereas {@link Settings#getPatternWarnLatex()} is configurable.
1574    * The message logged refers to the <code>command</code> which failed.
1575    * <p>
1576    * Logging:
1577    * <ul>
1578    * <li>WFU03: cannot close
1579    * <li>WAP04: if <code>logFile</code> is not readable.
1580    * <li>WLP03: <code>command</code> created bad boxes
1581    * <li>WLP04: <code>command</code> emitted warnings
1582    * </ul>
1583    *
1584    * @param logFile
1585    *    the log-file to detect warnings in.
1586    * @param command
1587    *    the command which created <code>logFile</code>
1588    *    and which maybe created warnings.
1589    */
1590   private void logWarns(File logFile, String command) {
1591     if (!logFile.exists()) {
1592       return;
1593     }
1594     // hasErrsWarns may log warnings WFU03 cannot close, WAP04 not readable
1595     if (this.settings.getDebugBadBoxes()
1596         && hasErrsWarns(logFile, PATTERN_OUFULL_HVBOX)) {
1597       this.log.warn("WLP03: Running " + command
1598           + " created bad boxes logged in '" + logFile.getName() + "'. ");
1599     }
1600 
1601     if (this.settings.getDebugWarnings()
1602         && hasErrsWarns(logFile, this.settings.getPatternWarnLatex())) {
1603       // logs warning WAP03: emitted warnings
1604       logWarn(logFile, command);
1605     }
1606   }
1607 
1608   /**
1609    * Runs conversion of <code>texFile</code> to html or xhtml
1610    * after processing latex to set up the references,
1611    * bibliography, index and that like.
1612    * <p>
1613    * Logging: FIXME: incomplete
1614    * <ul>
1615    * <li>EAP01: Running <code>command</code> failed. For details...
1616    * <li>EAP02: Running <code>command</code> failed. No log file
1617    * <li>WAP04: if <code>logFile</code> is not readable.
1618    * <li>WLP02: Cannot read blg file: BibTeX run required?
1619    * <li>WFU03: cannot close log file
1620    * <li>WLP04: Cannot read idx file; skip creation of index
1621    * <li>WLP05: Use package 'splitidx' without option 'split'
1622    * <li>EEX01, EEX02, EEX03, WEX04, WEX05:
1623    * if running an exernal command fails.
1624    * </ul>
1625    *
1626    * @param desc
1627    *    the tex file to be processed.
1628    * @throws BuildFailureException
1629    *    TEX01 as for
1630    *    {@link #preProcessLatex2dev(LatexMainDesc, LatexDev)}
1631    *    but also as for
1632    *    {@link #runLatex2html(LatexProcessor.LatexMainDesc)}.
1633    * @see #preProcessLatex2dev(LatexMainDesc, LatexDev)
1634    * @see #runLatex2html(LatexMainDesc)
1635    * @see Target#html
1636    */
1637   // TBD: check: is it really sensible to do preprocessing? 
1638   // TBD: also inconsistency: preprocessing with lualatex, xelatex, pdflatex
1639   // whereas processing is done with htlatex and with xtlatex... no lualatex involved. 
1640   void processLatex2html(LatexMainDesc desc)
1641       throws BuildFailureException {
1642     this.log.info("Converting into html format. ");
1643     // may throw BuildFailureException TEX01,
1644     // log warning EAP01, EAP02, WLP04, WLP05, WAP04, WLP02, WFU03, 
1645     // EEX01, EEX02, EEX03, WEX04, WEX05
1646     preProcessLatex2dev(desc, LatexDev.devViaDvi(true));
1647     // may throw BuildFailureException TEX01,
1648     // log warning EEX01, EEX02, EEX03, WEX04, WEX05
1649     runLatex2html(desc);
1650   }
1651 
1652   /**
1653    * Runs conversion of <code>texFile</code>
1654    * to odt or other open office formats
1655    * after processing latex to set up the references,
1656    * bibliography, index and that like.
1657    * <p>
1658    * Logging: FIXME: incomplete 
1659    * <ul>
1660    * <li>EAP01: Running <code>command</code> failed. For details...
1661    * <li>EAP02: Running <code>command</code> failed. No log file
1662    * <li>WAP04: if <code>logFile</code> is not readable.
1663    * <li>WLP02: Cannot read blg file: BibTeX run required?
1664    * <li>WFU03: cannot close log file
1665    * <li>WLP04: Cannot read idx file; skip creation of index
1666    * <li>WLP05: Use package 'splitidx' without option 'split'
1667    * <li>EEX01, EEX02, EEX03, WEX04, WEX05:
1668    * if running an exernal command fails.
1669    * </ul>
1670    *
1671    * @param desc
1672    *    the tex file to be processed.
1673    * @throws BuildFailureException
1674    *    TEX01 as for
1675    *    {@link #preProcessLatex2dev(LatexMainDesc, LatexDev)}
1676    *    but also as for
1677    *    {@link #runLatex2odt(LatexProcessor.LatexMainDesc)}.
1678    * @see #preProcessLatex2dev(LatexMainDesc, LatexDev)
1679    * @see #runLatex2odt(LatexMainDesc)
1680    * @see Target#odt
1681    */
1682   void processLatex2odt(LatexMainDesc desc)
1683       throws BuildFailureException {
1684     this.log.info("Converting into odt format. ");
1685     // may throw BuildFailureException TEX01,
1686     // log warning EAP01, EAP02, WAP04, WLP02, WFU03, WLP04, WLP05
1687     // EEX01, EEX02, EEX03, WEX04, WEX05
1688     preProcessLatex2dev(desc, this.settings.getPdfViaDvi());
1689     // may throw BuildFailureException TEX01,
1690     // log warning EEX01, EEX02, EEX03, WEX04, WEX05
1691     runLatex2odt(desc);
1692   }
1693 
1694   /**
1695    * Runs conversion of <code>texFile</code>
1696    * to docx or other MS word formats
1697    * after processing latex to set up the references,
1698    * bibliography, index and that like.
1699    * <p>
1700    * Logging: FIXME: incomplete
1701    * <ul>
1702    * <li>EAP01: Running <code>command</code> failed. For details...
1703    * <li>EAP02: Running <code>command</code> failed. No log file
1704    * <li>WAP04: if <code>logFile</code> is not readable.
1705    * <li>WLP02: Cannot read blg file: BibTeX run required?
1706    * <li>WFU03: cannot close log file
1707    * <li>WLP04: Cannot read idx file; skip creation of index
1708    * <li>WLP05: Use package 'splitidx' without option 'split'
1709    * <li>EEX01, EEX02, EEX03, WEX04, WEX05:
1710    * if running an exernal command fails.
1711    * </ul>
1712    *
1713    * @param desc
1714    *    the latex main file to be processed.
1715    * @throws BuildFailureException
1716    *    TEX01 as for
1717    *    {@link #preProcessLatex2dev(LatexMainDesc, LatexDev)}
1718    *    but also as for
1719    *    {@link #runLatex2odt(LatexProcessor.LatexMainDesc)}
1720    *    and for {@link #runOdt2doc(LatexMainDesc)}.
1721    * @see #preProcessLatex2dev(LatexMainDesc, LatexDev)
1722    * @see #runLatex2odt(LatexMainDesc)
1723    * @see #runOdt2doc(LatexMainDesc)
1724    * @see Target#docx
1725    */
1726   void processLatex2docx(LatexMainDesc desc)
1727       throws BuildFailureException {
1728     this.log.info("Converting into doc(x) format. ");
1729     // may throw BuildFailureException TEX0,
1730     // log warning EAP01, EAP02, WAP04, WLP02, WFU03, WLP04, WLP05
1731     // EEX01, EEX02, EEX03, WEX04, WEX05
1732     preProcessLatex2dev(desc, this.settings.getPdfViaDvi());
1733     // may throw BuildFailureException TEX0,
1734     // log warning EEX01, EEX02, EEX03, WEX04, WEX05
1735     runLatex2odt(desc);
1736     // may throw BuildFailureException TEX01,
1737     // log warning EEX01, EEX02, EEX03, WEX04, WEX05
1738     runOdt2doc(desc);
1739   }
1740 
1741   /**
1742    * Runs direct conversion of <code>texFile</code> to rtf format.
1743    * <p>
1744    * FIXME: Maybe prior invocation of LaTeX MakeIndex and BibTeX
1745    * after set up the references, bibliography, index and that like
1746    * would be better.
1747    * <p>
1748    * Logging: FIXME: incomplete
1749    * <ul>
1750    * <li>EEX01, EEX02, EEX03, WEX04, WEX05:
1751    * if running an exernal command fails.
1752    * </ul>
1753    *
1754    * @param desc
1755    *    the tex file to be processed.
1756    * @throws BuildFailureException
1757    *    TEX01 if running the latex2rtf command
1758    *    returned by
1759    *    {@link Settings#getLatex2rtfCommand()} failed.
1760    * @see #runLatex2rtf(File)
1761    * @see Target#rtf
1762    */
1763   void processLatex2rtf(LatexMainDesc desc) throws BuildFailureException {
1764     this.log.info("Converting into rtf format. ");
1765     // may throw BuildFailureException TEX01,
1766     // log warning EEX01, EEX02, EEX03, WEX04, WEX05
1767     runLatex2rtf(desc.texFile);//, timestamp
1768   }
1769 
1770   /**
1771    * Runs conversion of <code>texFile</code> to txt format via pdf.
1772    * <p>
1773    * Logging: FIXME: incomplete
1774    * <ul>
1775    * <li>WLP04: Cannot read idx file; skip creation of index
1776    * <li>WLP05: Use package 'splitidx' without option 'split'
1777    * <li>EEX01, EEX02, EEX03, WEX04, WEX05:
1778    * if running an exernal command fails.
1779    * </ul>
1780    *
1781    * @param desc
1782    *    the tex file to be processed.
1783    * @throws BuildFailureException
1784    *    TEX01 as for
1785    *    {@link #processLatex2devCore(LatexMainDesc, LatexDev)}
1786    *    and for {@link #runPdf2txt(LatexMainDesc)}.
1787    * @see #processLatex2devCore(LatexMainDesc, LatexDev)
1788    * @see #runPdf2txt(LatexMainDesc)
1789    * @see Target#txt
1790    */
1791   void processLatex2txt(LatexMainDesc desc)
1792       throws BuildFailureException {
1793     this.log.info("Converting into txt format. ");
1794     LatexDev dev = this.settings.getPdfViaDvi();
1795 
1796     // may throw BuildFailureException TEX01,
1797     // log warning EAP01, EAP02, WAP04, WLP02, WFU03, WLP04, WLP05,
1798     // EEX01, EEX02, EEX03, WEX04, WEX05
1799     processLatex2devCore(desc, dev);
1800     if (dev.isViaDvi()) {
1801       // may throw BuildFailureException TEX01,
1802       // may log warning EEX01, EEX02, EEX03, WEX04, WEX05
1803       runDvi2pdf(desc);
1804     }
1805 
1806     // warnings emitted by LaTex are ignored
1807     // (errors are emitted by runLatex2dev and that like.)
1808     // may throw BuildFailureException TEX01,
1809     // log warning EEX01, EEX02, EEX03, WEX04, WEX05
1810     runPdf2txt(desc);
1811   }
1812 
1813   /**
1814    * Prints meta information, mainly version information
1815    * on this software and on the converters used.
1816    * <p>
1817    * WMI01: If the version string of a converter cannot be read.
1818    * WMI02: If the version of a converter is not as expected.
1819    *
1820    * @param includeVersionInfo
1821    *     whether to include plain version info; else warnings only.
1822    * @return
1823    *    whether a warning has been issued.
1824    * @throws BuildFailureException
1825    *    <ul>
1826    *    <li>TMI01: if the stream to either the manifest file
1827    *    or to a property file, either 
1828    *    {@LINK #VERSION_PROPS_FILE} or
1829    *    {@link MetaInfo.GitProperties#GIT_PROPS_FILE}
1830    *    could not be created.</li>
1831    *    <li>TMI02: if the properties could not be read
1832    *    from one of the two property files mentioned above.</li>
1833    *    <li>TSS05: if converters are excluded in the
1834    *    pom which are not known.</li>
1835    *    </ul>
1836    */
1837   public boolean printMetaInfo(boolean includeVersionInfo)
1838       throws BuildFailureException {
1839     // may throw BuildFailureException TSS05
1840     SortedSet<Converter> convertersExcluded =
1841         this.settings.getConvertersExcluded();
1842     return this.metaInfo.printMetaInfo(includeVersionInfo, convertersExcluded);
1843   }
1844 
1845   boolean runBibtex(LatexMainDesc desc) throws BuildFailureException {
1846     String command = this.settings.getCommand(ConverterCategory.BibTeX);
1847     this.log.debug("Running " + command + " on '" + desc.xxxFile.getName() + "'. ");
1848     String[] args = buildArguments(this.settings.getBibtexOptions(), desc.xxxFile);
1849     // may throw BuildFailureException TEX01,
1850     // may log warning EEX01, EEX02, EEX03, WEX04, WEX05
1851     this.executor.executeEnvR0(desc.parentDir, // workingDir
1852         this.settings.getTexPath(), command, args, desc.withSuffix(SUFFIX_BBL));
1853 
1854     File logFile = desc.withSuffix(SUFFIX_BLG);
1855     // may log EAP01, EAP02, WAP04, WFU03
1856     logErrs(logFile, command, this.settings.getPatternErrBibtex());
1857     // may log warnings WFU03, WAP03, WAP04
1858     logWarns(logFile, command, this.settings.getPatternWarnBibtex());
1859     return true;
1860   }
1861 
1862   /**
1863    * Runs the MakeIndex command
1864    * given by {@link Settings#getMakeIndexCommand()}
1865    * on the idx-file corresponding with <code>texFile</code> which is assumed to exist.
1866    * <p>
1867    * Note that {@link Settings#getMakeIndexCommand()}
1868    * is invoked either directly, or, in case of a multiple index,
1869    * via {@link Settings#getSplitIndexCommand()}. 
1870    * To decide whether splitting is required, 
1871    * the idx-file must be read. 
1872    * If this is not possible, warning WLP04 is emitted 
1873    * indicating that the index is not created. 
1874    * This is the only case where the return value is <code>false</code>. 
1875    * <p>
1876    * Logging:
1877    * <ul>
1878    * <li>WLP04: Cannot read idx file; skip creation of index
1879    * <li>WLP05: Use package 'splitidx' without option 'split'
1880    * <li>EAP01: Running <code>makeindex</code> failed. For details...
1881    * <li>EAP02: Running <code>makeindex</code> failed. No log file
1882    * <li>WAP03: Running <code>makeindex</code> emitted warnings.
1883    * <li>WAP04: .ilg-file is not readable.
1884    * <li>WFU03: cannot close .ilg-file
1885    * <li>EEX01, EEX02, EEX03, WEX04, WEX05:
1886    * if running the makeindex command failed.
1887    * </ul>
1888    *
1889    * @param desc
1890    *     the description of a latex main file <code>dviFile</code>
1891    *     including the idx-file MakeIndex is to be run on.
1892    * @return
1893    *     whether MakeIndex had been run, possibly via splitindex. 
1894    *     This is done, except if the idx-file is not readable 
1895    *     and so it cannot be decided whether to invoke makeindex or splitindex. 
1896    *     In this case the warning WLP04 is emitted. 
1897    * @throws BuildFailureException
1898    *      TEX01 if invocation of the makeindex command
1899    *      returned by
1900    *      {@link Settings#getMakeIndexCommand()} failed.
1901    */
1902   boolean runMakeSplitIndex(LatexMainDesc desc)
1903     throws BuildFailureException {
1904 
1905     // determine the explicit given identifiers of indices
1906     final Set<String> explIdxIdent =
1907         this.fileUtils.collectMatchesForIdx(desc.idxFile,
1908             Pattern.compile(this.settings.getPatternMultiIndex()),
1909             GRP_IDX_IDENT);
1910     if (explIdxIdent == null) {
1911       this.log.warn("WLP04: Cannot read idx file '" + desc.idxFile.getName()
1912           + "'; skip creation of index. ");
1913       return false;
1914     }
1915     assert explIdxIdent != null;
1916 
1917     // Here, explIdxIdent contains the explicit identifiers of all indices
1918     // The identifier idx may be missing or not.
1919 
1920     // package splitidx is used with option split
1921     // is in general not allowed. The criteria are:
1922     // - if \jobname-xxx.idx exists for some xxx whereas \jobname.idx does not:
1923     //   This occurs only for option split
1924     //   and does not allow applying splitindex
1925     //   as it is intended in this software.
1926     //   This would require applying makeindex separately
1927     //   to all \jobname-xxx.idx
1928     // - if \jobname-xxx.idx exists for some xxx
1929     //   and also \jobname.idx exists but has no entry
1930     //   \indexentry[xxx]{...}{..}:
1931     //   This occurs only for option split
1932     //   and applying splitindex yields the wrong result.
1933     //   This would require applying makeindex separately
1934     //   to all \jobname-xxx.idx and to \jobname.idx
1935     // - if \jobname-xxx.idx does not exist for any xxx
1936     //   then all is ok, whether \jobname.idx exists or not.
1937     //   If it exists, even splitidx with option split is ok.
1938 
1939     // so algorithm:
1940     // determine list of these xxx for which \jobname-xxx.idx exists
1941     // if (\jobname-xxx.idx exists for some xxx) {
1942     //   if (!(\jobname.idx exists &&
1943     //         \jobname.idx matches some \indexentry[xxx]{...}{.. )) {
1944     //      log.error(cannot handle splitidx with option split)
1945     //      return false;
1946     // }
1947     // // For second condition,
1948     // // determine list of all yyy matching \indexentry[yyy]{...}{..}
1949     // // and make sure that it is non-empty.
1950     // }
1951 
1952     // filter for extended raw idx-files: \jobname-xxx.idx
1953     FileFilter filter =
1954         this.fileUtils.getFileFilterReplace(desc.idxFile, SEP_IDENT_IDX + ".+");
1955     // may cause WFU01: Cannot read directory
1956     File[] idxFilesExtInDir =
1957         this.fileUtils.listFilesOrWarn(desc.idxFile.getParentFile(), filter);
1958 
1959     // If the directory compising idxFile is not readable,
1960     // idxFilesExtInDir == null
1961     // Then the check for option split cannot be done.
1962 
1963     if (idxFilesExtInDir != null && idxFilesExtInDir.length > 0) {
1964       // Here, idxFilesExtInDir contains the idx-files \jobname-xxx.idx
1965       if (explIdxIdent.isEmpty()) {
1966         // Here, either \jobname.idx does not exist at all
1967         // or does not contain an entry \indexentry[yyy]{...}{..}
1968 
1969         this.log.warn("WLP05: Use package 'splitidx' "
1970             + "without option 'split' in '" + desc.texFile.getName() + "'. ");
1971         // this.log.warn("WLP05: Found extended idx-file " +
1972         // " without (according entry in) '" +
1973         // desc.idxFile.getName() +
1974         // "': use package 'splitidx' " +
1975         // "without option 'split'. ");
1976       }
1977     }
1978 
1979     // TBD: the following is misleading 
1980     // if \sindex[idx]{...} is used with a single index name idx 
1981     // This counts as a multi-index but is only one. 
1982     // But then we need splitindex anyway. 
1983 
1984     // check whether more than one index has to be created
1985     if (explIdxIdent.isEmpty()) {
1986       // may throw BuildFailureException TEX01
1987       // may log warnings EEX01, EEX02, EEX03, WEX04, WEX05,
1988       // EAP01, EAP02, WAP03, WAP04, WFU03
1989       runMakeIndex(desc);
1990     } else {
1991       // may throw BuildFailureException TEX01
1992       // may log warnings EEX01, EEX02, EEX03, WEX04, WEX05,
1993       // EAP01, EAP02, WAP03, WAP04, WFU03
1994       runSplitIndex(desc, explIdxIdent);
1995     }
1996 
1997     return true;
1998   }
1999 
2000   /**
2001    * Runs the MakeIndex command
2002    * given by {@link Settings#getMakeIndexCommand()}. 
2003    * Note that xindy is not supported in this context. 
2004    * <p>
2005    * Logging:
2006    * <ul>
2007    * <li>EAP01: Running <code>makeindex</code> failed. For details...
2008    * <li>EAP02: Running <code>makeindex</code> failed. No log file
2009    * <li>WAP03: Running <code>makeindex</code> emitted warnings.
2010    * <li>WAP04 .ilg-file is not readable.
2011    * <li>WFU03: cannot close .ilg-file
2012    * <li>EEX01, EEX02, EEX03, WEX04, WEX05:
2013    * if running the makeindex command failed.
2014    * </ul>
2015    *
2016    * @param desc
2017    *     the description of a latex main file <code>texFile</code>
2018    *     including the idx-file MakeIndex is to be run on.
2019    * @throws BuildFailureException
2020    *     TEX01 if invocation of the MakeIndex command
2021    *     returned by
2022    *     {@link Settings#getMakeIndexCommand()} failed.
2023    */
2024   private void runMakeIndex(LatexMainDesc desc) throws BuildFailureException {
2025 
2026     String command = this.settings.getCommand(ConverterCategory.MakeIndex);
2027     // File idxFile = desc.idxFile;
2028     // this.log.debug("Running " + command + " on '" + idxFile.getName() + "'. ");
2029     // String[] args =
2030     //     buildArguments(this.settings.getMakeIndexOptions(), idxFile);
2031     File xxxFile = desc.xxxFile;
2032     this.log.debug("Running " + command + " on '" + xxxFile.getName() + "'. ");
2033     String[] args =
2034         buildArguments(this.settings.getMakeIndexOptions(), xxxFile); // may throw BuildFailureException TEX01,
2035     // may log warning EEX01, EEX02, EEX03, WEX04, WEX05
2036     this.executor.executeEnvR0(desc.parentDir, // workingDir
2037         this.settings.getTexPath(), command, args, desc.indFile);
2038 
2039     // detect errors and warnings makeindex wrote into xxx.ilg
2040     // may log EAP01, EAP02, WAP04, WFU03
2041     logErrs(desc.ilgFile, command, this.settings.getPatternErrMakeIndex());
2042     // may log warnings WFU03, WAP03, WAP04
2043     logWarns(desc.ilgFile, command, this.settings.getPatternWarnMakeIndex());
2044   }
2045 
2046   /**
2047    * Combines an array of files from a file prefix <code>filePrefix</code>,
2048    * a collection of intermediate strings <code>variant</code>
2049    * and of the suffix <code>suffix</code>.
2050    *
2051    * @param filePrefix
2052    *     prefix of file name; in practice of index file without suffix
2053    * @param variant
2054    *     collection of strings; in practice set of identifiers of indices
2055    * @param suffix
2056    *     the suffix of a file; in practice {@link #SUFFIX_IDX}.
2057    * @return
2058    *     an array of file names of the form
2059    *     <code>&lt;filePrefix&gt;&lt;ident&gt;&lt;suffix&gt;</code>,
2060    *     where <code>ident</code> runs in <code>variant</code>.
2061    */
2062   private File[] files(String filePrefix, Collection<String> variant,
2063       String suffix) {
2064     File[] res = new File[variant.size()];
2065     int idx = 0;
2066     StringBuilder strb;
2067     for (String idxIdent : variant) {
2068       strb = new StringBuilder();
2069       strb.append(filePrefix);
2070       strb.append(idxIdent);
2071       strb.append(suffix);
2072 
2073       res[idx++] = new File(strb.toString());
2074     }
2075     return res;
2076   }
2077 
2078   /**
2079    * Runs the SplitIndex command
2080    * given by {@link Settings#getSplitIndexCommand()}.
2081    * <p>
2082    * Logging:
2083    * Note that <code>splitindex</code> neither writes a log file
2084    * nor may it fail in itself but invoking <code>makeindex</code>
2085    * or whatever program it uses.
2086    * <ul>
2087    * <li>EAP01: Running <code>splitindex</code> failed. For details...
2088    * <li>EAP02: Running <code>splitindex</code> failed. No log file
2089    * <li>WAP03: Running <code>splitindex</code> emitted warnings.
2090    * <li>WAP04 .ilg-file is not readable.
2091    * <li>WFU03: cannot close .ilg-file
2092    * <li>EEX01, EEX02, EEX03, WEX04, WEX05:
2093    * if running the splitindex command failed.
2094    * </ul>
2095    *
2096    * @param desc
2097    *     the description of a latex main file <code>texFile</code>
2098    *     including the idx-file SplitIndex is to be run on.
2099    * @param explIdxIdent
2100    *     the set of identifiers of indices,
2101    *     whether explicitly given or not in the idx file.
2102    * @throws BuildFailureException
2103    *     TEX01 if invocation of the SplitIndex command returned by
2104    *     {@link Settings#getSplitIndexCommand()} failed.
2105    */
2106   private void runSplitIndex(LatexMainDesc desc, Collection<String> explIdxIdent)
2107       throws BuildFailureException {
2108 
2109     String splitInxCmd = this.settings.getCommand(ConverterCategory.SplitIndex);
2110     // File idxFile = desc.idxFile;
2111     // this.log
2112     //     .debug("Running " + splitInxCmd + " on '" + idxFile.getName() + "'. ");
2113     // // buildArguments(this.settings.getMakeIndexOptions(), idxFile);
2114     //File idxFile = desc.idxFile;
2115     this.log
2116         .debug("Running " + splitInxCmd + " on '" + desc.xxxFile.getName() + "'. ");
2117     // buildArguments(this.settings.getMakeIndexOptions(), idxFile);
2118     // NOTE: named groups are not compatible with the lua version of splitindex 
2119 
2120     // set the options of splitindex set internally and not passed to makeindex or that like 
2121     String groupIdent = Settings.GRP_IDENT;
2122     String[] argsSplitIndexDefault = new String[] {
2123         "-m " + this.settings.getCommand(ConverterCategory.MakeIndex),
2124         // **** no splitindex.tlu
2125         // TBD: take over in .latexmkrc also: else latexmk cannot be used with splitindex.tlu. 
2126         // This is hardcoded by splitidx when writing xxx.ind
2127         "-i " + this.settings.getPatternMultiIndex(),
2128         // This is hardcoded by makeindex when writing xxx.ind
2129         "-r "+groupIdent+GRP_IDX_IDXENTRY+groupIdent+GRP_IDX_KEYPAGE, // groups in IDX_EXPL: \indexentry{...}
2130         // -s -$2 is hardcoded by splitidx when reading in the -xxx.ind-files
2131         "-s " + SEP_IDENT_IDX + groupIdent + GRP_IDX_IDENT
2132     };
2133 
2134     // add the single allowed option for splitindex given by the user 
2135     String argSplitindexOption = this.settings.getSplitIndexOptions();
2136     assert(argSplitindexOption.indexOf(' ')) == -1;// contains no blank, is single option or empty 
2137     // Arguments are default arguments, possibly option given explicitly and file name 
2138     // This is true only if getMakeIndexOptions() is not empty 
2139 
2140     // arguments: default options, user defined option filename 
2141     String[] args = new String[argsSplitIndexDefault.length + (argSplitindexOption.isEmpty() ? 0 : 1) + 1];
2142     System.arraycopy(argsSplitIndexDefault, 0, args, 0, argsSplitIndexDefault.length);
2143     if (!argSplitindexOption.isEmpty()) {
2144       args[args.length - 2] = argSplitindexOption;
2145     }
2146     args[args.length - 1] = desc.xxxFile.getName();
2147 
2148     // Add the options to be passed to makeindex or that like 
2149     String optionsMakeIndex = this.settings.getMakeIndexOptions();
2150     if (!optionsMakeIndex.isEmpty()) {
2151       // Here 'args' must be replaced by 'args -- optionsMakeIndex' 
2152       String[] optionsMake_IndexArr = optionsMakeIndex.split(" ");
2153       String[] optionsSplitIndexArr = args;
2154       args = new String[optionsMake_IndexArr.length + 1
2155           + optionsSplitIndexArr.length];
2156       System.arraycopy(optionsSplitIndexArr, 0, args, 0,
2157           optionsSplitIndexArr.length);
2158       args[optionsSplitIndexArr.length] = "--";
2159       System.arraycopy(optionsMake_IndexArr, 0, args,
2160           optionsSplitIndexArr.length + 1, optionsMake_IndexArr.length);
2161     }
2162 
2163     // determine the resulting ind-files
2164     //explIdxIdent.add(IMPL_IDENT_IDX);
2165     String filePrefix = desc.xxxFile.toString() + SEP_IDENT_IDX;
2166     File[] indFiles = files(filePrefix, explIdxIdent, SUFFIX_IND);
2167 
2168     // may throw BuildFailureException TEX01,
2169     // may log warning EEX01, EEX02, EEX03, WEX04, WEX05
2170     this.executor.executeEnvR0(desc.parentDir, // workingDir
2171         this.settings.getTexPath(), splitInxCmd, args, indFiles);
2172 
2173     // detect errors and warnings splitindex,
2174     // aka makeindex wrote into xxx.ilg
2175     File[] ilgFiles = files(filePrefix, explIdxIdent, SUFFIX_ILG);
2176     splitInxCmd = this.settings.getCommand(ConverterCategory.MakeIndex);
2177     for (int idx = 0; idx < explIdxIdent.size(); idx++) {
2178       // may log EAP01, EAP02, WAP04, WFU03
2179       logErrs(ilgFiles[idx], splitInxCmd,
2180           this.settings.getPatternErrMakeIndex());
2181       // may log warnings WFU03, WAP03, WAP04
2182       logWarns(ilgFiles[idx], splitInxCmd,
2183           this.settings.getPatternWarnMakeIndex());
2184     }
2185   }
2186 
2187   // return whether the processor has been invoked. 
2188   // TBD: clarify whether success plays a role
2189   // parece que nao usa executeEnvR0 return value. 
2190   // a valuable source of information is https://www.dickimaw-books.com/latex/auxglossaries/
2191   boolean runMakeGlossary(LatexMainDesc desc)
2192       throws BuildFailureException {
2193         // message emitted by makeglossaries: makeglossaries diagnostic messages
2194         // not used by makeglossaries-light
2195     // file name without ending: parameter for makeglossaries
2196     File xxxFile = desc.xxxFile;
2197     String command = this.settings.getCommand(ConverterCategory.MakeGlossaries);
2198     this.log.debug("Running " + command + " on '" + xxxFile.getName() + "'. ");
2199     String[] args =
2200       buildArguments(this.settings.getMakeGlossariesOptions(), xxxFile);
2201     // may throw BuildFailureException TEX01,
2202     // may log warning EEX01, EEX02, EEX03, WEX04, WEX05
2203     this.executor.executeEnvR0(desc.parentDir, // workingDir
2204         this.settings.getTexPath(), command, args, desc.glsFile);
2205     // TBD: check whether more than one gls file is possible.
2206 
2207     Boolean isMakeIndexOrLike = this.fileUtils.withMakindexLike(desc.auxFile);
2208     // assert isMakeIndexOrLike;
2209     String patternErr, patternWarn;
2210     if (isMakeIndexOrLike != null && isMakeIndexOrLike.booleanValue()) {
2211       patternErr = this.settings.getPatternErrMakeIndex();
2212       patternWarn = this.settings.getPatternWarnMakeIndex();
2213     } else {
2214       patternErr = this.settings.getPatternErrXindy();
2215       patternWarn = this.settings.getPatternWarnXindy();
2216     }
2217     Set<String> extLogFiles = this.fileUtils.collectLogsForGloss(desc.auxFile);
2218     if (isMakeIndexOrLike == null || extLogFiles == null) {
2219       this.log.warn("WLPXX: Cannot read AUX file; skipping evaluating log files for glossaries. ");
2220       return true;
2221     }
2222     for (String ext : extLogFiles) {
2223       // detect errors and warnings makeglossaries wrote into xxx.glg
2224       File logFile = desc.withSuffix("."+ext);
2225       // may log EAP01, EAP02, WAP04, WFU03
2226       logErrs(logFile, command, patternErr);
2227       // may log warnings WFU03, WAP03, WAP04
2228       logWarns(logFile, command, patternWarn);
2229     }
2230 
2231     return true;
2232   } // runMakeGlossary
2233 
2234   boolean runPythontex(LatexMainDesc desc)
2235       throws BuildFailureException {
2236     File xxxFile = desc.xxxFile;
2237     String command = this.settings.getCommand(ConverterCategory.Pythontex);
2238     this.log.debug("Running " + command + " on '" + xxxFile.getName() + "'. ");
2239     String[] args =
2240         buildArguments(this.settings.getPythontexOptions(), desc.xxxFile);
2241 
2242     File outFolder = TexFileUtils
2243         .replacePrefix(this.settings.getPrefixPytexOutFolder(), desc.xxxFile);
2244     String repOutFileName = desc.xxxFile.getName() + SUFFIX_PYTXMCR;
2245     File repOutFile = new File(outFolder, repOutFileName);
2246     if (repOutFile.exists()) {
2247       // In the long run: eliminate options rerun and runall 
2248       // from xxx.pytxcode; then rerun is controlled by command line options. 
2249       boolean isDel = repOutFile.delete();
2250       if (!isDel) {
2251         this.log.warn("Preliminary warning: Could not delete '" + repOutFile
2252             + "'; this may cause further warnings/errors. ");
2253       }
2254     }
2255     // TBD: repOutFile is not the only result file. 
2256     // one has to add pytxpyg, pkl and the stdout files. 
2257     // The precise names of the latter must be read from the pytxcode file. 
2258 
2259     // may throw BuildFailureException TEX01,
2260     // may log warning EEX01, EEX02, EEX03, WEX04, WEX05
2261     //CommandExecutor.CmdResult res = 
2262     this.executor.executeEnvR0(desc.parentDir, // workingDir
2263         this.settings.getTexPath(), command,
2264         //true, 
2265         // This may change later, when rerun=never is taken into account. 
2266         args, repOutFile);
2267 
2268     File logFile = desc.withSuffix(SUFFIX_PLG);
2269     // may log EAP01, EAP02, WAP04, WFU03
2270     logErrs(logFile, command, this.settings.getPatternErrPyTex());
2271     // may log warnings WFU03, WAP03, WAP04
2272     logWarns(logFile, command, this.settings.getPatternWarnPyTex());
2273     return true;
2274   } // runPythontex
2275 
2276   /**
2277    * Runs the latexmk command given by {@link Settings#getLatexmkCommand()}
2278    * on the latex main file <code>texFile</code>
2279    * described by <code>desc</code>
2280    * in the directory containing <code>texFile</code> with arguments
2281    * <!--given by 
2282    * {@link #buildLatexArguments(Settings, LatexDev, File, boolean)}-->.
2283    * The output format of the LaTeX run is given by <code>PDF</code> currently.
2284    * <p>
2285    * Logs a warning or an error if running latexmk failed.
2286    * <p>
2287    * Logging:
2288    * <ul>
2289    * <li>EEX01, EEX02, EEX03, WEX04, WEX05:
2290    * if running the latexmk command failed.
2291    * </ul>
2292    *
2293    * @param desc
2294    *     the description of a latex main file <code>texFile</code>
2295    *     to be processed.
2296    * @throws BuildFailureException
2297    *     TEX01 if invocation of the latexmk command returned by
2298    *     {@link Settings#getLatexmkCommand()} failed.
2299    */
2300   private void runLatexmk(LatexMainDesc desc) 
2301     throws BuildFailureException {
2302 
2303     File texFile = desc.texFile;
2304     // FIXME: wrong name; better is latex2dev
2305 
2306     String command = this.settings.getLatexmkCommand();
2307 
2308     String[] args = buildLatexmkArguments(settings, desc);
2309     this.log.debug("Running " + command + " on '" + texFile.getName() + "'. ");
2310     // may throw BuildFailureException TEX01,
2311     // may log warning EEX01, EEX02, WEX05
2312     // special: PDF file must exist after but need not be updated 
2313     this.executor.executeBuild(desc.parentDir, // workingDir
2314         this.settings.getTexPath(), command, args, desc.pdfFile);//
2315     // TBD: desc.withSuffix(SUFFIX_HTML): maybe depending on Target
2316   }
2317 
2318   // also for tests
2319   // TBD: eliminate magic comments: occur in Settings and also in .latexmkrc 
2320   protected static String[] buildLatexmkArguments(Settings settings,
2321       LatexMainDesc desc) throws BuildFailureException {
2322 
2323     // running specific compiler given in magic comment 
2324     // Optional<String> programMagic =
2325     //     desc.groupMatch(LatexMainParameterNames.programMagic);
2326     List<String> addArgs = new ArrayList<String>();
2327     // if (programMagic.isPresent()) {
2328     //   addArgs.add("-e");
2329     //   addArgs.add("$programMagic=q/" + programMagic.get() + "/");
2330     // }
2331 
2332     // running with specific time stamp if required in magic comment 
2333     // Optional<String> chkDiffMagic =
2334     //     desc.groupMatch(LatexMainParameterNames.chkDiffMagic);
2335     // if (chkDiffMagic.isPresent()) {
2336     //   addArgs.add("-e");
2337     //   // isChkDiff implements the same except for latexmk 
2338     //   Optional<String> chkDiffMagicValue =
2339     //   desc.groupMatch(LatexMainParameterNames.chkDiffMagicVal);
2340     //   String chkDiffMagicValueStr = chkDiffMagicValue.isPresent()
2341     //   ? chkDiffMagic.get() 
2342     //   : Boolean.TRUE.toString();// default=true if value not present 
2343     //   addArgs.add("$chkDiffMagic=q/" + chkDiffMagicValueStr + "/");
2344     // }
2345 
2346     // create arguments, both from settings and 
2347     // the optional ones from magic comments 
2348     return buildArguments(settings.getLatexmkOptions(), desc.texFile,
2349         addArgs.toArray(new String[addArgs.size()])
2350     );
2351   }
2352 
2353   /**
2354    * Runs the LaTeX command given by {@link #getLatex2pdfCommand()}
2355    * on the latex main file <code>texFile</code>
2356    * described by <code>desc</code>
2357    * in the directory containing <code>texFile</code> with arguments 
2358    * given by {@link #buildLatexArguments(Settings, LatexDev, File, boolean)}.
2359    * The output format of the LaTeX run is given by <code>dev</code>,
2360    * to be more precise by {@link LatexDev#getLatexOutputFormat()}.
2361    * <p>
2362    * Logs a warning or an error if the latex run failed
2363    * invoking {@link #logErrs(File, String)}
2364    * but not if bad boxes occurred or if warnings occurred.
2365    * This is done in
2366    * {@link #processLatex2dev(LatexMainDesc, LatexDev)}
2367    * after the last LaTeX run only.
2368    * <p>
2369    * Logging:
2370    * <ul>
2371    * <li>EAP01: Running <code>latex2pdf</code> failed. For details...
2372    * <li>EAP02: Running <code>latex2pdf</code> failed. No log file
2373    * <li>WAP04: .log-file is not readable.
2374    * <li>WFU03: cannot close .log-file
2375    * <li>EEX01, EEX02, EEX03, WEX04, WEX05:
2376    * if running the latex2pdf command failed.
2377    * </ul>
2378    *
2379    * @param desc
2380    *     the description of a latex main file <code>texFile</code>
2381    *     to be processed.
2382    * @param dev
2383    *     the device describing the output format 
2384    *     which is either pdf or dvi.
2385    *     See {@link LatexDev#getLatexOutputFormat()}.
2386    * @throws BuildFailureException
2387    *     TEX01 if invocation of the latex2pdf command returned by
2388    *     {@link #getLatex2pdfCommand()} failed.
2389    */
2390   private void runLatex2dev(LatexMainDesc desc,
2391                             LatexDev dev)
2392       throws BuildFailureException {
2393 
2394     File texFile = desc.texFile;
2395     // FIXME: wrong name; better is latex2dev
2396     String command = getLatex2pdfCommand();
2397     this.log.debug("Running " + command + " on '" + texFile.getName() + "'. ");
2398     // TBD: this is not completely correct. 
2399     // A wrapper of xelatex would not be recognized. 
2400     // boolean isTypeXelatex =
2401     //     Converter.XeLatex.getCommand().equals(settings.getLatex2pdfCommand());
2402     boolean isTypeXelatex =
2403         Converter.XeLatex.getCommand().equals(command);
2404     String[] args =
2405         buildLatexArguments(this.settings, dev, texFile, isTypeXelatex);
2406     File latexTargetFile = dev.latexTargetFile(desc, isTypeXelatex);
2407     // may throw BuildFailureException TEX01,
2408     // may log warning EEX01, EEX02, EEX03, WEX04, WEX05
2409     // CAUTION: an error also occurs if running xelatex in conjunction with dvi mode 
2410     // because this engine creates xdv instead of dvi 
2411     this.executor.executeEnvR0(desc.parentDir, // workingDir
2412         this.settings.getTexPath(), command, args,
2413         latexTargetFile);
2414 
2415     // logging errors (warnings are done in processLatex2pdf)
2416     // may log EAP01, EAP02, WAP04, WFU03
2417     logErrs(desc.logFile, command);
2418 
2419     // FIXME: documentation that in the dvi file,
2420     // png, jpg and svg are not visible, but present. 
2421   }
2422 
2423   // also for tests
2424   protected static String[] buildLatexArguments(Settings settings, LatexDev dev,
2425       File texFile, boolean isTypeXelatex) throws BuildFailureException {
2426     // FIXME: here it should be taken xelatex into account, using different settings: 
2427     // added is dev==pdf, then nothing, else dev==dvi it is "-no-pdf". 
2428     String options = settings.getLatex2pdfOptions();
2429     if (dev.isDefault()) {
2430       return buildArguments(options, texFile);
2431     }
2432       // Here we shall create dvi or xdv file 
2433       // FIXME: this shall be based on analysis of the options of the converter, 
2434       // not on its name 
2435     return buildArguments(options, texFile, 
2436       isTypeXelatex ? "-no-pdf"
2437                     : "-output-format=" + dev.getLatexOutputFormat());
2438   }
2439 
2440   /**
2441    * Runs conversion from dvi or xdv to pdf-file
2442    * executing {@link #getDvi2pdfCommand()}
2443    * on a dvi/xdv-file covered by <code>desc</code> with arguments
2444    * given by {@link #buildLatexArguments(Settings, LatexDev, File, boolean)}. 
2445    * Note that this works only because all converter of the according category 
2446    * can convert both dvi and xdv 
2447    * and apply also on the filename without extension. 
2448    * If both files are present, the xdv file is processed. 
2449    * <p>
2450    * Logging:
2451    * <ul>
2452    * WLP07: if both dvi and xdv files are present.
2453    * <li>EEX01, EEX02, EEX03, WEX04, WEX05:
2454    * if running the dvi2pdf command failed.
2455    * </ul>
2456    *
2457    * @param desc
2458    *     the description of a latex main file 
2459    *     <code>dviFile</code> or <code>xdvFile</code>
2460    *     including the dvi-file dvi2pdf is to be run on.
2461    * @throws BuildFailureException
2462    *     TEX01 if invocation of the dvi2pdf command returned by
2463    *     {@link #getDvi2pdfCommand()} failed.
2464    */
2465   // used in processLatex2pdf(File) and processLatex2txt(File) only
2466   private void runDvi2pdf(LatexMainDesc desc)
2467       throws BuildFailureException {
2468     assert this.settings.getPdfViaDvi().isViaDvi();
2469 
2470     String command = getDvi2pdfCommand();
2471     // use desc.xxxFile rather than desc.dviFile, 
2472     // because we have to treat both, dvi and xdv
2473     // TBD: warning if both are present; then xdv is preferred. 
2474     if (desc.dviFile.exists() && desc.xdvFile.exists()) {
2475       this.log.warn("WLP07: Found both '" + desc.dviFile + "' and '"
2476           + desc.xdvFile + "'; convert the latter. ");
2477     }
2478     this.log
2479         .debug("Running " + command + " on '" + desc.xxxFile.getName() + "'. ");
2480     String[] args =
2481         buildArguments(this.settings.getDvi2pdfOptions(), desc.xxxFile);
2482     // may throw BuildFailureException TEX01,
2483     // may log warning EEX01, EEX02, EEX03, WEX04, WEX05
2484     this.executor.executeEnvR0(desc.parentDir, // workingDir
2485         this.settings.getTexPath(), command, args, desc.pdfFile);
2486     // FIXME: what about error logging?
2487     // Seems not to create a log-file. 
2488   }
2489 
2490   /**
2491    * Runs the tex4ht command given by {@link Settings#getTex4htCommand()}
2492    * on <code>texFile</code> described by <code>desc</code>
2493    * in the directory containing <code>texFile</code>
2494    * with arguments given by {@link #buildHtlatexArguments(Settings, File)}.
2495    * <p>
2496    * Logging:
2497    * <ul>
2498    * <li>EAP01: Running <code>htlatex</code> failed. For details...
2499    * <li>EAP02: Running <code>htlatex</code> failed. No log file
2500    * <li>WLP03: <code>htlatex</code> created bad boxes
2501    * <li>WLP04: <code>htlatex</code> emitted warnings
2502    * <li>WAP04: log file is not readable.
2503    * <li>WFU03: cannot close log file
2504    * <li>EEX01, EEX02, EEX03, WEX04, WEX05:
2505    * if running the tex4ht command failed.
2506    * </ul>
2507    *
2508    * @param desc
2509    *     the description of a latex main file <code>texFile</code>
2510    *     to be processed.
2511    * @throws BuildFailureException
2512    *     TEX01 if invocation of the tex4ht command
2513    *      returned by {@link Settings#getTex4htCommand()} failed.
2514    */
2515   private void runLatex2html(LatexMainDesc desc) throws BuildFailureException {
2516 
2517     File texFile = desc.texFile;
2518     String command = this.settings.getTex4htCommand();
2519     this.log.debug("Running " + command + " on '" + texFile.getName() + "'. ");
2520     String[] args = buildHtlatexArguments(this.settings, texFile);
2521     // may throw BuildFailureException TEX01,
2522     // may log warning EEX01, EEX02, EEX03, WEX04, WEX05
2523     this.executor.executeEnvR0(desc.parentDir, // workingDir
2524         this.settings.getTexPath(), command, args,
2525         desc.withSuffix(SUFFIX_HTML));
2526 
2527     // logging errors and warnings
2528     // may log EAP01, EAP02, WAP04, WFU03
2529     logErrs(desc.logFile, command);
2530     // may log warnings WFU03, WAP04, WLP03, WLP04
2531     logWarns(desc.logFile, command);
2532   }
2533 
2534   protected static String[] buildHtlatexArguments(Settings settings,
2535       File texFile) {
2536     return new String[] {texFile.getName(), 
2537       settings.getTex4htStyOptions(),
2538       settings.getTex4htOptions(), 
2539       settings.getT4htOptions(),
2540       settings.getLatex2pdfOptions()};
2541   }
2542 
2543   /**
2544    * Runs the latex2rtf command
2545    * given by {@link Settings#getLatex2rtfCommand()}
2546    * on <code>texFile</code>
2547    * in the directory containing <code>texFile</code>
2548    * with arguments given by {@link #buildArguments(String, File, String...)}.
2549    * <p>
2550    * Logging:
2551    * <ul>
2552    * <li>EEX01, EEX02, EEX03, WEX04, WEX05:
2553    * if running the latex2rtf command failed.
2554    * </ul>
2555    *
2556    * @param texFile
2557    *     the latex file to be processed.
2558    * @throws BuildFailureException
2559    *     TEX01 if invocation of the latex2rtf command
2560    *     returned by
2561    *     {@link Settings#getLatex2rtfCommand()} failed.
2562    */
2563   private void runLatex2rtf(File texFile) throws BuildFailureException {
2564     String command = this.settings.getCommand(ConverterCategory.LaTeX2Rtf);
2565     this.log.debug("Running " + command + " on '" + texFile.getName() + "'. ");
2566     String[] args =
2567         buildArguments(this.settings.getLatex2rtfOptions(), texFile);
2568     // may throw BuildFailureException TEX01,
2569     // may log warning EEX01, EEX02, EEX03, WEX04, WEX05
2570     this.executor.executeEnvR0(texFile.getParentFile(), // workingDir
2571         this.settings.getTexPath(), command, args,
2572         TexFileUtils.replaceSuffix(texFile, SUFFIX_RTF));
2573 
2574     // FIXME: no check: just warning that no output has been created.
2575     // Warnings and error messages are output to stderr
2576     // and by default listed in the console window.
2577     // aThey can be redirected to a file “latex2rtf.log” by
2578     // appending 2>latex2rtf.log to the command line.
2579   }
2580 
2581   /**
2582    * Runs conversion from latex to odt
2583    * executing {@link Settings#getTex4htCommand()}
2584    * on <code>texFile</code>
2585    * in the directory containing <code>texFile</code> with arguments
2586    * given by {@link #buildLatexArguments(Settings, LatexDev, File, boolean)}.
2587    * <p>
2588    * Logs a warning or an error if the latex run failed
2589    * invoking {@link #logErrs(File, String)}
2590    * but not if bad boxes ocurred or if warnings occurred.
2591    * This is done in
2592    * {@link #processLatex2dev(LatexMainDesc, LatexDev)}
2593    * after the last LaTeX run only.
2594    * <p>
2595    * Logging:
2596    * <ul>
2597    * <li>EAP01: Running <code>htlatex</code> failed. For details...
2598    * <li>EAP02: Running <code>htlatex</code> failed. No log file
2599    * <li>WLP03: <code>htlatex</code> created bad boxes
2600    * <li>WLP04: <code>htlatex</code> emitted warnings
2601    * <li>WAP04: log file is not readable.
2602    * <li>WFU03: cannot close log file
2603    * <li>EEX01, EEX02, EEX03, WEX04, WEX05:
2604    * if running the tex4ht command failed.
2605    * </ul>
2606    *
2607    * @param desc
2608    *     the descriptor of the latex main file to be processed.
2609    * @throws BuildFailureException
2610    *     TEX01 if invocation of the tex4ht command
2611    *     returned by {@link Settings#getTex4htCommand()} failed.
2612    */
2613   private void runLatex2odt(LatexMainDesc desc) throws BuildFailureException {
2614     File texFile = desc.texFile;
2615     String command = this.settings.getTex4htCommand();
2616     this.log.debug("Running " + command + " on '" + texFile.getName() + "'. ");
2617     String[] args = new String[] {texFile.getName(), "xhtml,ooffice", // there is no choice here
2618         "ooffice/! -cmozhtf", // ooffice/! represents a font direcory
2619         "-coo -cvalidate"// -coo is mandatory, -cvalidate is not
2620     };
2621     // may throw BuildFailureException TEX01,
2622     // may log warning EEX01, EEX02, EEX03, WEX04, WEX05
2623     this.executor.executeEnvR0(desc.parentDir, this.settings.getTexPath(), command,
2624         args, desc.withSuffix(SUFFIX_ODT));
2625 
2626     // FIXME: logging refers to latex only, not to tex4ht or t4ht script
2627     // may log EAP01, EAP02, WAP04, WFU03
2628     logErrs(desc.logFile, command);
2629     // may log warnings WFU03, WAP04, WLP03, WLP04
2630     logWarns(desc.logFile, command);
2631   }
2632 
2633   // FIXME: missing options.
2634   // above all (input) doctype: -ddoc, -ddocx
2635   // and (output) doctype: -fdoc, -fdocx,
2636   // available: odt2doc --show.
2637   // among those also: latex and rtf !!!!!!
2638   // This is important to define the copy filter accordingly
2639   /**
2640    * Runs conversion from odt to doc or docx-file
2641    * executing {@link Settings#getOdt2docCommand()}
2642    * on an odt-file created from <code>texFile</code>
2643    * in the directory containing <code>texFile</code> with arguments
2644    * given by {@link #buildLatexArguments(Settings, LatexDev, File, boolean)}.
2645    * <p>
2646    * Logging:
2647    * <ul>
2648    * <li>EEX01, EEX02, EEX03, WEX04, WEX05:
2649    * if running the odt2doc command failed.
2650    * </ul>
2651    *
2652    * @param desc
2653    *     the description of a latex main file <code>texFile</code>
2654    *     to be processed.
2655    * @throws BuildFailureException
2656    *     TEX01 if invocation of the odt2doc command returned by
2657    *     {@link Settings#getOdt2docCommand()} failed.
2658    */
2659   private void runOdt2doc(LatexMainDesc desc) throws BuildFailureException {
2660     File odtFile = desc.withSuffix(SUFFIX_ODT);
2661     String command = this.settings.getCommand(ConverterCategory.Odt2Doc);
2662     this.log.debug("Running " + command + " on '" + odtFile.getName() + "'. ");
2663     String[] args = buildArguments(this.settings.getOdt2docOptions(), odtFile);
2664     String suffix = null;
2665     for (int idx = 0; idx < args.length - 1; idx++) {
2666       // FIXME: -f is hardcoded
2667       if (args[idx].startsWith("-f")) {
2668         assert suffix == null;// -f comes once only
2669         // without leading '-f'
2670         suffix = args[idx].substring(2, args[idx].length());
2671       }
2672     }
2673     // FIXME: no validity check on suffix 
2674     assert suffix != null;
2675     // may throw BuildFailureException TEX01,
2676     // may log warning EEX01, EEX02, EEX03, WEX04, WEX05
2677     this.executor.executeEnvR0(desc.parentDir, this.settings.getTexPath(), command,
2678         args, desc.withSuffix(suffix));
2679     // FIXME: what about error logging?
2680     // Seems not to create a log-file.
2681   }
2682 
2683   /**
2684    * Runs conversion from pdf to txt-file
2685    * executing {@link Settings#getPdf2txtCommand()}
2686    * on a pdf-file created from <code>texFile</code>
2687    * in the directory containing <code>texFile</code> with arguments
2688    * given by {@link #buildLatexArguments(Settings, LatexDev, File, boolean)}.
2689    * <p>
2690    * Logging:
2691    * <ul>
2692    * <li>EEX01, EEX02, EEX03, WEX04, WEX05:
2693    * if running the pdf2txt command failed.
2694    * </ul>
2695    *
2696    * @param desc
2697    *     the description of a latex main file <code>texFile</code>
2698    *     to be processed.
2699    * @throws BuildFailureException
2700    *     TEX01 if invocation of the pdf2txt command returned by
2701    *     {@link Settings#getPdf2txtCommand()} failed.
2702    */
2703   private void runPdf2txt(LatexMainDesc desc) throws BuildFailureException {
2704     File pdfFile = desc.withSuffix(SUFFIX_PDF);
2705     String command = this.settings.getCommand(ConverterCategory.Pdf2Txt);
2706     this.log.debug("Running " + command + " on '" + pdfFile.getName() + "'. ");
2707     String[] args = buildArguments(this.settings.getPdf2txtOptions(), pdfFile);
2708     // may throw BuildFailureException TEX01,
2709     // may log warning EEX01, EEX02, EEX03, WEX04, WEX05
2710     this.executor.executeEnvR0(desc.parentDir, this.settings.getTexPath(), command,
2711         args, desc.withSuffix(SUFFIX_TXT));
2712     // FIXME: what about error logging?
2713     // Seems not to create a log-file.
2714   }
2715 
2716   void processCheck(LatexMainDesc desc) throws BuildFailureException {
2717     this.log.info("Checking source. ");
2718     runChktex(desc);
2719   }
2720 
2721   /**
2722    * Runs the check command given by {@link Settings#getChkTexCommand()}
2723    * on the latex main file <code>texFile</code>
2724    * in the directory containing <code>texFile</code>
2725    * creating a log file with ending {@link #SUFFIX_CLG}
2726    * in that directory.
2727    * <p>
2728    * Logging:
2729    * <ul>
2730    * <li>EEX01, EEX02, EEX03, WEX04, WEX05:
2731    * if running the ChkTeX command failed.
2732    * </ul>
2733    *
2734    * @param desc
2735    *    the description of a latex main file <code>texFile</code>
2736    *    to be processed.
2737    * @throws BuildFailureException
2738    *     TEX01 if invocation of the check command
2739    *     returned by {@link Settings#getChkTexCommand()} failed.
2740    */
2741   private void runChktex(LatexMainDesc desc) throws BuildFailureException {
2742     File texFile = desc.texFile;
2743     File clgFile = desc.withSuffix(SUFFIX_CLG);
2744     String command = this.settings.getCommand(ConverterCategory.LatexChk);
2745     this.log.debug("Running " + command + " on '" + texFile.getName() + "'. ");
2746     String[] args = buildChkTexArguments(this.settings.getChkTexOptions(),
2747         texFile, clgFile);
2748     // may throw BuildFailureException TEX01,
2749     // may log warning EEX01, EEX02, EEX03, WEX04, WEX05
2750     CommandExecutor.CmdResult res = this.executor.executeEmptyEnv(
2751         texFile.getParentFile(), this.settings.getTexPath(), command,
2752         CommandExecutor.ReturnCodeChecker.IsOne, args, clgFile);
2753     switch (res.returnCode) {
2754       case 0: // all ok 
2755         if (clgFile.exists() && clgFile.length() != 0) {
2756           this.log.info("Checker '" + command + "' logged a message in '"
2757               + clgFile.getName() + "'. ");
2758         }
2759         break;
2760       case 1: // execution error already treated in executor; 
2761         // nothing to be done. 
2762         // see CommandExecutor.ReturnCodeChecker.isOne.hasFailed
2763         break;
2764       case 3: // no execution error but check found error 
2765         this.log.error("ELP02: Checker '" + command + "' logged an error in '"
2766             + clgFile.getName() + "'. ");
2767         break;
2768       case 2: // no execution error and no error but warning. 
2769         this.log.warn("WLP08: Checker '" + command + "' logged a warning in '"
2770             + clgFile.getName() + "'. ");
2771         break;
2772       default:
2773         logErrUnexpectedReturnCode(command, res);
2774     }
2775     // Possibly, if not using the -q option 
2776     // the status messages delivers even more pieces of information. 
2777     // Maintain this, to indicate why WLP06 does not occur any more. 
2778     // if (!clgFile.exists()) {
2779     //     // Here, chktex could not perform the check
2780     //     // but the failure is already logged.
2781     //     return;
2782     // }
2783     // // assert !clgFile.isDirectory();
2784     // if (clgFile.length() != 0) {
2785     //     this.log.warn("WLP06: Running " + command +
2786     //             " found issues logged in '" +
2787     //             texFile.getName() + "'. ");
2788     // }
2789   }
2790 
2791   /**
2792    * Logs an error on command <code>command</code> if the result <code>command</code> 
2793    * returned an unexpected return value. 
2794    * This method shall be used for checker commands only. 
2795    * The background is, that an error like that comes from a change in the application 
2796    * which needs adaption of this software. 
2797    * The error is the trigger for further development. 
2798    * 
2799    * @param command
2800    *    the ckecker command which returned an unexpected return value. 
2801    * @param res
2802    *    the result with unexpected return value. 
2803    */
2804   private void logErrUnexpectedReturnCode(String command, CommandExecutor.CmdResult res) {
2805         this.log.error("ELP01: For command '" + command
2806             + "' found unexpected return code " + res.returnCode + ". ");
2807   }
2808 
2809   /**
2810    * Runs the validation command for PDF format given by {@link Settings#getcommand
2811    * @param desc
2812    * @throws BuildFailureException
2813    */
2814   private void runValidatePdf(LatexMainDesc desc) throws BuildFailureException {
2815     File pdfFile = desc.pdfFile;
2816     String command = this.settings.getCommand(ConverterCategory.StandardValidator);
2817     this.log.debug("Running " + command + " on '" + pdfFile.getName() + "'. ");
2818     String[] args = buildArguments(this.settings.getChkTexOptions(), pdfFile);
2819     // may throw BuildFailureException TEX01,
2820     // may log warning EEX01, EEX02, EEX03, WEX04, WEX05
2821     CommandExecutor.CmdResult res = this.executor.executeEmptyEnv(
2822         pdfFile.getParentFile(), this.settings.getTexPath(), command,
2823         CommandExecutor.ReturnCodeChecker.IsNotZeroOrOne, args);
2824         // TBD: display the standards. 
2825         // TBD: clarify how to use verapdf output 
2826         switch (res.returnCode) {
2827           case 0: // check executed and passed
2828             this.log.info("Validation of '" + pdfFile.getName() + "' passed. ");
2829             break;
2830           case 1: // check executed and failed
2831             this.log.warn("XXX: Validation of '" + pdfFile.getName() + "' failed. ");
2832             break;
2833           case 2:
2834           case 3:
2835           case 4:
2836             // case 5 is missing 
2837           case 6:
2838           case 7:
2839           case 8:
2840           case 9:
2841           case 10:
2842           case 11:
2843           case 12: // execution error already treated in executor;
2844             // nothing to be done.
2845             // see CommandExecutor.ReturnCodeChecker.IsNotZeroOrOne.hasFailed
2846             break;
2847           default:
2848             logErrUnexpectedReturnCode(command, res);
2849         }
2850       }
2851 
2852 
2853 
2854   /**
2855    * Returns an array of strings,
2856    * each entry with a single option given by <code>options</code>
2857    * except the last three which represent <code>-o clgFile texFile</code>.
2858    *
2859    * @param options
2860    *     the options string. The individual options
2861    *     are expected to be separated by a single blank.
2862    * @param texFile
2863    *     the latex main file to be checked.
2864    * @param clgFile
2865    *     the log-file with the result of the check of
2866    *     <code>texFile</code>.
2867    * @return
2868    *     An array of strings:
2869    *     If <code>options</code> is not empty,
2870    *     the first entries are the options in <code>options</code>.
2871    *     The last three entries are
2872    *     <code>-o</code>, <code>clgFile</code> and <code>texFile</code>.
2873    */
2874   protected static String[] buildChkTexArguments(String options, File texFile,
2875       File clgFile) {
2876     return buildArguments(options, texFile, "-o", clgFile.getName());
2877   }
2878 
2879   /**
2880    * Returns whether the given pdf files coincide
2881    * running the diff tool specified
2882    * by {@link Settings#getCommand(ConverterCategory)}
2883    * with {@link ConverterCategory#DiffPdf}. 
2884    * For a checker method the return value is quite unusual: 
2885    * It causes a break of the process. 
2886    *
2887    * Logging:
2888    * <ul>
2889    * <li>EEX01, EEX02, EEX03, WEX04, WEX05:
2890    * if running the ChkTeX command failed.
2891    * </ul>
2892    * 
2893    * 
2894    * @param pdfFileCmp
2895    *     the pdf file for comparison
2896    * @param pdfFileAct
2897    *     the pdf file actually created.
2898    * @return
2899    *     whether <code>pdfFileAct</code> could be checked to be coincideswith
2900    *     <code>pdfFileCmp</code>. 
2901    *     This is false whether the check failed or the check could not be performed. 
2902    * @throws BuildFailureException
2903    *      TEX01 if invocation of the diff command failed. 
2904    * @see #runChktex(LatexMainDesc)
2905    */
2906   private boolean runDiffPdf(File pdfFileCmp, File pdfFileAct)
2907       throws BuildFailureException {
2908     //
2909     // File clgFile = TexFileUtils.replaceSuffix(texFile, SUFFIX_CLG);
2910     String command = this.settings.getCommand(ConverterCategory.DiffPdf);
2911     this.log.debug("Running " + command + " diffing '" + pdfFileCmp.getName()
2912         + "' and '" + pdfFileAct.getName() + "'. ");
2913     // String[] args = buildChkTexArguments(this.settings.getChkTexOptions(),
2914     // texFile,
2915     // clgFile);
2916     String[] args = new String[] {pdfFileCmp.toString(), pdfFileAct.toString()};
2917 
2918     // may throw BuildFailureException TEX01,
2919     // may log warning EEX01, EEX02, EEX03, WEX04, WEX05
2920     CmdResult res = this.executor.executeEmptyEnv(null, // texFile.getParentFile(),
2921         this.settings.getTexPath(), command,
2922         CommandExecutor.ReturnCodeChecker.IsNotZeroOrOne, args);
2923     // other value 2 caused an exception before 
2924     //assert returnCode == 0 || returnCode == 1 : "diff unexpected return value "+returnCode;
2925     // 0 means that the files 'coincide', 
2926     // where as 2 means that there is a significant difference 
2927     int returnCode = res.returnCode;
2928     switch (returnCode) {
2929       case 0:
2930         // Here, files could be diffed and coincide. 
2931         // treated outside via return value 
2932        break;
2933       case 1:
2934         // Here, files could be diffed but do not coincide 
2935         // treated outside via return value 
2936         break;
2937      case 2:
2938         // Here, files could not be diffed because of an error in command 
2939         // This is already treated in executor 
2940         // see CommandExecutor.ReturnCodeChecker.IsNotZeroOrOne
2941         // Nevertheless, the return value is part of the treatment. 
2942         break;
2943       default:
2944         logErrUnexpectedReturnCode(command, res);
2945     }
2946       return returnCode == 0;
2947   }
2948 
2949   // run pdfinfo or that like
2950   // in the long should return a map for metdatata. 
2951   // currently it is the create time as epoch 
2952   /**
2953    * Returns the meta info <code>CreationDate</code> of <code>pdfFile</code>, 
2954    * which is in fact date and time, as epoch time in seconds 
2955    * by invoking a tool like <code>prefinfo</code>. 
2956    * @param pdfFile
2957    * @return
2958    *    <code>CreationDate</code> as epoch time. 
2959    * @throws BuildFailureException
2960    */
2961   long runPdfInfo(File pdfFile) throws BuildFailureException {
2962     //System.out.println("pdfinfo on "+pdfFile);
2963     String command = this.settings.getCommand(ConverterCategory.MetaInfoPdf);
2964     this.log.debug("Running " + command + " extracting metainformation from '" + pdfFile.getName() + "''. ");
2965     // TBD: neither of this works really. first yields wrong pdf, second does not work with more than 1 option 
2966     //String[] args = buildArguments(this.settings.getPdfMetainfoOptions(), pdfFile);
2967     String[] args = new String[] {this.settings.getPdfMetainfoXmpOptions(), pdfFile.toString()};
2968     CmdResult res = this.executor.executeEmptyEnv(null, // texFile.getParentFile(),
2969         this.settings.getTexPath(), command,
2970         CommandExecutor.ReturnCodeChecker.IsNonZero, args);
2971     //System.out.println("pdfinfo yields\n"+res.output);
2972     // TBD: eliminate literal 
2973 
2974     String patternString;
2975     if (res.output.isEmpty()) {
2976       // no XMP data, fallback 
2977       System.out.println("no xmp");
2978       args = new String[] {this.settings.getPdfMetainfoOptions(), pdfFile.toString()};
2979       res = this.executor.executeEmptyEnv(null, // texFile.getParentFile(),
2980         this.settings.getTexPath(), command,
2981         CommandExecutor.ReturnCodeChecker.IsNonZero, args);
2982         patternString = "CreationDate:\\s*(?<creationDate>.*?)\\R";
2983     } else {
2984       // Here, XMP data is available and latex then writes date into it 
2985       patternString = "<xmp:CreateDate>(?<creationDate>.*?)</xmp:CreateDate>\\R";
2986     }
2987     Pattern pattern = Pattern.compile(patternString);
2988     Matcher matcher = pattern.matcher(res.output);
2989 
2990    if (!matcher.find()) {
2991       // TBD: rethink 
2992       throw new RuntimeException("Found no creation date");
2993     }
2994     String creationDateIso8601 = matcher.group("creationDate");// TBD: eliminate literal 
2995     long epochTimeSec = ZonedDateTime.parse(creationDateIso8601).toEpochSecond();
2996     //long epochTimeSec = Instant.parse(creationDateIso8601).getEpochSecond();
2997 
2998     // String[] lines = res.output.split("\\R");
2999     // for (String line : lines) {
3000     //   line
3001     // }
3002 
3003     return epochTimeSec;
3004     
3005   }
3006 
3007   /**
3008    * The folder in the jar-file containing this application 
3009    * containing the files of the {@link Injection}s. 
3010    */
3011   private static final String FOLDER_INJ = "injections/";
3012 
3013   /**
3014    * Processes injection of files 
3015    * like <code>.latexmkrc</code> and <code>.chktexrc</code> 
3016    * as described by <code>injections</code>, e.g. 
3017    * <ul>
3018    * <li> performing filtering similar to the resources plugin, </li>
3019    * <li> turning the result into an executable file, 
3020    * </ul>
3021    * Filtering differs from the mechanism of the resources plugin 
3022    * in two respects: 
3023    * <ul>
3024    * <li> The names are those of parameters in settings, 
3025    * not properties in the pom. </li>
3026    * <li> The sources are loaded as a resource by the classloader 
3027    * by name <code>.latexmkrc</code> and <code>.chktexrc</code>, respectively. 
3028    * They are a kind of templates containing parameter names 
3029    * and the target is a file in the folder 
3030    * given by {@link Settings#getTexSrcDirectoryFile()}. 
3031    * This file contains the values of the parameters, finally. 
3032    * </ul>
3033    * 
3034    * may log: 
3035    * <ul>
3036    * <li> WFU10: if the file has not been created by this software 
3037    * <li> WFU11: if it cannot be ensured that the file has been created 
3038    *      by this software </li>
3039    *      or if the reader to read to decide cannot be closed. </li>
3040    * </ul>
3041    * 
3042    * @param injections
3043    *   define the files to be injected and how injection is done. 
3044    * @throws BuildFailureException
3045    *   <ul>
3046    *   <li>
3047    *   TLP03: Failure while writing file '...' or closing in-stream. 
3048    *   The file is created from a template replacing parameter names 
3049    *   by their actual values. 
3050    *   A reason may be that the template cannot be read 
3051    *   or its in-stream cannot be closed. </li>
3052    *   <li> TMI01: if the stream to the template cannot be read. </li>
3053    *   <li> TMI01: if the stream to the according properties file 
3054    *        could not be created </li>
3055    *   <li> TMI02: if the properties could not be read. </li>
3056    *   </ul>
3057    */
3058   //      
3059   public void processFileInjections(Set<Injection> injections)
3060       throws BuildFailureException {
3061     //this.settings.getProperties();
3062     // TBD: centralize this because also needed for goal clr
3063 
3064     for (Injection inj : injections) {
3065       String fileName = inj.getFileName();
3066       // may throw TMI01
3067       InputStream inStream = MetaInfo.getStream(FOLDER_INJ+fileName);
3068       File outFile = this.settings.rcResourceToFile(fileName);
3069 
3070       try {
3071         // isCreatedByMyself may emit warnings WFU10, WFU11 
3072         if (!outFile.exists()
3073             || this.fileUtils.isCreatedByMyself(outFile, inj)) {
3074           // Here, outFile does not exist or is a regular file. 
3075 
3076           // may throw FileNotFoundException and so IOException: 
3077           // if does not exist 
3078           // and a regular file of that name cannot be created for writing 
3079           // or if it does exist and cannot be opened for writing, 
3080           // or if some error occurs when creating or opening for writing 
3081           PrintStream writer = new PrintStream(outFile);
3082           // may throw IOException: readline. 
3083           // may throw TMI01, TMI02
3084           String version = this.metaInfo.getCoordinates().version;
3085           this.settings.filterInjection(inStream, writer, version, inj);
3086         }
3087         // may throw IOExeption 
3088         inStream.close();
3089       } catch (IOException ioe) {
3090         // Here, the goal inj must be aborted, or inStream could not be closed. 
3091         // THis is true not only when the Print stream cannot be created, 
3092         // but also if the resource cannot be read in filterRcFile. 
3093         // To be strict, one could go on even if inStream could not be closed. 
3094         throw new BuildFailureException("TLP03 Failure while writing file '"
3095             + fileName + "' or closing in-stream. ", ioe);
3096       }
3097 
3098       boolean success = outFile.setExecutable(inj.setExecutable(), false);
3099       assert success || !inj.setExecutable();
3100     }
3101   }
3102 
3103   /**
3104    * Deletes injected files if written by this software if possible. 
3105    * 
3106    * WFU10, WFU11: if a config file is not written by this software 
3107    *               or it is not clear or the reader cannot close. 
3108    * EFU05: if the config file shall be deleted but this coes not work. 
3109    */
3110   private void clearInjFiles() {
3111     for (Injection inj : Injection.values()) {
3112       File outFile = this.settings.rcResourceToFile(inj.getFileName());
3113       // isCreatedByMyself may emit warnings WFU10, WFU11 
3114       if (outFile.exists() && this.fileUtils.isCreatedByMyself(outFile, inj)) {
3115         // could emit EFU05
3116         this.fileUtils.deleteOrError(outFile, false);
3117       }
3118     }
3119   }
3120 
3121 }