package org.jabref.logic.auxparser; import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.jabref.model.database.BibDatabase; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.FieldName; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * LaTeX Aux to BibTeX Parser * <p> * Extracts a subset of BibTeX entries from a BibDatabase that are included in an AUX file. * Also supports nested AUX files (latex \\include). * * There exists no specification of the AUX file. * Every package, class or document can write to the AUX file. * The AUX file consists of LaTeX macros and is read at the \begin{document} and again at the \end{document}. * * BibTeX citation: \citation{x,y,z} * Biblatex citation: \abx@aux@cite{x,y,z} * Nested AUX files: \@input{x} */ public class AuxParser { private static final Log LOGGER = LogFactory.getLog(AuxParser.class); private static final Pattern CITE_PATTERN = Pattern.compile("\\\\(citation|abx@aux@cite)\\{(.+)\\}"); private static final Pattern INPUT_PATTERN = Pattern.compile("\\\\@input\\{(.+)\\}"); private final String auxFile; private final BibDatabase masterDatabase; /** * Generates a database based on the given AUX file and BibTeX database * * @param auxFile Path to the LaTeX AUX file * @param database BibTeX database */ public AuxParser(String auxFile, BibDatabase database) { this.auxFile = auxFile; masterDatabase = database; } /** * Executes the parsing logic and returns a result containing all information and the generated BibDatabase. * * @return an AuxParserResult containing the generated BibDatabase and parsing statistics */ public AuxParserResult parse() { return parseAuxFile(); } private AuxParserResult parseAuxFile() { AuxParserResult result = new AuxParserResult(masterDatabase); // nested AUX files List<String> fileList = new ArrayList<>(1); fileList.add(auxFile); int fileIndex = 0; while (fileIndex < fileList.size()) { String file = fileList.get(fileIndex); try (BufferedReader br = new BufferedReader(new FileReader(file))) { String line; while ((line = br.readLine()) != null) { matchCitation(result, line); matchNestedAux(result, fileList, line); } } catch (FileNotFoundException e) { LOGGER.info("Cannot locate input file", e); } catch (IOException e) { LOGGER.warn("Problem opening file", e); } fileIndex++; } resolveTags(result); return result; } private void matchNestedAux(AuxParserResult result, List<String> fileList, String line) { Matcher inputMatch = INPUT_PATTERN.matcher(line); while (inputMatch.find()) { String inputString = inputMatch.group(1); String inputFile = inputString; Path rootPath = new File(auxFile).toPath().getParent(); if (rootPath != null) { inputFile = rootPath.resolve(inputString).toString(); } if (!fileList.contains(inputFile)) { fileList.add(inputFile); result.increaseNestedAuxFilesCounter(); } } } private void matchCitation(AuxParserResult result, String line) { Matcher citeMatch = CITE_PATTERN.matcher(line); while (citeMatch.find()) { String keyString = citeMatch.group(2); String[] keys = keyString.split(","); for (String key : keys) { result.getUniqueKeys().add(key.trim()); } } } /* * Try to find an equivalent BibTeX entry inside the reference database for all keys inside the AUX file. */ private void resolveTags(AuxParserResult result) { for (String key : result.getUniqueKeys()) { Optional<BibEntry> entry = masterDatabase.getEntryByKey(key); if (result.getGeneratedBibDatabase().getEntryByKey(key).isPresent()) { // do nothing, key has already been processed } else if (entry.isPresent()) { insertEntry(entry.get(), result); resolveCrossReferences(entry.get(), result); } else { result.getUnresolvedKeys().add(key); } } // Copy database definitions if (result.getGeneratedBibDatabase().hasEntries()) { result.getGeneratedBibDatabase().copyPreamble(masterDatabase); result.insertStrings(masterDatabase.getUsedStrings(result.getGeneratedBibDatabase().getEntries())); } } /* * Resolves and adds CrossRef entries */ private void resolveCrossReferences(BibEntry entry, AuxParserResult result) { entry.getField(FieldName.CROSSREF).ifPresent(crossref -> { if (!result.getGeneratedBibDatabase().getEntryByKey(crossref).isPresent()) { Optional<BibEntry> refEntry = masterDatabase.getEntryByKey(crossref); if (refEntry.isPresent()) { insertEntry(refEntry.get(), result); result.increaseCrossRefEntriesCounter(); } else { result.getUnresolvedKeys().add(crossref); } } }); } /* * Insert a clone of the given entry. The clone is given a new unique ID. */ private void insertEntry(BibEntry entry, AuxParserResult result) { BibEntry clonedEntry = (BibEntry) entry.clone(); result.getGeneratedBibDatabase().insertEntry(clonedEntry); } }