diff --git a/src/main/java/org/jabref/logic/texparser/DefaultTexParser.java b/src/main/java/org/jabref/logic/texparser/DefaultTexParser.java new file mode 100644 index 00000000000..6b103d97243 --- /dev/null +++ b/src/main/java/org/jabref/logic/texparser/DefaultTexParser.java @@ -0,0 +1,137 @@ +package org.jabref.logic.texparser; + +import java.io.IOException; +import java.io.LineNumberReader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.jabref.model.texparser.TexParser; +import org.jabref.model.texparser.TexParserResult; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class DefaultTexParser implements TexParser { + + private static final Logger LOGGER = LoggerFactory.getLogger(DefaultTexParser.class); + + /** + * It is allowed to add new cite commands for pattern matching. + * + *

Some valid examples: "citep", "[cC]ite", "[cC]ite(author|title|year|t|p)?" + * + *

TODO: Add support for multicite commands. + */ + private static final String[] CITE_COMMANDS = { + "[cC]ite(alt|alp|author|authorfull|date|num|p|t|text|title|url|year|yearpar)?", + "([aA]|fnote|foot|footfull|full|no|[nN]ote|[pP]aren|[pP]note|[tT]ext|[sS]mart|super)cite", + "footcitetext" + }; + private static final String CITE_GROUP = "key"; + private static final Pattern CITE_PATTERN = Pattern.compile( + String.format("\\\\(%s)\\*?(?:\\[(?:[^\\]]*)\\]){0,2}\\{(?<%s>[^\\}]*)\\}", + String.join("|", CITE_COMMANDS), CITE_GROUP)); + + private static final String INCLUDE_GROUP = "file"; + private static final Pattern INCLUDE_PATTERN = Pattern.compile( + String.format("\\\\(?:include|input)\\{(?<%s>[^\\}]*)\\}", INCLUDE_GROUP)); + + private static final String TEX_EXT = ".tex"; + + private final TexParserResult result; + + public DefaultTexParser() { + this.result = new TexParserResult(); + } + + public TexParserResult getResult() { + return result; + } + + @Override + public TexParserResult parse(String citeString) { + matchCitation(Paths.get("foo/bar"), 1, citeString); + return result; + } + + @Override + public TexParserResult parse(Path texFile) { + return parse(Collections.singletonList(texFile)); + } + + @Override + public TexParserResult parse(List texFiles) { + List referencedFiles = new ArrayList<>(); + + result.addFiles(texFiles); + + for (Path file : texFiles) { + try (LineNumberReader lnr = new LineNumberReader(Files.newBufferedReader(file))) { + for (String line = lnr.readLine(); line != null; line = lnr.readLine()) { + if (line.isEmpty() || line.charAt(0) == '%') { + // Skip comments and blank lines. + continue; + } + + matchCitation(file, lnr.getLineNumber(), line); + matchNestedFile(file, texFiles, referencedFiles, line); + } + } catch (IOException e) { + LOGGER.warn("Error opening the TEX file", e); + } + } + + // Parse all files referenced by TEX files, recursively. + if (!referencedFiles.isEmpty()) { + parse(referencedFiles); + } + + return result; + } + + /** + * Find cites along a specific line and store them. + */ + private void matchCitation(Path file, int lineNumber, String line) { + Matcher citeMatch = CITE_PATTERN.matcher(line); + + while (citeMatch.find()) { + String[] keys = citeMatch.group(CITE_GROUP).split(","); + + for (String key : keys) { + result.addKey(key, file, lineNumber, citeMatch.start(), citeMatch.end(), line); + } + } + } + + /** + * Find inputs and includes along a specific line and store them for parsing later. + */ + private void matchNestedFile(Path file, List texFiles, List referencedFiles, String line) { + Matcher includeMatch = INCLUDE_PATTERN.matcher(line); + StringBuilder include; + + while (includeMatch.find()) { + include = new StringBuilder(includeMatch.group(INCLUDE_GROUP)); + + if (!include.toString().endsWith(TEX_EXT)) { + include.append(TEX_EXT); + } + + Path folder = file.getParent(); + Path inputFile = (folder == null) + ? Paths.get(include.toString()) + : folder.resolve(include.toString()); + + if (!texFiles.contains(inputFile)) { + referencedFiles.add(inputFile); + } + } + } +} diff --git a/src/main/java/org/jabref/logic/texparser/TexBibEntriesResolver.java b/src/main/java/org/jabref/logic/texparser/TexBibEntriesResolver.java new file mode 100644 index 00000000000..d32bc9c7289 --- /dev/null +++ b/src/main/java/org/jabref/logic/texparser/TexBibEntriesResolver.java @@ -0,0 +1,61 @@ +package org.jabref.logic.texparser; + +import java.util.Optional; +import java.util.Set; + +import org.jabref.model.database.BibDatabase; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.FieldName; +import org.jabref.model.texparser.TexBibEntriesResolverResult; +import org.jabref.model.texparser.TexParserResult; + +public class TexBibEntriesResolver { + + private final BibDatabase masterDatabase; + + public TexBibEntriesResolver(BibDatabase masterDatabase) { + this.masterDatabase = masterDatabase; + } + + /** + * Look for BibTeX entries within the reference database for all keys inside of the TEX files. + * Insert these data in the list of new entries. + */ + public TexBibEntriesResolverResult resolveKeys(TexParserResult texParserResult) { + TexBibEntriesResolverResult result = new TexBibEntriesResolverResult(texParserResult); + Set keySet = result.getCitationsKeySet(); + + for (String key : keySet) { + if (!result.checkEntryNewDatabase(key)) { + Optional entry = masterDatabase.getEntryByKey(key); + + if (entry.isPresent()) { + result.insertEntry(entry.get()); + resolveCrossReferences(result, entry.get()); + } else { + result.addUnresolvedKey(key); + } + } + } + + return result; + } + + /** + * Find cross references for inserting into the list of new entries. + */ + private void resolveCrossReferences(TexBibEntriesResolverResult result, BibEntry entry) { + entry.getField(FieldName.CROSSREF).ifPresent(crossRef -> { + if (!result.checkEntryNewDatabase(crossRef)) { + Optional refEntry = masterDatabase.getEntryByKey(crossRef); + + if (refEntry.isPresent()) { + result.insertEntry(refEntry.get()); + result.increaseCrossRefsCount(); + } else { + result.addUnresolvedKey(crossRef); + } + } + }); + } +} diff --git a/src/main/java/org/jabref/model/texparser/Citation.java b/src/main/java/org/jabref/model/texparser/Citation.java new file mode 100644 index 00000000000..9905b4f9174 --- /dev/null +++ b/src/main/java/org/jabref/model/texparser/Citation.java @@ -0,0 +1,113 @@ +package org.jabref.model.texparser; + +import java.nio.file.Path; +import java.util.Objects; +import java.util.StringJoiner; + +public class Citation { + + /** + * The total number of characters that are shown around a cite (cite width included). + */ + private static final int CONTEXT_WIDTH = 50; + + private final Path path; + private final int line; + private final int colStart; + private final int colEnd; + private final String lineText; + + public Citation(Path path, int line, int colStart, int colEnd, String lineText) { + if (path == null) { + throw new IllegalArgumentException("Path cannot be null."); + } + + if (line <= 0) { + throw new IllegalArgumentException("Line has to be greater than 0."); + } + + if (colStart < 0 || colEnd > lineText.length()) { + throw new IllegalArgumentException("Citation has to be between 0 and line length."); + } + + this.path = path; + this.line = line; + this.colStart = colStart; + this.colEnd = colEnd; + this.lineText = lineText; + } + + public Path getPath() { + return path; + } + + public int getLine() { + return line; + } + + public int getColStart() { + return colStart; + } + + public int getColEnd() { + return colEnd; + } + + public String getLineText() { + return lineText; + } + + public int getContextWidth() { + return CONTEXT_WIDTH; + } + + /** + * Get a fixed-width string that contains a cite and the text that surrounds it along the same line. + */ + public String getContext() { + int center = (colStart + colEnd) / 2; + int lineLength = lineText.length(); + + int start = Math.max(0, (center + CONTEXT_WIDTH / 2 < lineLength) + ? center - CONTEXT_WIDTH / 2 + : lineLength - CONTEXT_WIDTH); + int end = Math.min(lineLength, start + CONTEXT_WIDTH); + + return lineText.substring(start, end); + } + + @Override + public String toString() { + return new StringJoiner(", ", getClass().getSimpleName() + "[", "]") + .add("path = " + path) + .add("line = " + line) + .add("colStart = " + colStart) + .add("colEnd = " + colEnd) + .add("lineText = " + lineText) + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (o == null || getClass() != o.getClass()) { + return false; + } + + Citation that = (Citation) o; + + return Objects.equals(path, that.path) + && Objects.equals(line, that.line) + && Objects.equals(colStart, that.colStart) + && Objects.equals(colEnd, that.colEnd) + && Objects.equals(lineText, that.lineText); + } + + @Override + public int hashCode() { + return Objects.hash(path, line, colStart, colEnd, lineText); + } +} diff --git a/src/main/java/org/jabref/model/texparser/TexBibEntriesResolverResult.java b/src/main/java/org/jabref/model/texparser/TexBibEntriesResolverResult.java new file mode 100644 index 00000000000..cac45ad21cd --- /dev/null +++ b/src/main/java/org/jabref/model/texparser/TexBibEntriesResolverResult.java @@ -0,0 +1,125 @@ +package org.jabref.model.texparser; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.StringJoiner; + +import org.jabref.model.database.BibDatabase; +import org.jabref.model.entry.BibEntry; + +import com.google.common.collect.Multimap; + +public class TexBibEntriesResolverResult { + + private final TexParserResult texParserResult; + private final List unresolvedKeys; + private final List newEntries; + private int crossRefsCount; + + public TexBibEntriesResolverResult(TexParserResult texParserResult) { + this.texParserResult = texParserResult; + this.unresolvedKeys = new ArrayList<>(); + this.newEntries = new ArrayList<>(); + this.crossRefsCount = 0; + } + + public TexParserResult getTexParserResult() { + return texParserResult; + } + + public List getUnresolvedKeys() { + return unresolvedKeys; + } + + public List getNewEntries() { + return newEntries; + } + + public int getCrossRefsCount() { + return crossRefsCount; + } + + /** + * Return the citations multimap from the TexParserResult object. + */ + public Multimap getCitations() { + return texParserResult.getCitations(); + } + + /** + * Return a set of strings with the keys of the citations multimap from the TexParserResult object. + */ + public Set getCitationsKeySet() { + return texParserResult.getCitationsKeySet(); + } + + /** + * Add an unresolved key to the list. + */ + public void addUnresolvedKey(String key) { + unresolvedKeys.add(key); + } + + /** + * Check if an entry with the given key is present in the list of new entries. + */ + public boolean checkEntryNewDatabase(String key) { + return newEntries.stream().anyMatch(e -> e.getCiteKeyOptional().get().equals(key)); + } + + /** + * Add 1 to the cross references counter. + */ + public void increaseCrossRefsCount() { + crossRefsCount++; + } + + /** + * Insert into the list of new entries an entry with the given key. + */ + public void insertEntry(BibDatabase masterDatabase, String key) { + insertEntry(masterDatabase.getEntryByKey(key).get()); + } + + /** + * Insert into the list of new entries the given entry. + */ + public void insertEntry(BibEntry entry) { + newEntries.add(entry); + } + + @Override + public String toString() { + return new StringJoiner(", ", this.getClass().getSimpleName() + "[", "]") + .add("texParserResult = " + texParserResult) + .add("unresolvedKeys = " + unresolvedKeys) + .add("newEntries = " + newEntries) + .add("crossRefsCount = " + crossRefsCount) + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (o == null || getClass() != o.getClass()) { + return false; + } + + TexBibEntriesResolverResult that = (TexBibEntriesResolverResult) o; + + return Objects.equals(texParserResult, that.texParserResult) + && Objects.equals(unresolvedKeys, that.unresolvedKeys) + && Objects.equals(newEntries, that.newEntries) + && Objects.equals(crossRefsCount, that.crossRefsCount); + } + + @Override + public int hashCode() { + return Objects.hash(texParserResult, unresolvedKeys, newEntries, crossRefsCount); + } +} diff --git a/src/main/java/org/jabref/model/texparser/TexParser.java b/src/main/java/org/jabref/model/texparser/TexParser.java new file mode 100644 index 00000000000..45ed93b8c1e --- /dev/null +++ b/src/main/java/org/jabref/model/texparser/TexParser.java @@ -0,0 +1,31 @@ +package org.jabref.model.texparser; + +import java.nio.file.Path; +import java.util.List; + +public interface TexParser { + + /** + * For testing purposes. + * + * @param citeString String that contains a citation + * @return a TexParserResult, where Path is foo/bar and lineNumber is 1 + */ + TexParserResult parse(String citeString); + + /** + * Parse a single TEX file. + * + * @param texFile Path to a TEX file + * @return a TexParserResult, which contains all data related to the bibliographic entries + */ + TexParserResult parse(Path texFile); + + /** + * Parse a list of TEX files. + * + * @param texFiles List of Path objects linked to a TEX file + * @return a TexParserResult, which contains all data related to the bibliographic entries + */ + TexParserResult parse(List texFiles); +} diff --git a/src/main/java/org/jabref/model/texparser/TexParserResult.java b/src/main/java/org/jabref/model/texparser/TexParserResult.java new file mode 100644 index 00000000000..67dd5b0b445 --- /dev/null +++ b/src/main/java/org/jabref/model/texparser/TexParserResult.java @@ -0,0 +1,93 @@ +package org.jabref.model.texparser; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.StringJoiner; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; + +public class TexParserResult { + + private final List fileList; + private final List nestedFiles; + private final Multimap citations; + + public TexParserResult() { + this.fileList = new ArrayList<>(); + this.nestedFiles = new ArrayList<>(); + this.citations = HashMultimap.create(); + } + + public List getFileList() { + return fileList; + } + + public List getNestedFiles() { + return nestedFiles; + } + + public Multimap getCitations() { + return citations; + } + + /** + * Return a set of strings with the keys of the citations multimap. + */ + public Set getCitationsKeySet() { + return citations.keySet(); + } + + /** + * Add a list of files to fileList or nestedFiles, depending on whether this is the first list. + */ + public void addFiles(List texFiles) { + if (fileList.isEmpty()) { + fileList.addAll(texFiles); + } else { + nestedFiles.addAll(texFiles); + } + } + + /** + * Add a citation to the citations multimap. + */ + public void addKey(String key, Path file, int lineNumber, int start, int end, String line) { + Citation citation = new Citation(file, lineNumber, start, end, line); + citations.put(key, citation); + } + + @Override + public String toString() { + return new StringJoiner(", ", getClass().getSimpleName() + "[", "]") + .add("fileList = " + fileList) + .add("nestedFiles = " + nestedFiles) + .add("citations = " + citations) + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (o == null || getClass() != o.getClass()) { + return false; + } + + TexParserResult that = (TexParserResult) o; + + return Objects.equals(fileList, that.fileList) + && Objects.equals(nestedFiles, that.nestedFiles) + && Objects.equals(citations, that.citations); + } + + @Override + public int hashCode() { + return Objects.hash(fileList, nestedFiles, citations); + } +} diff --git a/src/test/java/org/jabref/logic/texparser/DefaultTexParserTest.java b/src/test/java/org/jabref/logic/texparser/DefaultTexParserTest.java new file mode 100644 index 00000000000..961fd9339b0 --- /dev/null +++ b/src/test/java/org/jabref/logic/texparser/DefaultTexParserTest.java @@ -0,0 +1,165 @@ +package org.jabref.logic.texparser; + +import java.net.URISyntaxException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; + +import org.jabref.model.texparser.TexParserResult; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class DefaultTexParserTest { + private final static String DARWIN = "Darwin1888"; + private final static String EINSTEIN = "Einstein1920"; + private final static String NEWTON = "Newton1999"; + private final static String EINSTEIN_A = "Einstein1920a"; + private final static String EINSTEIN_B = "Einstein1920b"; + private final static String EINSTEIN_C = "Einstein1920c"; + private final static String EINSTEIN_21 = "Einstein1921"; + private final static String UNRESOLVED = "UnresolvedKey"; + private final static String UNKNOWN = "UnknownKey"; + + private void testMatchCite(String key, String citeString) { + TexParserResult texParserResult = new DefaultTexParser().parse(citeString); + TexParserResult expectedParserResult = new TexParserResult(); + + expectedParserResult.addKey(key, Paths.get("foo/bar"), 1, 0, citeString.length(), citeString); + + assertEquals(expectedParserResult, texParserResult); + } + + private void testNonMatchCite(String citeString) { + TexParserResult texParserResult = new DefaultTexParser().parse(citeString); + TexParserResult expectedParserResult = new TexParserResult(); + + assertEquals(expectedParserResult, texParserResult); + } + + @Test + public void testCiteCommands() { + testMatchCite(UNRESOLVED, "\\cite[pre][post]{UnresolvedKey}"); + testMatchCite(UNRESOLVED, "\\cite*{UnresolvedKey}"); + testMatchCite(UNRESOLVED, "\\parencite[post]{UnresolvedKey}"); + testMatchCite(UNRESOLVED, "\\cite[pre][post]{UnresolvedKey}"); + testMatchCite(EINSTEIN_C, "\\citep{Einstein1920c}"); + + testNonMatchCite("\\citet21312{123U123n123resolvedKey}"); + testNonMatchCite("\\1cite[pr234e][post]{UnresolvedKey}"); + testNonMatchCite("\\citep55{5}UnresolvedKey}"); + testNonMatchCite("\\cit2et{UnresolvedKey}"); + } + + @Test + public void testTwoCitationsSameLine() { + String citeString = "\\citep{Einstein1920c} and \\citep{Einstein1920a}"; + + TexParserResult texParserResult = new DefaultTexParser().parse(citeString); + TexParserResult expectedParserResult = new TexParserResult(); + + expectedParserResult.addKey(EINSTEIN_C, Paths.get("foo/bar"), 1, 0, 21, citeString); + expectedParserResult.addKey(EINSTEIN_A, Paths.get("foo/bar"), 1, 26, 47, citeString); + + assertEquals(expectedParserResult, texParserResult); + } + + @Test + public void testSingleFile() throws URISyntaxException { + Path texFile = Paths.get(DefaultTexParserTest.class.getResource("paper.tex").toURI()); + + TexParserResult parserResult = new DefaultTexParser().parse(texFile); + TexParserResult expectedParserResult = new TexParserResult(); + + expectedParserResult.getFileList().add(texFile); + expectedParserResult.addKey(EINSTEIN, texFile, 4, 0, 19, "\\cite{Einstein1920}"); + expectedParserResult.addKey(DARWIN, texFile, 5, 0, 17, "\\cite{Darwin1888}."); + expectedParserResult.addKey(EINSTEIN, texFile, 6, 14, 33, "Einstein said \\cite{Einstein1920} that lorem impsum, consectetur adipiscing elit."); + expectedParserResult.addKey(DARWIN, texFile, 7, 67, 84, "Nunc ultricies leo nec libero rhoncus, eu vehicula enim efficitur. \\cite{Darwin1888}"); + + assertEquals(expectedParserResult, parserResult); + } + + @Test + public void testTwoFiles() throws URISyntaxException { + Path texFile = Paths.get(DefaultTexParserTest.class.getResource("paper.tex").toURI()); + Path texFile2 = Paths.get(DefaultTexParserTest.class.getResource("paper2.tex").toURI()); + + TexParserResult parserResult = new DefaultTexParser().parse(Arrays.asList(texFile, texFile2)); + TexParserResult expectedParserResult = new TexParserResult(); + + expectedParserResult.getFileList().addAll(Arrays.asList(texFile, texFile2)); + expectedParserResult.addKey(EINSTEIN, texFile, 4, 0, 19, "\\cite{Einstein1920}"); + expectedParserResult.addKey(DARWIN, texFile, 5, 0, 17, "\\cite{Darwin1888}."); + expectedParserResult.addKey(EINSTEIN, texFile, 6, 14, 33, "Einstein said \\cite{Einstein1920} that lorem impsum, consectetur adipiscing elit."); + expectedParserResult.addKey(DARWIN, texFile, 7, 67, 84, "Nunc ultricies leo nec libero rhoncus, eu vehicula enim efficitur. \\cite{Darwin1888}"); + expectedParserResult.addKey(DARWIN, texFile2, 4, 48, 65, "This is some content trying to cite a bib file: \\cite{Darwin1888}"); + expectedParserResult.addKey(EINSTEIN, texFile2, 5, 48, 67, "This is some content trying to cite a bib file: \\cite{Einstein1920}"); + expectedParserResult.addKey(NEWTON, texFile2, 6, 48, 65, "This is some content trying to cite a bib file: \\cite{Newton1999}"); + + assertEquals(expectedParserResult, parserResult); + } + + @Test + public void testDuplicateFiles() throws URISyntaxException { + Path texFile = Paths.get(DefaultTexParserTest.class.getResource("paper.tex").toURI()); + + TexParserResult parserResult = new DefaultTexParser().parse(Arrays.asList(texFile, texFile)); + TexParserResult expectedParserResult = new TexParserResult(); + + expectedParserResult.getFileList().addAll(Arrays.asList(texFile, texFile)); + expectedParserResult.addKey(EINSTEIN, texFile, 4, 0, 19, "\\cite{Einstein1920}"); + expectedParserResult.addKey(DARWIN, texFile, 5, 0, 17, "\\cite{Darwin1888}."); + expectedParserResult.addKey(EINSTEIN, texFile, 6, 14, 33, "Einstein said \\cite{Einstein1920} that lorem impsum, consectetur adipiscing elit."); + expectedParserResult.addKey(DARWIN, texFile, 7, 67, 84, "Nunc ultricies leo nec libero rhoncus, eu vehicula enim efficitur. \\cite{Darwin1888}"); + + assertEquals(expectedParserResult, parserResult); + } + + @Test + public void testUnknownKey() throws URISyntaxException { + Path texFile = Paths.get(DefaultTexParserTest.class.getResource("unknown_key.tex").toURI()); + + TexParserResult parserResult = new DefaultTexParser().parse(texFile); + TexParserResult expectedParserResult = new TexParserResult(); + + expectedParserResult.getFileList().add(texFile); + expectedParserResult.addKey(DARWIN, texFile, 4, 48, 65, "This is some content trying to cite a bib file: \\cite{Darwin1888}"); + expectedParserResult.addKey(EINSTEIN, texFile, 5, 48, 67, "This is some content trying to cite a bib file: \\cite{Einstein1920}"); + expectedParserResult.addKey(UNKNOWN, texFile, 6, 48, 65, "This is some content trying to cite a bib file: \\cite{UnknownKey}"); + + assertEquals(expectedParserResult, parserResult); + } + + @Test + public void testFileNotFound() { + Path texFile = Paths.get("file_not_found.tex"); + + TexParserResult parserResult = new DefaultTexParser().parse(texFile); + TexParserResult expectedParserResult = new TexParserResult(); + + expectedParserResult.getFileList().add(texFile); + + assertEquals(expectedParserResult, parserResult); + } + + @Test + public void testNestedFiles() throws URISyntaxException { + Path texFile = Paths.get(DefaultTexParserTest.class.getResource("nested.tex").toURI()); + Path texFile2 = Paths.get(DefaultTexParserTest.class.getResource("nested2.tex").toURI()); + Path texFile3 = Paths.get(DefaultTexParserTest.class.getResource("paper.tex").toURI()); + + TexParserResult parserResult = new DefaultTexParser().parse(texFile); + TexParserResult expectedParserResult = new TexParserResult(); + + expectedParserResult.getFileList().add(texFile); + expectedParserResult.getNestedFiles().addAll(Arrays.asList(texFile2, texFile3)); + expectedParserResult.addKey(EINSTEIN, texFile3, 4, 0, 19, "\\cite{Einstein1920}"); + expectedParserResult.addKey(DARWIN, texFile3, 5, 0, 17, "\\cite{Darwin1888}."); + expectedParserResult.addKey(EINSTEIN, texFile3, 6, 14, 33, "Einstein said \\cite{Einstein1920} that lorem impsum, consectetur adipiscing elit."); + expectedParserResult.addKey(DARWIN, texFile3, 7, 67, 84, "Nunc ultricies leo nec libero rhoncus, eu vehicula enim efficitur. \\cite{Darwin1888}"); + + assertEquals(expectedParserResult, parserResult); + } +} diff --git a/src/test/java/org/jabref/logic/texparser/TexBibEntriesResolverTest.java b/src/test/java/org/jabref/logic/texparser/TexBibEntriesResolverTest.java new file mode 100644 index 00000000000..3d33d7639bb --- /dev/null +++ b/src/test/java/org/jabref/logic/texparser/TexBibEntriesResolverTest.java @@ -0,0 +1,170 @@ +package org.jabref.logic.texparser; + +import java.net.URISyntaxException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; + +import org.jabref.model.database.BibDatabase; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.BibtexEntryTypes; +import org.jabref.model.texparser.TexBibEntriesResolverResult; +import org.jabref.model.texparser.TexParserResult; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class TexBibEntriesResolverTest { + private final static String DARWIN = "Darwin1888"; + private final static String EINSTEIN = "Einstein1920"; + private final static String NEWTON = "Newton1999"; + private final static String EINSTEIN_A = "Einstein1920a"; + private final static String EINSTEIN_B = "Einstein1920b"; + private final static String EINSTEIN_C = "Einstein1920c"; + private final static String EINSTEIN_21 = "Einstein1921"; + private final static String UNRESOLVED = "UnresolvedKey"; + private final static String UNKNOWN = "UnknownKey"; + + private static BibDatabase database; + + @BeforeEach + private void setUp() { + database = new BibDatabase(); + + BibEntry darwin = new BibEntry(BibtexEntryTypes.BOOK) + .withField("bibtexkey", DARWIN) + .withField("title", "The descent of man, and selection in relation to sex") + .withField("publisher", "J. Murray") + .withField("year", "1888") + .withField("author", "Darwin, Charles"); + database.insertEntry(darwin); + + BibEntry einstein = new BibEntry(BibtexEntryTypes.BOOK) + .withField("bibtexkey", EINSTEIN) + .withField("title", "Relativity: The special and general theory") + .withField("publisher", "Penguin") + .withField("year", "1920") + .withField("author", "Einstein, Albert"); + database.insertEntry(einstein); + + BibEntry newton = new BibEntry(BibtexEntryTypes.BOOK) + .withField("bibtexkey", NEWTON) + .withField("title", "The Principia: mathematical principles of natural philosophy") + .withField("publisher", "Univ of California Press") + .withField("year", "1999") + .withField("author", "Newton, Isaac"); + database.insertEntry(newton); + + BibEntry einsteinA = new BibEntry(BibtexEntryTypes.BOOK) + .withField("bibtexkey", EINSTEIN_A) + .withField("crossref", "Einstein1920") + .withField("pages", "22--23"); + database.insertEntry(einsteinA); + + BibEntry einsteinB = new BibEntry(BibtexEntryTypes.BOOK) + .withField("bibtexkey", EINSTEIN_B) + .withField("crossref", "Einstein1921") + .withField("pages", "22--23"); + database.insertEntry(einsteinB); + + BibEntry einsteinC = new BibEntry(BibtexEntryTypes.BOOK) + .withField("bibtexkey", EINSTEIN_C) + .withField("crossref", "Einstein1920") + .withField("pages", "25--33"); + database.insertEntry(einsteinC); + } + + @Test + public void testSingleFile() throws URISyntaxException { + Path texFile = Paths.get(TexBibEntriesResolverTest.class.getResource("paper.tex").toURI()); + TexParserResult parserResult = new DefaultTexParser().parse(texFile); + + TexBibEntriesResolverResult crossingResult = new TexBibEntriesResolver(database).resolveKeys(parserResult); + TexBibEntriesResolverResult expectedCrossingResult = new TexBibEntriesResolverResult(parserResult); + + expectedCrossingResult.insertEntry(database, DARWIN); + expectedCrossingResult.insertEntry(database, EINSTEIN); + + assertEquals(expectedCrossingResult, crossingResult); + } + + @Test + public void testTwoFiles() throws URISyntaxException { + Path texFile = Paths.get(TexBibEntriesResolverTest.class.getResource("paper.tex").toURI()); + Path texFile2 = Paths.get(TexBibEntriesResolverTest.class.getResource("paper2.tex").toURI()); + TexParserResult parserResult = new DefaultTexParser().parse(Arrays.asList(texFile, texFile2)); + + TexBibEntriesResolverResult crossingResult = new TexBibEntriesResolver(database).resolveKeys(parserResult); + TexBibEntriesResolverResult expectedCrossingResult = new TexBibEntriesResolverResult(parserResult); + + expectedCrossingResult.insertEntry(database, DARWIN); + expectedCrossingResult.insertEntry(database, EINSTEIN); + expectedCrossingResult.insertEntry(database, NEWTON); + + assertEquals(expectedCrossingResult, crossingResult); + } + + @Test + public void testDuplicateFiles() throws URISyntaxException { + Path texFile = Paths.get(TexBibEntriesResolverTest.class.getResource("paper.tex").toURI()); + TexParserResult parserResult = new DefaultTexParser().parse(texFile); + + TexBibEntriesResolverResult crossingResult = new TexBibEntriesResolver(database).resolveKeys(parserResult); + TexBibEntriesResolverResult expectedCrossingResult = new TexBibEntriesResolverResult(parserResult); + + expectedCrossingResult.insertEntry(database, DARWIN); + expectedCrossingResult.insertEntry(database, EINSTEIN); + + assertEquals(expectedCrossingResult, crossingResult); + } + + @Test + public void testUnknownKey() throws URISyntaxException { + Path texFile = Paths.get(TexBibEntriesResolverTest.class.getResource("unknown_key.tex").toURI()); + TexParserResult parserResult = new DefaultTexParser().parse(texFile); + + TexBibEntriesResolverResult crossingResult = new TexBibEntriesResolver(database).resolveKeys(parserResult); + TexBibEntriesResolverResult expectedCrossingResult = new TexBibEntriesResolverResult(parserResult); + + expectedCrossingResult.insertEntry(database, DARWIN); + expectedCrossingResult.insertEntry(database, EINSTEIN); + expectedCrossingResult.addUnresolvedKey(UNKNOWN); + + assertEquals(expectedCrossingResult, crossingResult); + } + + @Test + public void testNestedFiles() throws URISyntaxException { + Path texFile = Paths.get(TexBibEntriesResolverTest.class.getResource("nested.tex").toURI()); + TexParserResult parserResult = new DefaultTexParser().parse(texFile); + + TexBibEntriesResolverResult crossingResult = new TexBibEntriesResolver(database).resolveKeys(parserResult); + TexBibEntriesResolverResult expectedCrossingResult = new TexBibEntriesResolverResult(parserResult); + + expectedCrossingResult.insertEntry(database, DARWIN); + expectedCrossingResult.insertEntry(database, EINSTEIN); + + assertEquals(expectedCrossingResult, crossingResult); + } + + @Test + public void testCrossRef() throws URISyntaxException { + Path texFile = Paths.get(TexBibEntriesResolverTest.class.getResource("crossref.tex").toURI()); + TexParserResult parserResult = new DefaultTexParser().parse(texFile); + + TexBibEntriesResolverResult crossingResult = new TexBibEntriesResolver(database).resolveKeys(parserResult); + TexBibEntriesResolverResult expectedCrossingResult = new TexBibEntriesResolverResult(parserResult); + + expectedCrossingResult.insertEntry(database, EINSTEIN_B); + expectedCrossingResult.insertEntry(database, EINSTEIN_A); + expectedCrossingResult.insertEntry(database, EINSTEIN); + expectedCrossingResult.insertEntry(database, EINSTEIN_C); + expectedCrossingResult.addUnresolvedKey(EINSTEIN_21); + expectedCrossingResult.addUnresolvedKey(UNRESOLVED); + expectedCrossingResult.increaseCrossRefsCount(); + + assertEquals(expectedCrossingResult, crossingResult); + } +} diff --git a/src/test/java/org/jabref/logic/texparser/TexParserTest.java b/src/test/java/org/jabref/logic/texparser/TexParserTest.java new file mode 100644 index 00000000000..92ffd08988c --- /dev/null +++ b/src/test/java/org/jabref/logic/texparser/TexParserTest.java @@ -0,0 +1,147 @@ +package org.jabref.logic.texparser; + +import java.net.URISyntaxException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; + +import org.jabref.model.database.BibDatabase; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.BibtexEntryTypes; +import org.jabref.model.texparser.TexBibEntriesResolverResult; +import org.jabref.model.texparser.TexParserResult; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class TexParserTest { + private final static String DARWIN = "Darwin1888"; + private final static String EINSTEIN = "Einstein1920"; + private final static String NEWTON = "Newton1999"; + private final static String EINSTEIN_A = "Einstein1920a"; + private final static String EINSTEIN_B = "Einstein1920b"; + private final static String EINSTEIN_C = "Einstein1920c"; + private final static String EINSTEIN_21 = "Einstein1921"; + private final static String UNRESOLVED = "UnresolvedKey"; + private final static String UNKNOWN = "UnknownKey"; + + private static BibDatabase database; + private static BibDatabase database2; + + @BeforeEach + private void setUp() { + database = new BibDatabase(); + database2 = new BibDatabase(); + + BibEntry darwin = new BibEntry(BibtexEntryTypes.BOOK) + .withField("bibtexkey", DARWIN) + .withField("title", "The descent of man, and selection in relation to sex") + .withField("publisher", "J. Murray") + .withField("year", "1888") + .withField("author", "Darwin, Charles"); + database.insertEntry(darwin); + + BibEntry einstein = new BibEntry(BibtexEntryTypes.BOOK) + .withField("bibtexkey", EINSTEIN) + .withField("title", "Relativity: The special and general theory") + .withField("publisher", "Penguin") + .withField("year", "1920") + .withField("author", "Einstein, Albert"); + database.insertEntry(einstein); + database2.insertEntry(einstein); + + BibEntry newton = new BibEntry(BibtexEntryTypes.BOOK) + .withField("bibtexkey", NEWTON) + .withField("title", "The Principia: mathematical principles of natural philosophy") + .withField("publisher", "Univ of California Press") + .withField("year", "1999") + .withField("author", "Newton, Isaac"); + database.insertEntry(newton); + + BibEntry einsteinA = new BibEntry(BibtexEntryTypes.BOOK) + .withField("bibtexkey", EINSTEIN_A) + .withField("crossref", "Einstein1920") + .withField("pages", "22--23"); + database.insertEntry(einsteinA); + + BibEntry einsteinB = new BibEntry(BibtexEntryTypes.BOOK) + .withField("bibtexkey", EINSTEIN_B) + .withField("crossref", "Einstein1921") + .withField("pages", "22--23"); + database.insertEntry(einsteinB); + + BibEntry einsteinC = new BibEntry(BibtexEntryTypes.BOOK) + .withField("bibtexkey", EINSTEIN_C) + .withField("crossref", "Einstein1920") + .withField("pages", "25--33"); + database.insertEntry(einsteinC); + } + + @Test + public void testSameFileDifferentDatabases() throws URISyntaxException { + Path texFile = Paths.get(TexParserTest.class.getResource("paper.tex").toURI()); + + TexParserResult parserResult = new DefaultTexParser().parse(texFile); + TexParserResult expectedParserResult = new TexParserResult(); + + expectedParserResult.getFileList().add(texFile); + expectedParserResult.addKey(EINSTEIN, texFile, 4, 0, 19, "\\cite{Einstein1920}"); + expectedParserResult.addKey(DARWIN, texFile, 5, 0, 17, "\\cite{Darwin1888}."); + expectedParserResult.addKey(EINSTEIN, texFile, 6, 14, 33, "Einstein said \\cite{Einstein1920} that lorem impsum, consectetur adipiscing elit."); + expectedParserResult.addKey(DARWIN, texFile, 7, 67, 84, "Nunc ultricies leo nec libero rhoncus, eu vehicula enim efficitur. \\cite{Darwin1888}"); + + TexBibEntriesResolverResult crossingResult = new TexBibEntriesResolver(database).resolveKeys(parserResult); + TexBibEntriesResolverResult expectedCrossingResult = new TexBibEntriesResolverResult(expectedParserResult); + + expectedCrossingResult.insertEntry(database, DARWIN); + expectedCrossingResult.insertEntry(database, EINSTEIN); + + assertEquals(expectedCrossingResult, crossingResult); + + TexBibEntriesResolverResult crossingResult2 = new TexBibEntriesResolver(database2).resolveKeys(parserResult); + TexBibEntriesResolverResult expectedCrossingResult2 = new TexBibEntriesResolverResult(expectedParserResult); + + expectedCrossingResult2.insertEntry(database2, EINSTEIN); + expectedCrossingResult2.addUnresolvedKey(DARWIN); + + assertEquals(expectedCrossingResult2, crossingResult2); + } + + @Test + public void testTwoFilesDifferentDatabases() throws URISyntaxException { + Path texFile = Paths.get(TexParserTest.class.getResource("paper.tex").toURI()); + Path texFile2 = Paths.get(TexParserTest.class.getResource("paper2.tex").toURI()); + + TexParserResult parserResult = new DefaultTexParser().parse(Arrays.asList(texFile, texFile2)); + TexParserResult expectedParserResult = new TexParserResult(); + + expectedParserResult.getFileList().addAll(Arrays.asList(texFile, texFile2)); + expectedParserResult.addKey(EINSTEIN, texFile, 4, 0, 19, "\\cite{Einstein1920}"); + expectedParserResult.addKey(DARWIN, texFile, 5, 0, 17, "\\cite{Darwin1888}."); + expectedParserResult.addKey(EINSTEIN, texFile, 6, 14, 33, "Einstein said \\cite{Einstein1920} that lorem impsum, consectetur adipiscing elit."); + expectedParserResult.addKey(DARWIN, texFile, 7, 67, 84, "Nunc ultricies leo nec libero rhoncus, eu vehicula enim efficitur. \\cite{Darwin1888}"); + expectedParserResult.addKey(DARWIN, texFile2, 4, 48, 65, "This is some content trying to cite a bib file: \\cite{Darwin1888}"); + expectedParserResult.addKey(EINSTEIN, texFile2, 5, 48, 67, "This is some content trying to cite a bib file: \\cite{Einstein1920}"); + expectedParserResult.addKey(NEWTON, texFile2, 6, 48, 65, "This is some content trying to cite a bib file: \\cite{Newton1999}"); + + TexBibEntriesResolverResult crossingResult = new TexBibEntriesResolver(database).resolveKeys(parserResult); + TexBibEntriesResolverResult expectedCrossingResult = new TexBibEntriesResolverResult(expectedParserResult); + + expectedCrossingResult.insertEntry(database, DARWIN); + expectedCrossingResult.insertEntry(database, EINSTEIN); + expectedCrossingResult.insertEntry(database, NEWTON); + + assertEquals(expectedCrossingResult, crossingResult); + + TexBibEntriesResolverResult crossingResult2 = new TexBibEntriesResolver(database2).resolveKeys(parserResult); + TexBibEntriesResolverResult expectedCrossingResult2 = new TexBibEntriesResolverResult(expectedParserResult); + + expectedCrossingResult2.insertEntry(database2, EINSTEIN); + expectedCrossingResult2.addUnresolvedKey(DARWIN); + expectedCrossingResult2.addUnresolvedKey(NEWTON); + + assertEquals(expectedCrossingResult2, crossingResult2); + } +} diff --git a/src/test/resources/org/jabref/logic/texparser/crossref.tex b/src/test/resources/org/jabref/logic/texparser/crossref.tex new file mode 100644 index 00000000000..4a2809c0d16 --- /dev/null +++ b/src/test/resources/org/jabref/logic/texparser/crossref.tex @@ -0,0 +1,11 @@ +\documentclass{article} + +\begin{document} +This is some content trying to cite a bib file: \cite{Einstein1920a} +This is some content trying to cite a bib file: \cite{Einstein1920b} +This is some content trying to cite a bib file: \cite{Einstein1920c} +This is some content trying to cite a bib file: \cite{UnresolvedKey} + +\bibliographystyle{plain} +\bibliography{origin} +\end{document} diff --git a/src/test/resources/org/jabref/logic/texparser/nested.tex b/src/test/resources/org/jabref/logic/texparser/nested.tex new file mode 100644 index 00000000000..e43fa1eaf14 --- /dev/null +++ b/src/test/resources/org/jabref/logic/texparser/nested.tex @@ -0,0 +1,9 @@ +\documentclass{article} +\begin{document} + +This is some content trying to cite a bib file. +\include{nested2} + +\bibliographystyle{plain} +\bibliography{origin} +\end{document} diff --git a/src/test/resources/org/jabref/logic/texparser/nested2.tex b/src/test/resources/org/jabref/logic/texparser/nested2.tex new file mode 100644 index 00000000000..84e07c2ca67 --- /dev/null +++ b/src/test/resources/org/jabref/logic/texparser/nested2.tex @@ -0,0 +1,9 @@ +\documentclass{article} +\begin{document} + +This is some content trying to cite a bib file. +\input{paper} + +\bibliographystyle{plain} +\bibliography{origin} +\end{document} diff --git a/src/test/resources/org/jabref/logic/texparser/paper.tex b/src/test/resources/org/jabref/logic/texparser/paper.tex new file mode 100644 index 00000000000..5a8afd4ab0e --- /dev/null +++ b/src/test/resources/org/jabref/logic/texparser/paper.tex @@ -0,0 +1,13 @@ +\documentclass{article} +\begin{document} + +\cite{Einstein1920} +\cite{Darwin1888}. +Einstein said \cite{Einstein1920} that lorem impsum, consectetur adipiscing elit. +Nunc ultricies leo nec libero rhoncus, eu vehicula enim efficitur. \cite{Darwin1888} + +% Comment with \cite{Darwin1888} + +\bibliographystyle{plain} +\bibliography{origin} +\end{document} diff --git a/src/test/resources/org/jabref/logic/texparser/paper2.tex b/src/test/resources/org/jabref/logic/texparser/paper2.tex new file mode 100644 index 00000000000..6e3f81fee3c --- /dev/null +++ b/src/test/resources/org/jabref/logic/texparser/paper2.tex @@ -0,0 +1,10 @@ +\documentclass{article} +\begin{document} + +This is some content trying to cite a bib file: \cite{Darwin1888} +This is some content trying to cite a bib file: \cite{Einstein1920} +This is some content trying to cite a bib file: \cite{Newton1999} + +\bibliographystyle{plain} +\bibliography{origin} +\end{document} diff --git a/src/test/resources/org/jabref/logic/texparser/unknown_key.tex b/src/test/resources/org/jabref/logic/texparser/unknown_key.tex new file mode 100644 index 00000000000..3543179b4c4 --- /dev/null +++ b/src/test/resources/org/jabref/logic/texparser/unknown_key.tex @@ -0,0 +1,10 @@ +\documentclass{article} +\begin{document} + +This is some content trying to cite a bib file: \cite{Darwin1888} +This is some content trying to cite a bib file: \cite{Einstein1920} +This is some content trying to cite a bib file: \cite{UnknownKey} + +\bibliographystyle{plain} +\bibliography{origin} +\end{document}