Skip to content

Commit

Permalink
Merge pull request #5011 from davidemdot/latex-integration-project
Browse files Browse the repository at this point in the history
[WIP] LaTeX Integration project
  • Loading branch information
tobiasdiez authored Jun 28, 2019
2 parents 8100878 + 2037295 commit de20a67
Show file tree
Hide file tree
Showing 15 changed files with 1,104 additions and 0 deletions.
137 changes: 137 additions & 0 deletions src/main/java/org/jabref/logic/texparser/DefaultTexParser.java
Original file line number Diff line number Diff line change
@@ -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.
*
* <p>Some valid examples: "citep", "[cC]ite", "[cC]ite(author|title|year|t|p)?"
*
* <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<Path> texFiles) {
List<Path> 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<Path> texFiles, List<Path> 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);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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<String> keySet = result.getCitationsKeySet();

for (String key : keySet) {
if (!result.checkEntryNewDatabase(key)) {
Optional<BibEntry> 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<BibEntry> refEntry = masterDatabase.getEntryByKey(crossRef);

if (refEntry.isPresent()) {
result.insertEntry(refEntry.get());
result.increaseCrossRefsCount();
} else {
result.addUnresolvedKey(crossRef);
}
}
});
}
}
113 changes: 113 additions & 0 deletions src/main/java/org/jabref/model/texparser/Citation.java
Original file line number Diff line number Diff line change
@@ -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);
}
}
Loading

0 comments on commit de20a67

Please sign in to comment.