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}