Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion jabkit/src/main/java/org/jabref/cli/ArgumentProcessor.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
// sorted alphabetically
subcommands = {
CheckConsistency.class,
// CheckIntegrity.class,
CheckIntegrity.class,
Convert.class,
Fetch.class,
GenerateBibFromAux.class,
Expand Down
112 changes: 100 additions & 12 deletions jabkit/src/main/java/org/jabref/cli/CheckIntegrity.java
Original file line number Diff line number Diff line change
@@ -1,37 +1,125 @@
package org.jabref.cli;

import java.nio.file.Path;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.concurrent.Callable;

import org.jabref.cli.converter.CygWinPathConverter;
import org.jabref.logic.importer.ParserResult;
import org.jabref.logic.integrity.IntegrityCheck;
import org.jabref.logic.integrity.IntegrityMessage;
import org.jabref.logic.journals.JournalAbbreviationLoader;
import org.jabref.logic.l10n.Localization;
import org.jabref.model.database.BibDatabaseContext;
import org.jabref.model.entry.BibEntry;

import picocli.CommandLine;

import static picocli.CommandLine.Command;
import static picocli.CommandLine.Mixin;
import static picocli.CommandLine.Option;
import static picocli.CommandLine.Parameters;

@Command(name = "check-integrity", description = "Check integrity of the database.")
class CheckIntegrity implements Runnable {
class CheckIntegrity implements Callable<Integer> {

@CommandLine.ParentCommand
private ArgumentProcessor argumentProcessor;

@Mixin
private ArgumentProcessor.SharedOptions sharedOptions = new ArgumentProcessor.SharedOptions();

@Parameters(index = "0", converter = CygWinPathConverter.class, description = "BibTeX file to check", arity = "0..1")
private Path inputFile;
@Parameters(description = "BibTeX file to check", arity = "1")
private String inputFile;

@Option(names = {"--input"}, converter = CygWinPathConverter.class, description = "Input BibTeX file")
private Path inputOption;
@Option(names = {"--output-format"}, description = "Output format: errorformat, txt or csv", defaultValue = "errorformat")
private String outputFormat;

@Option(names = {"--output-format"}, description = "Output format: txt or csv")
private String outputFormat = "txt"; // FixMe: Default value?
@Option(names = {"--allow-integer-edition"}, description = "Allows Integer edition: true or false", defaultValue = "true")
private boolean allowIntegerEdition = true;

@Override
public void run() {
public Integer call() {
Optional<ParserResult> parserResult = ArgumentProcessor.importFile(
inputFile,
"bibtex",
argumentProcessor.cliPreferences,
sharedOptions.porcelain);
if (parserResult.isEmpty()) {
System.out.println(Localization.lang("Unable to open file '%0'.", inputFile));
return 2;
}

if (parserResult.get().isInvalid()) {
System.out.println(Localization.lang("Input file '%0' is invalid and could not be parsed.", inputFile));
return 2;
}

if (!sharedOptions.porcelain) {
System.out.println(Localization.lang("Checking integrity of '%0'.", inputFile));
System.out.flush();
}

// TODO: Implement integrity checking
BibDatabaseContext databaseContext = parserResult.get().getDatabaseContext();

IntegrityCheck integrityCheck = new IntegrityCheck(
databaseContext,
argumentProcessor.cliPreferences.getFilePreferences(),
argumentProcessor.cliPreferences.getCitationKeyPatternPreferences(),
JournalAbbreviationLoader.loadRepository(argumentProcessor.cliPreferences.getJournalAbbreviationPreferences()),
allowIntegerEdition
);

List<IntegrityMessage> messages = databaseContext.getEntries().stream()
.flatMap(entry -> {
if (!sharedOptions.porcelain) {
System.out.println(Localization.lang("Checking integrity of '%0'.", entry.getCitationKey().orElse("")));
}
return integrityCheck.checkEntry(entry).stream();
})
.toList();

return switch (outputFormat.toLowerCase(Locale.ROOT)) {
case "errorformat" ->
outputErrorFormat(messages);
case "txt" ->
outputTxt(messages);
case "csv" ->
outputCsv(messages);
default -> {
System.out.println(Localization.lang("Unknown output format '%0'.", outputFormat));
yield 3;
}
};
}

private int outputCsv(List<IntegrityMessage> messages) {
System.out.println("Citation Key,Field,Message");
for (IntegrityMessage message : messages) {
String citationKey = message.entry().getCitationKey().orElse("");
String field = message.field() != null ? message.field().getDisplayName() : "";
String msg = message.message().replace("\"", "\\\"");
if (msg.contains(",")) {
msg = "\"" + msg + "\"";
}
System.out.printf("%s,%s,%s%n", citationKey, field, msg);
}
return 0;
}

private int outputTxt(List<IntegrityMessage> messages) {
messages.forEach(System.out::println);
return 0;
}

private int outputErrorFormat(List<IntegrityMessage> messages) {
for (IntegrityMessage message : messages) {
BibEntry.FieldRange fieldRange = message.entry().getFieldRangeFromField(message.field());
System.out.printf("%s:%d:%d: %s\n".formatted(
inputFile,
fieldRange.startLine(),
fieldRange.startColumn(),
message.message()));
}
return 0;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.Stack;
import java.util.function.Predicate;
import java.util.regex.Pattern;

Expand Down Expand Up @@ -46,6 +47,7 @@
import org.jabref.model.entry.field.Field;
import org.jabref.model.entry.field.FieldFactory;
import org.jabref.model.entry.field.FieldProperty;
import org.jabref.model.entry.field.InternalField;
import org.jabref.model.entry.field.StandardField;
import org.jabref.model.entry.types.EntryTypeFactory;
import org.jabref.model.groups.ExplicitGroup;
Expand Down Expand Up @@ -90,7 +92,7 @@
*/
public class BibtexParser implements Parser {
private static final Logger LOGGER = LoggerFactory.getLogger(BibtexParser.class);
private static final Integer LOOKAHEAD = 1024;
private static final int LOOKAHEAD = 1024;
private static final String BIB_DESK_ROOT_GROUP_NAME = "BibDeskGroups";
private static final DocumentBuilderFactory DOCUMENT_BUILDER_FACTORY = DocumentBuilderFactory.newInstance();
private static final int INDEX_RELATIVE_PATH_IN_PLIST = 4;
Expand All @@ -100,7 +102,13 @@ public class BibtexParser implements Parser {
private BibDatabase database;
private Set<BibEntryType> entryTypes;
private boolean eof;

private int line = 1;
private int column = 1;
// Stores the last read column of the highest column number encountered on any line so far.
// In basic JDK data structures, there is no size-limited stack. We did not want to include Apache Commons Collections only for "CircularFifoBuffer"
private Stack<Integer> highestColumns = new Stack<>();

private ParserResult parserResult;
private final MetaDataParser metaDataParser;
private final Map<String, String> parsedBibDeskGroups;
Expand Down Expand Up @@ -634,13 +642,20 @@ private int read() throws IOException {
}
if (character == '\n') {
line++;
highestColumns.push(column);
column = 1;
} else {
column++;
}
return character;
}

private void unread(int character) throws IOException {
if (character == '\n') {
line--;
column = highestColumns.pop();
} else {
column--;
}
pushbackReader.unread(character);
if (pureTextFromFile.getLast() == character) {
Expand Down Expand Up @@ -679,13 +694,19 @@ private String parsePreamble() throws IOException {
private BibEntry parseEntry(String entryType) throws IOException {
BibEntry result = new BibEntry(EntryTypeFactory.parse(entryType));

result.setStartLine(line);
result.setStartColumn(column);

skipWhitespace();
consume('{', '(');
int character = peek();
if ((character != '\n') && (character != '\r')) {
skipWhitespace();
}
int keyStartLine = line;
int keyStartColumn = column;
String key = parseKey();
result.getFieldRanges().put(InternalField.KEY_FIELD, new BibEntry.FieldRange(keyStartLine, keyStartColumn, line, column));
result.setCitationKey(key);
skipWhitespace();

Expand Down Expand Up @@ -713,10 +734,14 @@ private BibEntry parseEntry(String entryType) throws IOException {
// Consume new line which signals end of entry
skipOneNewline();

result.setEndLine(line);
result.setEndColumn(column);
return result;
}

private void parseField(BibEntry entry) throws IOException {
int startLine = line;
int startColumn = column;
Field field = FieldFactory.parseField(parseTextToken().toLowerCase(Locale.ROOT));

skipWhitespace();
Expand Down Expand Up @@ -770,6 +795,8 @@ private void parseField(BibEntry entry) throws IOException {
}
}
}

entry.getFieldRanges().put(field, new BibEntry.FieldRange(startLine, startColumn, line, column));
}

private String parseFieldContent(Field field) throws IOException {
Expand Down
54 changes: 54 additions & 0 deletions jablib/src/main/java/org/jabref/model/entry/BibEntry.java
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,14 @@ public class BibEntry {
*/
private boolean changed;

private int startLine = -1;
private int endLine = -1;

private int startColumn = -1;
private int endColumn = -1;

private final Map<Field, FieldRange> fieldRanges = new HashMap<>();

/**
* Constructs a new BibEntry. The internal ID is set to IdGenerator.next()
*/
Expand Down Expand Up @@ -1286,4 +1294,50 @@ public boolean isEmpty() {
}
return StandardField.AUTOMATIC_FIELDS.containsAll(this.getFields());
}

public int getStartLine() {
return startLine;
}

public void setStartLine(int startLine) {
this.startLine = startLine;
}

public int getEndLine() {
return endLine;
}

public void setEndLine(int endLine) {
this.endLine = endLine;
}

public int getStartColumn() {
return startColumn;
}

public void setStartColumn(int startColumn) {
this.startColumn = startColumn;
}

public int getEndColumn() {
return endColumn;
}

public void setEndColumn(int endColumn) {
this.endColumn = endColumn;
}

public Map<Field, FieldRange> getFieldRanges() {
return fieldRanges;
}

public FieldRange getFieldRangeFromField(Field field) {
return fieldRanges.getOrDefault(field, fieldRanges.getOrDefault(field.getAlias().orElse(InternalField.KEY_FIELD), FieldRange.getNullRange()));
}

public record FieldRange(int startLine, int startColumn, int endLine, int endColumn) {
public static FieldRange getNullRange() {
return new FieldRange(0, 0, 0, 0);
}
}
}
1 change: 1 addition & 0 deletions jablib/src/main/resources/l10n/JabRef_en.properties
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,7 @@ Unknown\ export\ format\ %0=Unknown export format %0
Importing\ %0=Importing %0
Importing\ file\ %0\ as\ unknown\ format=Importing file %0 as unknown format
Format\ used\:\ %0=Format used: %0
Unknown\ output\ format\ '%0'.=Unknown output format '%0'.

Extension=Extension

Expand Down
Loading