1
2 package eu.simuline.m2latex.core;
3
4 import java.io.BufferedReader;
5 import java.io.File;
6 import java.io.FileReader;
7 import java.io.IOException;
8
9 import com.florianingerl.util.regex.Matcher;
10 import com.florianingerl.util.regex.Pattern;
11
12
13 /**
14 * Very bad name:
15 * Represents some aspect of postprocessing
16 * performed in {@link LatexProcessor#processLatex2devCore(LatexMainDesc, LatexDev)}.
17 * No it is auxiliary processing,
18 * because it is alternating auxiliary tools and latex compiler.
19 * <p>
20 * Auxiliary processing consists of two parts:
21 * <ul>
22 * <li>the run of a latex compiler, typically via a package loaded,
23 * writes (into) a file.
24 * This may be a specific file or a file common for more auxiliary processings.
25 * At time of this writing this is the AUX file.
26 * In that case, the latex run writes lines into the file
27 * which are specific for the auxiliary processing.
28 * <li>the subsequent run of an auxiliary program,
29 * triggered if that specific file is present,
30 * or if the specific lines in a common file are present.
31 * The auxiliary program reads the lines of that file
32 * and writes some file, typically another one,
33 * which is read by the next run of the latex compiler.
34 * </ul>
35 *
36 * As described above, the initial run of the latex compiler
37 * writes (lines into) a file,
38 * the presence of which triggers invocation of the corresponding auxiliary program
39 * which writes some file which is read by the next run of the latex compiler.
40 * If the compiler writes a different file,
41 * of course the auxiliary program must be rerun and so on.
42 * There are really cases where this does not terminate
43 * so to ensure termination a maximal number of runs of the latex compiler is specified.
44 * <p>
45 * Creating a bibliography with <code>bibtex</code> or equivalent,
46 * is one of the auxiliary processings {@link #BibTex}.
47 * It is a bit special in that the latex compiler does not need a special package
48 * to write the bibliographical information into the AUX file.
49 * The auxiliary program <code>bibtex</code> or that like extracts and sorts the entries
50 * into a BBL file which is read back by the latex compiler
51 * to create the bibliography.
52 * CAUTION: the lines of the AUX file read by <code>bibtex</code>
53 * may be altered by subsequent calls of a latex compiler or of an auxiliary program
54 * as shown by https://tex.stackexchange.com/questions/724138/is-backreference-possible-with-bibtex-maybe-in-conjunction-with-biblatex.
55 * <p>
56 * At time of this writing, bibliography with package <code>biblatex</code>
57 * in conjunction with auxiliary program <code>biber</code> is not yet supported.
58 * <p>
59 * Writing an index is only supported via package <code>idxindex</code>
60 * and auxiliary program <code>makeindex</code>.
61 * This includes <code>splitindex</code> for multiple indices
62 * invoking <code>makeindex</code> internally.
63 * This is {@link #Idx}.
64 * Clearly a subsequent run of the latex compiler changes the index
65 * if the index entry moves to another page number.
66 * <p>
67 * At time of this writing, indices with package <code>???</code>
68 * in conjunction with auxiliary program <code>xindy</code> is not yet supported.
69 *
70 */
71 enum Auxiliary {
72
73 /**
74 * Bibliography processing with bibtex or related.
75 * TBC: in conjunction with biblatex package also?
76 * Maybe this shall not be included, because no rerun needed.
77 * Then the process we want to introduce is superluous.
78 */
79 BibTex {
80 // CAUTION: it is not .bib, it is .aux.
81 // TBD: it is only part of .aux and also:
82 // for includes the files referenced therein are also to be taken into account.
83 String extension() {
84 return ".aux";
85 }
86
87 boolean doesFitAuxiliary(File file) {
88 return doesFitAuxiliary(file, PATTERN_NEED_BIBTEX_RUN);
89 }
90
91
92 int numRunsAfter() {
93 return 2;
94 }
95
96 /**
97 * Takes not only the AUX file <code>file</code>
98 * into account, but also files included
99 * via command {@link #PATTERN_INPUT}.
100 */
101 FileId getIdent(File file) throws IOException {
102 //System.out.println("update:Bibtex");
103 return updateIdentInclude(file, new FileId(), PATTERN_BIBTEX);
104 }
105
106 boolean process(LatexMainDesc desc, LatexProcessor proc)
107 throws BuildFailureException {
108 return proc.runBibtex(desc);
109 }
110
111
112
113
114 },
115 // /**
116 // * Bibliography processing with biblatex and biber.
117 // * TBC: is this really the sole case with backrefence
118 // * which may force rerun?
119 // */
120 // BibEr {
121 // // CAUTION: it is not .bib, it is
122 // String extension() {
123 // return ".bcf";
124 // }
125 //},
126 // TBD: index with xindy
127
128 /**
129 * Index processing with makeindex and possibly splitindex.
130 */
131 Idx {
132 String extension() {
133 return ".idx";
134 }
135
136 boolean process(LatexMainDesc desc, LatexProcessor proc)
137 throws BuildFailureException {
138 return proc.runMakeSplitIndex(desc);
139 }
140 },
141 Glo {
142 String extension() {
143 return ".aux";
144 }
145
146 boolean doesFitAuxiliary(File file) {
147 return doesFitAuxiliary(file, PATTERN_NEED_MAKEGLOSSARIES_RUN);
148 }
149
150 FileId getIdent(File file) throws IOException {
151 //System.out.println("update:Bibtex");
152 return updateIdentGls(file, new FileId());
153 }
154
155 // This need not be a seprate function.
156 // It is, to be more extensible
157 // if relevant material turns out to occur
158 // which is not in the top level AUX file.
159 /**
160 * Updates the identifier <code>fileId</code>
161 * taking lines in <code>file</code> into account
162 * matching {@link #PATTERN_INPUT_GLOSSARY}
163 * indicating the presence of a specific glossary
164 * and taking whole files into account
165 * the name of which is the name of this AUX file
166 * with the extension replaced by
167 * the content of the group {@link #GRP_EXT_GLOSS}.
168 * @param file
169 * an AUX file from which the FileId is computed.
170 * @param fileId
171 * the empty FileId.
172 * @return
173 * the FileId created.
174 * @throws IOException
175 */
176 FileId updateIdentGls(File file, FileId fileId) throws IOException {
177 // File parent = file.getParentFile();
178 // String inFile;
179 File glossFile;
180 try (BufferedReader bufferedReader =
181 new BufferedReader(new FileReader(file))) {
182 for (String line = bufferedReader.readLine(); line != null;
183 // readLine may thr. IOException
184 line = bufferedReader.readLine()) {
185 Matcher matcher = PATTERN_INPUT_GLOSSARY.matcher(line);
186 if (!matcher.find()) {
187 continue;
188 }
189 // Take all glossary files into account
190 // which are created by makeglossaries
191 fileId.update(line);
192 glossFile = TexFileUtils.replaceSuffix(file,
193 "." + matcher.group(GRP_EXT_GLOSS));
194 updateIdent(glossFile, fileId);
195 // We assume all is top level
196 // Matcher matcher = PATTERN_INPUT.matcher(line);
197 // if (matcher.find()) {
198 // inFile = matcher.group(GRP_INPUT);
199 // assert inFile.endsWith(this.extension());
200 // // ignore return value
201 // updateIdentInclude(new File(parent, inFile), fileId, patternAux);
202 // }
203 } // for
204 } // try
205 return fileId;
206 }
207
208 boolean process(LatexMainDesc desc, LatexProcessor proc)
209 throws BuildFailureException {
210 return proc.runMakeGlossary(desc);
211 }
212 },
213 Pyt {
214 String extension() {
215 return ".pytxcode";
216 }
217
218 boolean mayBeEntryInToc() {
219 return false;
220 }
221
222 boolean process(LatexMainDesc desc, LatexProcessor proc)
223 throws BuildFailureException {
224 return proc.runPythontex(desc);
225 }
226 };
227
228 // maybe this is bad performance, scanning twice
229 /**
230 * The pattern signifying
231 * the presence of <code>bibdata</code> or <code>bibstyle</code>
232 * in the top level AUX file
233 * indicating that <code>bibtex</code> must be run.
234 * It is important, that one of the two commands is sufficient:
235 * if only one is present,
236 * or if <code>citation</code> is missing
237 * (which may be in included AUX fies),
238 * then running <code>bibtex</code> yields an error.
239 * If neither <code>bibdata</code> nor <code>bibstyle</code> is present,
240 * then <code>bibtex</code> is not run.
241 * Then the latex engine yields a warning if a citation is present.
242 * So either <code>bibdata</code> and <code>bibstyle</code>
243 * and at least one citation is present,
244 * or neither, or there is an error or a warning.
245 */
246 private static final Pattern PATTERN_NEED_BIBTEX_RUN =
247 Pattern.compile("^\\\\(bibdata|bibstyle)");
248
249 /**
250 * The pattern signifying the the presence of an `istfile`,
251 * which has indeed ending <code>ist</code>
252 * for makeglossaries configured with makeindex
253 * but which is <code>xdy</code> for xindy.
254 * This detail does not affect the general pattern.
255 */
256 private static final Pattern PATTERN_NEED_MAKEGLOSSARIES_RUN =
257 Pattern.compile("^\\\\@istfilename");
258
259 // filename with .aux
260 /**
261 * The pattern for iputting another AUX file
262 * with a group with name {@link #GRP_INPUT}
263 * comprising the name of the file with ending.
264 */
265 private static final Pattern PATTERN_INPUT =
266 Pattern.compile("^\\\\@input\\{(?<fileName>.*)\\}");
267
268 /**
269 * The name of the group in pattern {@link #PATTERN_INPUT}
270 * comprising the filename with ending.
271 */
272 private static final String GRP_INPUT = "fileName";
273
274
275 /**
276 * Pattern which holds in braces
277 * the name of the glossary,
278 * the extension of the log file and of the file created by the auxiliary tool,
279 * and finally the extension of the file created by the package <code>glossaries</code>.
280 * Only the latter goes into computation of the hash.
281 * Example: <code>\@newglossary{main}{glg}{gls}{glo}</code>.
282 */
283 private static final Pattern PATTERN_INPUT_GLOSSARY =
284 Pattern.compile("\\\\@newglossary\\{.+\\}\\{.+\\}\\{.+\\}\\{(?<fileExt>.*)\\}");
285
286 /**
287 * The name of the group in pattern {@link #PATTERN_INPUT_GLOSSARY}
288 * comprising the file extension of the file
289 * created by a latex run by the package <code>glossaries</code>
290 * holding the entries of the accoding glossary.
291 */
292 private static final String GRP_EXT_GLOSS = "fileExt";
293
294 /**
295 * The pattern of the aux file
296 * read by <code>bibtex</code>.
297 * This does not include
298 * the pattern {@link #PATTERN_INPUT}
299 * for inputting other aux files.
300 * In fact only those patterns contributing to hashing.
301 */
302 static final Pattern PATTERN_BIBTEX =
303 Pattern.compile("^\\\\(citation|bibstyle|bibdata)");
304
305 /**
306 * The file extension <code>.ext</code> triggering the action.
307 * If <code>xxx.tex</code> is the latex main file,
308 * then <code>xxx.ext</code> is the triggering file.
309 * This file is created fromt the TEX file,
310 * in some cases due to some package.
311 *
312 * @return
313 * The extension (with dot) as a string.
314 */
315 // TBD: without dot would be more elegant
316 abstract String extension();
317
318 boolean doesFitAuxiliary(File file) {
319 return file.exists();
320 }
321
322 // currently true except for Pyt
323 boolean mayBeEntryInToc() {
324 return true;
325 }
326
327 // must be non-negative
328 int numRunsAfter() {
329 return 1;
330 }
331
332 /**
333 * Try to process the file associated with this auxiliary.
334 * @return
335 * whether a processor has been invoked.
336 */
337 abstract boolean process(LatexMainDesc desc, LatexProcessor proc)
338 throws BuildFailureException;
339
340 // overwritten for bibtex and one time also for bib2gls
341 /**
342 * Yields the identifier
343 * corresponding with the given text file.
344 * It takes only the lines into account
345 * which are relevant for this this {@link Auxiliary}.
346 * Note that for some {@link Auxiliary}s,
347 * typically tied to AUX files,
348 * other files referred to are taken into account
349 * as for {@link Auxiliary#BibTex}
350 *
351 * @param file
352 * a text file written by the part of the {@link Auxiliary}
353 * given by the run of the latex compiler
354 * and at least partially read by the corresponding auxiliary program.
355 * @return
356 * The file identifier taking all lines of <code>file</code> into account
357 * which are relevant for this {@link Auxiliary}.
358 * @throws IOException
359 * if the file or files referred to within it
360 * could not be read completely.
361 * This degrades rerun detection.
362 */
363 FileId getIdent(File file) throws IOException {
364 return updateIdent(file, new FileId());
365 }
366
367 FileId updateIdent(File file, FileId fileId) throws IOException {
368 //System.out.println("update:gen");
369 try (BufferedReader bufferedReader =
370 new BufferedReader(new FileReader(file))) {
371 for (String line = bufferedReader.readLine(); line != null;
372 // readLine may thr. IOException
373 line = bufferedReader.readLine()) {
374 fileId.update(line);
375 }
376 }
377 return fileId;
378 }
379
380 // invoked by BibTex.getIdent(File), Glo.getIdent(File) (strange)
381 // and by itself.
382 FileId updateIdentInclude(File file, FileId fileId, Pattern patternAux) throws IOException {
383 File parent = file.getParentFile();
384 String inFile;
385 try (BufferedReader bufferedReader =
386 new BufferedReader(new FileReader(file))) {
387 for (String line = bufferedReader.readLine(); line != null;
388 // readLine may thr. IOException
389 line = bufferedReader.readLine()) {
390 if (patternAux.matcher(line).find()) {
391 fileId.update(line);
392 continue;
393 }
394 Matcher matcher = PATTERN_INPUT.matcher(line);
395 if (matcher.find()) {
396 inFile = matcher.group(GRP_INPUT);
397 assert inFile.endsWith(this.extension());
398 // ignore return value
399 updateIdentInclude(new File(parent, inFile), fileId, patternAux);
400 }
401 } // for
402 } // try
403 return fileId;
404 }
405
406 /**
407 * Returns whether this {@link Auxiliary} fits which is assumed
408 * if the <code>pattern</code> matches any line in <code>file</code>.
409 * This is used to implement {@link #doesFitAuxiliary(File)}
410 * for auxiliaries {@link #BibTex} and {@link #Glo}.
411 *
412 * @param file
413 * the file the name of which is the according latex main file
414 * endowed with ending given by {@link #extension()}.
415 * @param pattern
416 * The pattern to be matched to lines in <code>file</code>.
417 * For {@link #BibTex}{@link #doesFitAuxiliary(File)}
418 * this method is invoked with pattern {@link #PATTERN_NEED_BIBTEX_RUN},
419 * whereas for {@link #Glo},
420 * the pattern is {@link #PATTERN_NEED_MAKEGLOSSARIES_RUN}.
421 * @return
422 * Whether <code>pattern</code> matches any line in <code>file</code>.
423 * @see #BibTex{@link #doesFitAuxiliary(File)}
424 * @see #Glo{@link #doesFitAuxiliary(File)}
425 */
426 boolean doesFitAuxiliary(File file, Pattern pattern) {
427 if (!file.exists()) {
428 return false;
429 }
430
431 try (BufferedReader bufferedReader =
432 new BufferedReader(new FileReader(file))) {
433 for (String line = bufferedReader.readLine(); line != null;
434 // readLine may thr. IOException
435 line = bufferedReader.readLine()) {
436 if (pattern.matcher(line).find()) {
437 return true;
438 }
439 } // for
440 return false;
441 } catch (IOException e) {
442 // TBD: add warning
443 return true;
444 }
445 }
446
447 }