Skip to content

Commit cbb4d1f

Browse files
palukkukoppor
andauthored
Open linked pdf files in bib files (#14275)
* add pdf opening ability * fix jbang * fix format * fix checkstyle * fix tests * remove unused import * Add debug * .jbang scripts should not be included inside themselves * Add ability to open pdfs in standalone nad gui mode * update execution rights * More `@NonNull` * More `@NonNull` * Fix data type * Fix catch of NPE in context of Optionals * Add `@NullMarked` and refactor Optional use * More close constructors * Add link to other tests * Output field at parsing * Initial test for BibDefinitionProvider * address comments * Fix file path on databasecontext * Fix test * Fix formatting * Fix Path.of - and remove obsolete method * Group ".remote" together in module-info * Fix URI.from() architecture rule * Fix test --------- Co-authored-by: Oliver Kopp <kopp.dev@gmail.com>
1 parent 2062ca9 commit cbb4d1f

File tree

22 files changed

+288
-56
lines changed

22 files changed

+288
-56
lines changed

.jbang/JabLsLauncher.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
//SOURCES ../jabls/src/main/java/org/jabref/languageserver/util/LspLinkHandler.java
3737
//SOURCES ../jabls/src/main/java/org/jabref/languageserver/util/LspParserHandler.java
3838
//SOURCES ../jabls/src/main/java/org/jabref/languageserver/util/LspRangeUtil.java
39+
//SOURCES ../jabls/src/main/java/org/jabref/languageserver/util/definition/BibDefinitionProvider.java
3940
//SOURCES ../jabls/src/main/java/org/jabref/languageserver/util/definition/DefinitionProvider.java
4041
//SOURCES ../jabls/src/main/java/org/jabref/languageserver/util/definition/DefinitionProviderFactory.java
4142
//SOURCES ../jabls/src/main/java/org/jabref/languageserver/util/definition/LatexDefinitionProvider.java

jablib/src/main/java/module-info.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
exports org.jabref.logic.protectedterms;
3131
exports org.jabref.logic.remote;
3232
exports org.jabref.logic.remote.client;
33+
exports org.jabref.logic.remote.server;
3334
exports org.jabref.logic.net.ssl;
3435
exports org.jabref.logic.citationstyle;
3536
exports org.jabref.architecture;
@@ -85,7 +86,6 @@
8586
exports org.jabref.logic.biblog;
8687
exports org.jabref.model.biblog;
8788
exports org.jabref.model.http;
88-
exports org.jabref.logic.remote.server;
8989
exports org.jabref.logic.util.strings;
9090
exports org.jabref.model.openoffice;
9191
exports org.jabref.logic.openoffice;

jablib/src/main/java/org/jabref/logic/importer/fileformat/BibtexParser.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@
8888
///
8989
/// **Opposite class:**
9090
/// [`BibDatabaseWriter`](org.jabref.logic.exporter.BibDatabaseWriter)
91+
///
92+
/// FIXME: This class relies on `char`, but should use [java.lang.Character] to be fully Unicode compliant.
9193
public class BibtexParser implements Parser {
9294
private static final Logger LOGGER = LoggerFactory.getLogger(BibtexParser.class);
9395
private static final int LOOKAHEAD = 1024;
@@ -759,7 +761,7 @@ private void parseField(BibEntry entry) throws IOException {
759761
Field field = FieldFactory.parseField(parseTextToken());
760762

761763
skipWhitespace();
762-
consume('=');
764+
consume(field, '=');
763765
String content = parseFieldContent(field);
764766
if (!content.isEmpty()) {
765767
if (entry.hasField(field)) {
@@ -1172,6 +1174,19 @@ private void consume(char expected) throws IOException {
11721174
}
11731175
}
11741176

1177+
private void consume(Field field, char expected) throws IOException {
1178+
int character = read();
1179+
1180+
if (character != expected) {
1181+
throw new IOException(
1182+
"Error at line " + line
1183+
+ " after column " + column
1184+
+ " (" + field.getName() + "): Expected "
1185+
+ expected + " but received "
1186+
+ (char) character + " (" + character + ")");
1187+
}
1188+
}
1189+
11751190
private boolean consumeUncritically(char expected) throws IOException {
11761191
int character;
11771192
// @formatter:off

jablib/src/main/java/org/jabref/logic/importer/util/FileFieldParser.java

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,14 @@
44
import java.nio.file.InvalidPathException;
55
import java.nio.file.Path;
66
import java.util.ArrayList;
7+
import java.util.HashMap;
78
import java.util.List;
9+
import java.util.Map;
10+
import java.util.stream.Collectors;
811

912
import org.jabref.logic.util.URLUtil;
1013
import org.jabref.model.entry.LinkedFile;
14+
import org.jabref.model.util.Range;
1115

1216
import org.slf4j.Logger;
1317
import org.slf4j.LoggerFactory;
@@ -21,7 +25,7 @@ public class FileFieldParser {
2125

2226
private boolean windowsPath;
2327

24-
public FileFieldParser(String value) {
28+
private FileFieldParser(String value) {
2529
if (value == null) {
2630
this.value = null;
2731
} else {
@@ -46,11 +50,16 @@ public FileFieldParser(String value) {
4650
public static List<LinkedFile> parse(String value) {
4751
// We need state to have a more clean code. Thus, we instantiate the class and then return the result
4852
FileFieldParser fileFieldParser = new FileFieldParser(value);
49-
return fileFieldParser.parse();
53+
return fileFieldParser.parse().stream().map(LinkedFilePosition::linkedFile).collect(Collectors.toList());
5054
}
5155

52-
public List<LinkedFile> parse() {
53-
List<LinkedFile> files = new ArrayList<>();
56+
public static Map<LinkedFile, Range> parseToPosition(String value) {
57+
FileFieldParser fileFieldParser = new FileFieldParser(value);
58+
return fileFieldParser.parse().stream().collect(HashMap::new, (map, position) -> map.put(position.linkedFile(), position.range()), HashMap::putAll);
59+
}
60+
61+
private List<LinkedFilePosition> parse() {
62+
List<LinkedFilePosition> files = new ArrayList<>();
5463

5564
if ((value == null) || value.trim().isEmpty()) {
5665
return files;
@@ -59,7 +68,7 @@ public List<LinkedFile> parse() {
5968
if (LinkedFile.isOnlineLink(value.trim())) {
6069
// needs to be modifiable
6170
try {
62-
return List.of(new LinkedFile(URLUtil.create(value), ""));
71+
return List.of(new LinkedFilePosition(new LinkedFile(URLUtil.create(value), ""), new Range(0, value.length() - 1)));
6372
} catch (MalformedURLException e) {
6473
LOGGER.error("invalid url", e);
6574
return files;
@@ -72,6 +81,7 @@ public List<LinkedFile> parse() {
7281
resetDataStructuresForNextElement();
7382
boolean inXmlChar = false;
7483
boolean escaped = false;
84+
int startColumn = 0;
7585

7686
for (int i = 0; i < value.length(); i++) {
7787
char c = value.charAt(i);
@@ -114,7 +124,8 @@ public List<LinkedFile> parse() {
114124
}
115125
} else if (!escaped && (c == ';') && !inXmlChar) {
116126
linkedFileData.add(charactersOfCurrentElement.toString());
117-
files.add(convert(linkedFileData));
127+
files.add(new LinkedFilePosition(convert(linkedFileData), new Range(startColumn, i)));
128+
startColumn = i + 1;
118129

119130
// next iteration
120131
resetDataStructuresForNextElement();
@@ -127,7 +138,7 @@ public List<LinkedFile> parse() {
127138
linkedFileData.add(charactersOfCurrentElement.toString());
128139
}
129140
if (!linkedFileData.isEmpty()) {
130-
files.add(convert(linkedFileData));
141+
files.add(new LinkedFilePosition(convert(linkedFileData), new Range(startColumn, value.length() - 1)));
131142
}
132143
return files;
133144
}
@@ -193,4 +204,7 @@ static LinkedFile convert(List<String> entry) {
193204
entry.clear();
194205
return field;
195206
}
207+
208+
private record LinkedFilePosition(LinkedFile linkedFile, Range range) {
209+
}
196210
}

jablib/src/main/java/org/jabref/logic/util/io/FileUtil.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -403,9 +403,9 @@ public static Optional<Path> findSingleFileRecursively(String filename, Path roo
403403
return Optional.empty();
404404
}
405405

406-
public static Optional<Path> find(final BibDatabaseContext databaseContext,
406+
public static Optional<Path> find(@NonNull BibDatabaseContext databaseContext,
407407
@NonNull String fileName,
408-
FilePreferences filePreferences) {
408+
@NonNull FilePreferences filePreferences) {
409409
return find(fileName, databaseContext.getFileDirectories(filePreferences));
410410
}
411411

@@ -416,7 +416,7 @@ public static Optional<Path> find(final BibDatabaseContext databaseContext,
416416
* Will look in each of the given directories starting from the beginning and
417417
* returning the first found file to match if any.
418418
*/
419-
public static Optional<Path> find(String fileName, List<Path> directories) {
419+
public static Optional<Path> find(@NonNull String fileName, @NonNull List<@NonNull Path> directories) {
420420
if (directories.isEmpty()) {
421421
// Fallback, if no directories to resolve are passed
422422
Path path = Path.of(fileName);

jablib/src/main/java/org/jabref/model/database/BibDatabaseContext.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,10 @@ public class BibDatabaseContext {
6969

7070
@Nullable
7171
private DatabaseSynchronizer dbmsSynchronizer;
72+
7273
@Nullable
7374
private CoarseChangeFilter dbmsListener;
75+
7476
private DatabaseLocation location;
7577

7678
public BibDatabaseContext() {
@@ -172,7 +174,7 @@ public boolean isStudy() {
172174
* @param preferences The fileDirectory preferences
173175
* @return List of existing absolute paths
174176
*/
175-
public List<Path> getFileDirectories(FilePreferences preferences) {
177+
public @NonNull List<@NonNull Path> getFileDirectories(@NonNull FilePreferences preferences) {
176178
// Paths are a) ordered and b) should be contained only once in the result
177179
SequencedSet<Path> fileDirs = new LinkedHashSet<>(3);
178180

jablib/src/main/java/org/jabref/model/entry/LinkedFile.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,9 @@
2828
import org.jspecify.annotations.NullMarked;
2929
import org.jspecify.annotations.Nullable;
3030

31-
/**
32-
* Represents the link to an external file (e.g. associated PDF file).
33-
* This class is {@link Serializable} which is needed for drag and drop in gui
34-
*/
31+
/// Represents the link to an external file (e.g. associated PDF file).
32+
/// This class is {@link Serializable} which is needed for drag and drop in gui
33+
/// The conversion from String ([org.jabref.model.entry.field.StandardField.FILE]) is done at [org.jabref.logic.importer.util.FileFieldParser#parse(String)]
3534
@AllowedToUseLogic("Uses FileUtil from logic")
3635
@NullMarked
3736
public class LinkedFile implements Serializable {

jablib/src/test/java/org/jabref/logic/importer/ParserResultTest.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import static org.junit.jupiter.api.Assertions.assertTrue;
1919
import static org.mockito.Mockito.mock;
2020

21+
// Other tests for reading can be found at [org.jabref.logic.importer.fileformat.BibtexImporterTest]
2122
class ParserResultTest {
2223
@Test
2324
void isEmptyForNewParseResult() {

jablib/src/test/java/org/jabref/logic/importer/util/FileFieldParserTest.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,11 @@ private static Stream<Arguments> stringsToParseTest() throws MalformedURLExcepti
219219
Arguments.of(
220220
List.of(new LinkedFile("", "A:\\Zotero\\storage\\test.pdf", "")),
221221
"A:\\Zotero\\storage\\test.pdf"
222+
),
223+
// Mixed path
224+
Arguments.of(
225+
List.of(new LinkedFile("", "C:/Users/Philip/Downloads/corti-et-al-2009-cocoa-and-cardiovascular-health.pdf", "PDF")),
226+
":C\\:/Users/Philip/Downloads/corti-et-al-2009-cocoa-and-cardiovascular-health.pdf:PDF"
222227
)
223228
);
224229
}

jablib/src/test/java/org/jabref/model/entry/identifier/RFCTest.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.jabref.model.entry.identifier;
22

33
import java.net.URI;
4+
import java.net.URISyntaxException;
45
import java.util.Optional;
56

67
import org.junit.jupiter.api.Test;
@@ -30,8 +31,8 @@ void invalidRfc() {
3031
}
3132

3233
@Test
33-
void getExternalUri() {
34+
void getExternalUri() throws URISyntaxException {
3435
RFC rfc = new RFC("rfc7276");
35-
assertEquals(Optional.of(URI.create("https://www.rfc-editor.org/rfc/rfc7276")), rfc.getExternalURI());
36+
assertEquals(Optional.of(new URI("https://www.rfc-editor.org/rfc/rfc7276")), rfc.getExternalURI());
3637
}
3738
}

0 commit comments

Comments
 (0)