From ffa85c8445b4f3d2ece1bc2423e60dc7c012f16c Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Sun, 7 Apr 2024 09:40:14 +0200 Subject: [PATCH 01/18] Add logic for parsing references from last page of PDF (BibliopgraphyFromPdfImporter) - Support more date formats - Increase log level for issues for date parsing --- .../org/jabref/logic/importer/Importer.java | 32 +- .../BibliopgraphyFromPdfImporter.java | 301 ++++++++++++++++++ .../fileformat/PdfContentImporter.java | 10 +- .../java/org/jabref/model/entry/Date.java | 15 +- .../BibliopgraphyFromPdfImporterTest.java | 156 +++++++++ .../importer/fileformat/tua3i2refpage.pdf | Bin 0 -> 43796 bytes 6 files changed, 484 insertions(+), 30 deletions(-) create mode 100644 src/main/java/org/jabref/logic/importer/fileformat/BibliopgraphyFromPdfImporter.java create mode 100644 src/test/java/org/jabref/logic/importer/fileformat/BibliopgraphyFromPdfImporterTest.java create mode 100644 src/test/resources/org/jabref/logic/importer/fileformat/tua3i2refpage.pdf diff --git a/src/main/java/org/jabref/logic/importer/Importer.java b/src/main/java/org/jabref/logic/importer/Importer.java index fa4159dc404..d991b087d3b 100644 --- a/src/main/java/org/jabref/logic/importer/Importer.java +++ b/src/main/java/org/jabref/logic/importer/Importer.java @@ -168,22 +168,6 @@ public static BufferedReader getReader(InputStream stream) { return new BufferedReader(reader); } - /** - * Returns the name of this import format. - * - *

The name must be unique.

- * - * @return format name, must be unique and not null - */ - public abstract String getName(); - - /** - * Returns the type of files that this importer can read - * - * @return {@link FileType} corresponding to the importer - */ - public abstract FileType getFileType(); - /** * Returns a one-word ID which identifies this importer. Used for example, to identify the importer when used from * the command line. @@ -202,6 +186,15 @@ public String getId() { return result.toString(); } + /** + * Returns the name of this import format. + * + *

The name must be unique.

+ * + * @return format name, must be unique and not null + */ + public abstract String getName(); + /** * Returns the description of the import format. *

@@ -216,6 +209,13 @@ public String getId() { */ public abstract String getDescription(); + /** + * Returns the type of files that this importer can read + * + * @return {@link FileType} corresponding to the importer + */ + public abstract FileType getFileType(); + @Override public int hashCode() { return getName().hashCode(); diff --git a/src/main/java/org/jabref/logic/importer/fileformat/BibliopgraphyFromPdfImporter.java b/src/main/java/org/jabref/logic/importer/fileformat/BibliopgraphyFromPdfImporter.java new file mode 100644 index 00000000000..38d2d3b20b8 --- /dev/null +++ b/src/main/java/org/jabref/logic/importer/fileformat/BibliopgraphyFromPdfImporter.java @@ -0,0 +1,301 @@ +package org.jabref.logic.importer.fileformat; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.StringWriter; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.jabref.logic.citationkeypattern.CitationKeyGenerator; +import org.jabref.logic.citationkeypattern.CitationKeyPatternPreferences; +import org.jabref.logic.importer.Importer; +import org.jabref.logic.importer.ParserResult; +import org.jabref.logic.l10n.Localization; +import org.jabref.logic.util.FileType; +import org.jabref.logic.util.StandardFileType; +import org.jabref.logic.xmp.EncryptedPdfsNotSupportedException; +import org.jabref.logic.xmp.XmpUtilReader; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.Date; +import org.jabref.model.entry.field.Field; +import org.jabref.model.entry.field.StandardField; +import org.jabref.model.entry.types.StandardEntryType; + +import com.google.common.annotations.VisibleForTesting; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.text.PDFTextStripper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Parses the references from the "References" section from a PDF + *

+ * Currently, IEEE two column format is supported. + *

+ */ +public class BibliopgraphyFromPdfImporter extends Importer { + + private static final Logger LOGGER = LoggerFactory.getLogger(BibliopgraphyFromPdfImporter.class); + + private static final Pattern REFERENCE_PATTERN = Pattern.compile("\\[(\\d+)\\](.*?)(?=\\[|$)", Pattern.DOTALL); + private final CitationKeyPatternPreferences citationKeyPatternPreferences; + + public BibliopgraphyFromPdfImporter(CitationKeyPatternPreferences citationKeyPatternPreferences) { + this.citationKeyPatternPreferences = citationKeyPatternPreferences; + } + + @Override + public boolean isRecognizedFormat(BufferedReader input) throws IOException { + return input.readLine().startsWith("%PDF"); + } + + @Override + public ParserResult importDatabase(BufferedReader reader) throws IOException { + Objects.requireNonNull(reader); + throw new UnsupportedOperationException("BibliopgraphyFromPdfImporter does not support importDatabase(BufferedReader reader)." + + "Instead use importDatabase(Path filePath)."); + } + + @Override + public String getName() { + return "Bibliography from PDF"; + } + + @Override + public String getDescription() { + return "Reads the references from the 'References' section of a PDF file."; + } + + @Override + public FileType getFileType() { + return StandardFileType.PDF; + } + + @Override + public ParserResult importDatabase(Path filePath) { + List result; + + try (PDDocument document = new XmpUtilReader().loadWithAutomaticDecryption(filePath)) { + String contents = getLastPageContents(document); + result = getEntriesFromPDFContent(contents); + } catch (EncryptedPdfsNotSupportedException e) { + return ParserResult.fromErrorMessage(Localization.lang("Decryption not supported.")); + } catch (IOException exception) { + return ParserResult.fromError(exception); + } + + ParserResult parserResult = new ParserResult(result); + + // Generate citation keys for result + CitationKeyGenerator citationKeyGenerator = new CitationKeyGenerator(parserResult.getDatabaseContext(), citationKeyPatternPreferences); + parserResult.getDatabase().getEntries().forEach(citationKeyGenerator::generateAndSetKey); + + return parserResult; + } + + private record IntermediateData(String number, String reference) { + } + + /** + * In: "[1] ...\n...\n...[2]...\n...\n...\n[3]..." + * Out: List = ["[1] ...", "[2]...", "[3]..."] + */ + private List getEntriesFromPDFContent(String contents) { + List referencesStrings = new ArrayList<>(); + Matcher matcher = REFERENCE_PATTERN.matcher(contents); + while (matcher.find()) { + String reference = matcher.group(2).replaceAll("\\r?\\n", " ").trim(); + referencesStrings.add(new IntermediateData(matcher.group(1), reference)); + } + + return referencesStrings.stream() + .map(data -> parseReference(data.number(), data.reference())) + .toList(); + } + + private String getLastPageContents(PDDocument document) throws IOException { + PDFTextStripper stripper = new PDFTextStripper(); + + int lastPage = document.getNumberOfPages(); + stripper.setStartPage(lastPage); + stripper.setEndPage(lastPage); + StringWriter writer = new StringWriter(); + stripper.writeText(document, writer); + + return writer.toString(); + } + + /** + * Example: J. Knaster et al., “Overview of the IFMIF/EVEDA project”, Nucl. Fusion, vol. 57, p. 102016, 2017. doi:10.1088/ 1741-4326/aa6a6a + * + * @param number The number of the reference - used for logging only + */ + @VisibleForTesting + BibEntry parseReference(String number, String reference) { + String originalReference = "[" + number + "] " + reference; + BibEntry result = new BibEntry(StandardEntryType.Article); + + reference = reference.replace(".-", "-"); + + // J. Knaster et al., “Overview of the IFMIF/EVEDA project”, Nucl. Fusion, vol. 57, p. 102016, 2017. doi:10.1088/ 1741-4326/aa6a6a + // Y. Shimosaki et al., “Lattice design for 5 MeV – 125 mA CW RFQ operation in LIPAc”, in Proc. IPAC’19, Mel- bourne, Australia, May 2019, pp. 977-979. doi:10.18429/ JACoW-IPAC2019-MOPTS051 + int pos = reference.indexOf("doi:"); + if (pos >= 0) { + String doi = reference.substring(pos + 4).trim(); + doi = doi.replace(" ", ""); + result.setField(StandardField.DOI, doi); + reference = reference.substring(0, pos).trim(); + } + + // J. Knaster et al., “Overview of the IFMIF/EVEDA project”, Nucl. Fusion, vol. 57, p. 102016, 2017. + // Y. Shimosaki et al., “Lattice design for 5 MeV – 125 mA CW RFQ operation in LIPAc”, in Proc. IPAC’19, Mel- bourne, Australia, May 2019, pp. 977-979 + Pattern yearAtEnd = Pattern.compile(", (\\d{4})\\.$"); + Matcher matcher = yearAtEnd.matcher(reference); + if (matcher.find()) { + result.setField(StandardField.YEAR, matcher.group(1)); + reference = reference.substring(0, matcher.start()).trim(); + } + + Pattern pages = Pattern.compile(", pp\\. (\\d+--?\\d+)\\.?(.*)"); + reference = updateEntryAndReferenceIfMatches(reference, pages, result, StandardField.PAGES); + + // J. Knaster et al., “Overview of the IFMIF/EVEDA project”, Nucl. Fusion, vol. 57, p. 102016 + // Y. Shimosaki et al., “Lattice design for 5 MeV – 125 mA CW RFQ operation in LIPAc”, in Proc. IPAC’19, Mel- bourne, Australia, May 2019 + Pattern page = Pattern.compile(", p\\. (\\d+)(.*)"); + reference = updateEntryAndReferenceIfMatches(reference, page, result, StandardField.PAGES); + + Pattern monthRangeAndYear = Pattern.compile(", ([A-Z][a-z]{2,7}\\.?)-[A-Z][a-z]{2,7}\\.? (\\d+)(.*)"); + matcher = monthRangeAndYear.matcher(reference); + if (matcher.find()) { + // strip out second month + reference = reference.substring(0, matcher.start()) + ", " + matcher.group(1) + " " + matcher.group(2) + matcher.group(3); + } + + // J. Knaster et al., “Overview of the IFMIF/EVEDA project”, Nucl. Fusion, vol. 57 + // Y. Shimosaki et al., “Lattice design for 5 MeV – 125 mA CW RFQ operation in LIPAc”, in Proc. IPAC’19, Mel- bourne, Australia, May 2019 + Pattern monthAndYear = Pattern.compile(", ([A-Z][a-z]{2,7}\\.? \\d+),? ?(.*)"); + matcher = monthAndYear.matcher(reference); + if (matcher.find()) { + Optional parsedDate = Date.parse(matcher.group(1)); + if (parsedDate.isPresent()) { + Date date = parsedDate.get(); + date.getYear().ifPresent(year -> result.setField(StandardField.YEAR, year.toString())); + date.getMonth().ifPresent(month -> result.setField(StandardField.MONTH, month.getJabRefFormat())); + + String prefix = reference.substring(0, matcher.start()).trim(); + String suffix = matcher.group(2); + if (!suffix.isEmpty() && !".".equals(suffix)) { + suffix = ", " + suffix.replaceAll("^\\. ", ""); + } else { + suffix = ""; + } + reference = prefix + suffix; + } + } + + // J. Knaster et al., “Overview of the IFMIF/EVEDA project”, Nucl. Fusion, vol. 57 + // Y. Shimosaki et al., “Lattice design for 5 MeV – 125 mA CW RFQ operation in LIPAc”, in Proc. IPAC’19, Mel- bourne, Australia + Pattern volume = Pattern.compile(", vol\\. (\\d+)(.*)"); + reference = updateEntryAndReferenceIfMatches(reference, volume, result, StandardField.VOLUME); + + Pattern no = Pattern.compile(", no\\. (\\d+)(.*)"); + reference = updateEntryAndReferenceIfMatches(reference, no, result, StandardField.NUMBER); + + // J. Knaster et al., “Overview of the IFMIF/EVEDA project”, Nucl. Fusion + // Y. Shimosaki et al., “Lattice design for 5 MeV – 125 mA CW RFQ operation in LIPAc”, in Proc. IPAC’19, Mel- bourne, Australia + Pattern authorsAndTitleAtBeginning = Pattern.compile("^([^“]+), “(.*?)”, "); + matcher = authorsAndTitleAtBeginning.matcher(reference); + if (matcher.find()) { + result.setField(StandardField.AUTHOR, matcher.group(1) + .replace("- ", "") + .replaceAll("et al\\.?", "and others")); + result.setField(StandardField.TITLE, matcher.group(2) + .replace("- ", "") + .replaceAll("et al\\.?", "and others")); + reference = reference.substring(matcher.end()).trim(); + } else { + // No authors present + // Example: “AF4.1.1 SRF Linac Engineering Design Report”, Internal note. + Pattern title = Pattern.compile("“(.*?)”, (.*)"); + reference = updateEntryAndReferenceIfMatches(reference, title, result, StandardField.TITLE); + } + + // Nucl. Fusion + // in Proc. IPAC’19, Mel- bourne, Australia + // presented at th 8th DITANET Topical Workshop on Beam Position Monitors, CERN, Geneva, Switzreland + List stringsToRemove = List.of("presented at", "to be presented at"); + // need to use "iterator()" instead of "stream().foreach", because "reference" is modified inside the loop + Iterator iterator = stringsToRemove.iterator(); + while (iterator.hasNext()) { + String check = iterator.next(); + if (reference.startsWith(check)) { + reference = reference.substring(check.length()).trim(); + result.setType(StandardEntryType.InProceedings); + } + } + + boolean startsWithInProc = reference.startsWith("in Proc."); + boolean conainsWorkshop = reference.contains("Workshop"); + if (startsWithInProc || conainsWorkshop) { + int beginIndex = startsWithInProc ? 3 : 0; + result.setField(StandardField.BOOKTITLE, reference.substring(beginIndex).replace("- ", "").trim()); + result.setType(StandardEntryType.InProceedings); + reference = ""; + } + + // Nucl. Fusion + reference = reference.trim() + .replace("- ", "") + .replaceAll("\\.$", ""); + if (!reference.contains(",") && !reference.isEmpty()) { + if (reference.endsWith(" Note") || reference.endsWith(" note")) { + result.setField(StandardField.NOTE, reference); + result.setType(StandardEntryType.TechReport); + } else { + result.setField(StandardField.JOURNAL, reference.replace("- ", "")); + } + reference = ""; + } else { + String toAdd = reference; + result.setType(StandardEntryType.InProceedings); + if (result.hasField(StandardField.BOOKTITLE)) { + String oldTitle = result.getField(StandardField.BOOKTITLE).get(); + result.setField(StandardField.BOOKTITLE, oldTitle + toAdd); + } else { + result.setField(StandardField.BOOKTITLE, toAdd); + } + reference = ""; + LOGGER.debug("InProceedings fallback used for current state of handled string {}", reference); + } + + if (reference.isEmpty()) { + result.setField(StandardField.COMMENT, originalReference); + } else { + result.setField(StandardField.COMMENT, "Unprocessed: " + reference + "\n\n" + originalReference); + } + return result; + } + + /** + * @param pattern A pattern matching two groups: The first one to take, the second one to leave at the end of the string + */ + private static String updateEntryAndReferenceIfMatches(String reference, Pattern pattern, BibEntry result, Field field) { + Matcher matcher; + matcher = pattern.matcher(reference); + if (matcher.find()) { + result.setField(field, matcher.group(1).replace("- ", "")); + String suffix = matcher.group(2); + if (!suffix.isEmpty()) { + suffix = " " + suffix; + } + reference = reference.substring(0, matcher.start()).trim() + suffix; + } + return reference; + } +} diff --git a/src/main/java/org/jabref/logic/importer/fileformat/PdfContentImporter.java b/src/main/java/org/jabref/logic/importer/fileformat/PdfContentImporter.java index 3d2e4f7aa86..40233fc288d 100644 --- a/src/main/java/org/jabref/logic/importer/fileformat/PdfContentImporter.java +++ b/src/main/java/org/jabref/logic/importer/fileformat/PdfContentImporter.java @@ -5,6 +5,7 @@ import java.io.StringWriter; import java.nio.file.Path; import java.util.ArrayList; +import java.util.List; import java.util.Locale; import java.util.Objects; import java.util.Optional; @@ -182,16 +183,9 @@ public ParserResult importDatabase(BufferedReader reader) throws IOException { + "Instead use importDatabase(Path filePath, Charset defaultEncoding)."); } - @Override - public ParserResult importDatabase(String data) throws IOException { - Objects.requireNonNull(data); - throw new UnsupportedOperationException("PdfContentImporter does not support importDatabase(String data)." - + "Instead use importDatabase(Path filePath, Charset defaultEncoding)."); - } - @Override public ParserResult importDatabase(Path filePath) { - final ArrayList result = new ArrayList<>(1); + List result = new ArrayList<>(1); try (PDDocument document = new XmpUtilReader().loadWithAutomaticDecryption(filePath)) { String firstPageContents = getFirstPageContents(document); Optional entry = getEntryFromPDFContent(firstPageContents, OS.NEWLINE); diff --git a/src/main/java/org/jabref/model/entry/Date.java b/src/main/java/org/jabref/model/entry/Date.java index 8c127f6bf2f..3509b8cc304 100644 --- a/src/main/java/org/jabref/model/entry/Date.java +++ b/src/main/java/org/jabref/model/entry/Date.java @@ -41,10 +41,13 @@ public class Date { "M/uu", // covers 9/15 "MMMM d, uuuu", // covers September 1, 2015 "MMMM, uuuu", // covers September, 2015 + "MMMM uuuu", // covers September 2015 "d.M.uuuu", // covers 15.1.2015 "uuuu.M.d", // covers 2015.1.15 "uuuu", // covers 2015 "MMM, uuuu", // covers Jan, 2020 + "MMM. uuuu", // covers Oct. 2020 + "MMM uuuu", // covers Jan 2020 "uuuu.MM.d", // covers 2015.10.15 "d MMMM u/d MMMM u", // covers 20 January 2015/20 February 2015 "d MMMM u", // covers 20 January 2015 @@ -141,7 +144,7 @@ public static Optional parse(String dateString) { TemporalAccessor parsedEndDate = SIMPLE_DATE_FORMATS.parse(strDates[1].strip()); return Optional.of(new Date(parsedDate, parsedEndDate)); } catch (DateTimeParseException e) { - LOGGER.debug("Invalid Date format for range", e); + LOGGER.warn("Invalid Date format for range", e); return Optional.empty(); } } else if (dateString.matches( @@ -162,7 +165,7 @@ public static Optional parse(String dateString) { TemporalAccessor parsedEndDate = SIMPLE_DATE_FORMATS.parse(strDates[1].strip()); return Optional.of(new Date(parsedDate, parsedEndDate)); } catch (DateTimeParseException e) { - LOGGER.debug("Invalid Date format range", e); + LOGGER.warn("Invalid Date format range", e); return Optional.empty(); } } else if (dateString.matches( @@ -179,7 +182,7 @@ public static Optional parse(String dateString) { TemporalAccessor parsedEndDate = parseDateWithEraIndicator(strDates[1]); return Optional.of(new Date(parsedDate, parsedEndDate)); } catch (DateTimeParseException e) { - LOGGER.debug("Invalid Date format range", e); + LOGGER.warn("Invalid Date format range", e); return Optional.empty(); } } else if (dateString.matches( @@ -196,13 +199,13 @@ public static Optional parse(String dateString) { TemporalAccessor parsedEndDate = parseDateWithEraIndicator(strDates[1]); return Optional.of(new Date(parsedDate, parsedEndDate)); } catch (DateTimeParseException e) { - LOGGER.debug("Invalid Date format range", e); + LOGGER.warn("Invalid Date format range", e); return Optional.empty(); } } // if dateString is single year - if (dateString.matches("\\d{4}-|" + "\\d{4}\\?")) { + if (dateString.matches("\\d{4}-|\\d{4}\\?")) { try { String year = dateString.substring(0, dateString.length() - 1); TemporalAccessor parsedDate = SIMPLE_DATE_FORMATS.parse(year); @@ -225,7 +228,7 @@ public static Optional parse(String dateString) { TemporalAccessor date = parseDateWithEraIndicator(dateString); return Optional.of(new Date(date)); } catch (DateTimeParseException e) { - LOGGER.debug("Invalid Date format with era indicator", e); + LOGGER.warn("Invalid Date format with era indicator", e); return Optional.empty(); } } diff --git a/src/test/java/org/jabref/logic/importer/fileformat/BibliopgraphyFromPdfImporterTest.java b/src/test/java/org/jabref/logic/importer/fileformat/BibliopgraphyFromPdfImporterTest.java new file mode 100644 index 00000000000..0a4c0970d01 --- /dev/null +++ b/src/test/java/org/jabref/logic/importer/fileformat/BibliopgraphyFromPdfImporterTest.java @@ -0,0 +1,156 @@ +package org.jabref.logic.importer.fileformat; + +import java.nio.file.Path; +import java.util.List; +import java.util.stream.Stream; + +import org.jabref.logic.citationkeypattern.CitationKeyPatternPreferences; +import org.jabref.logic.citationkeypattern.GlobalCitationKeyPattern; +import org.jabref.logic.importer.ParserResult; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.field.StandardField; +import org.jabref.model.entry.types.StandardEntryType; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.jabref.logic.citationkeypattern.CitationKeyGenerator.DEFAULT_UNWANTED_CHARACTERS; +import static org.junit.jupiter.api.Assertions.assertEquals; + +class BibliopgraphyFromPdfImporterTest { + + private BibliopgraphyFromPdfImporter bibliopgraphyFromPdfImporter; + + @BeforeEach + void setup() { + GlobalCitationKeyPattern globalCitationKeyPattern = GlobalCitationKeyPattern.fromPattern("[auth][year]"); + CitationKeyPatternPreferences citationKeyPatternPreferences = new CitationKeyPatternPreferences( + false, + false, + false, + CitationKeyPatternPreferences.KeySuffix.SECOND_WITH_A, + "", + "", + DEFAULT_UNWANTED_CHARACTERS, + globalCitationKeyPattern, + "", + ','); + bibliopgraphyFromPdfImporter = new BibliopgraphyFromPdfImporter(citationKeyPatternPreferences); + } + + @Test + void tua3i2refpage() throws Exception { + Path file = Path.of(BibliopgraphyFromPdfImporterTest.class.getResource("tua3i2refpage.pdf").toURI()); + ParserResult parserResult = bibliopgraphyFromPdfImporter.importDatabase(file); + BibEntry entry01 = new BibEntry(); + assertEquals(List.of(entry01), parserResult.getDatabase().getEntries()); + } + + static Stream references() { + return Stream.of( + Arguments.of( + new BibEntry(StandardEntryType.Article) + .withField(StandardField.AUTHOR, "J. Knaster and others") + .withField(StandardField.TITLE, "Overview of the IFMIF/EVEDA project") + .withField(StandardField.JOURNAL, "Nucl. Fusion") + .withField(StandardField.VOLUME, "57") + .withField(StandardField.PAGES, "102016") + .withField(StandardField.YEAR, "2017") + .withField(StandardField.DOI, "10.1088/1741-4326/aa6a6a") + .withField(StandardField.COMMENT, "[1] J. Knaster et al., “Overview of the IFMIF/EVEDA project”, Nucl. Fusion, vol. 57, p. 102016, 2017. doi:10.1088/ 1741-4326/aa6a6a"), + "1", + "J. Knaster et al., “Overview of the IFMIF/EVEDA project”, Nucl. Fusion, vol. 57, p. 102016, 2017. doi:10.1088/ 1741-4326/aa6a6a" + ), + Arguments.of( + new BibEntry(StandardEntryType.InProceedings) + .withField(StandardField.AUTHOR, "Y. Shimosaki and others") + .withField(StandardField.TITLE, "Lattice design for 5 MeV – 125 mA CW RFQ operation in LIPAc") + .withField(StandardField.BOOKTITLE, "Proc. IPAC’19, Melbourne, Australia") + .withField(StandardField.MONTH, "#may#") + .withField(StandardField.YEAR, "2019") + .withField(StandardField.PAGES, "977-979") + .withField(StandardField.DOI, "10.18429/JACoW-IPAC2019-MOPTS051") + .withField(StandardField.COMMENT, "[2] Y. Shimosaki et al., “Lattice design for 5 MeV – 125 mA CW RFQ operation in LIPAc”, in Proc. IPAC’19, Mel- bourne, Australia, May 2019, pp. 977-979. doi:10.18429/ JACoW-IPAC2019-MOPTS051"), + "2", + "Y. Shimosaki et al., “Lattice design for 5 MeV – 125 mA CW RFQ operation in LIPAc”, in Proc. IPAC’19, Mel- bourne, Australia, May 2019, pp. 977-979. doi:10.18429/ JACoW-IPAC2019-MOPTS051" + ), + Arguments.of( + new BibEntry(StandardEntryType.InProceedings) + .withField(StandardField.AUTHOR, "L. Bellan and others") + .withField(StandardField.TITLE, "Acceleration of the high current deuteron beam through the IFMIF-EVEDA beam dynamics performances") + .withField(StandardField.BOOKTITLE, "Proc. HB’21, Batavia, IL, USA") + .withField(StandardField.MONTH, "#oct#") + .withField(StandardField.YEAR, "2021") + .withField(StandardField.PAGES, "197-202") + .withField(StandardField.DOI, "10.18429/JACoW-HB2021-WEDC2") + .withField(StandardField.COMMENT, "[5] L. Bellan et al., “Acceleration of the high current deuteron beam through the IFMIF-EVEDA beam dynamics perfor- mances”, in Proc. HB’21, Batavia, IL, USA, Oct. 2021, pp. 197-202. doi:10.18429/JACoW-HB2021-WEDC2"), + "5", + "L. Bellan et al., “Acceleration of the high current deuteron beam through the IFMIF-EVEDA beam dynamics perfor- mances”, in Proc. HB’21, Batavia, IL, USA, Oct. 2021, pp. 197-202. doi:10.18429/JACoW-HB2021-WEDC2" + ), + Arguments.of( + new BibEntry(StandardEntryType.InProceedings) + .withField(StandardField.AUTHOR, "K. Masuda and others") + .withField(StandardField.TITLE, "Commissioning of IFMIF Prototype Accelerator towards CW operation") + .withField(StandardField.BOOKTITLE, "Proc. LINAC’22, Liverpool, UK") + .withField(StandardField.MONTH, "#aug#") + .withField(StandardField.YEAR, "2022") + .withField(StandardField.PAGES, "319-323") + .withField(StandardField.DOI, "10.18429/JACoW-LINAC2022-TU2AA04") + .withField(StandardField.COMMENT, "[6] K. Masuda et al., “Commissioning of IFMIF Prototype Ac- celerator towards CW operation”, in Proc. LINAC’22, Liv- erpool, UK, Aug.-Sep. 2022, pp. 319-323. doi:10.18429/ JACoW-LINAC2022-TU2AA04"), + + "6", + "K. Masuda et al., “Commissioning of IFMIF Prototype Ac- celerator towards CW operation”, in Proc. LINAC’22, Liv- erpool, UK, Aug.-Sep. 2022, pp. 319-323. doi:10.18429/ JACoW-LINAC2022-TU2AA04" + ), + Arguments.of( + new BibEntry(StandardEntryType.InProceedings) + .withField(StandardField.AUTHOR, "I. Podadera, J. M. Carmona, A. Ibarra, and J. Molla") + .withField(StandardField.TITLE, "Beam position monitor development for LIPAc") + .withField(StandardField.BOOKTITLE, "th 8th DITANET Topical Workshop on Beam Position Monitors, CERN, Geneva, Switzreland") + .withField(StandardField.MONTH, "#jan#") + .withField(StandardField.YEAR, "2012") + .withField(StandardField.COMMENT, "[10] I. Podadera, J. M. Carmona, A. Ibarra, and J. Molla, “Beam position monitor development for LIPAc”, presented at th 8th DITANET Topical Workshop on Beam Position Monitors, CERN, Geneva, Switzreland, Jan. 2012."), + "10", + "I. Podadera, J. M. Carmona, A. Ibarra, and J. Molla, “Beam position monitor development for LIPAc”, presented at th 8th DITANET Topical Workshop on Beam Position Monitors, CERN, Geneva, Switzreland, Jan. 2012." + ), + Arguments.of( + new BibEntry(StandardEntryType.InProceedings) + .withField(StandardField.AUTHOR, "S. Kwon and others") + .withField(StandardField.TITLE, "High beam current operation with beam di-agnostics at LIPAc") + .withField(StandardField.BOOKTITLE, "HB’23, Geneva, Switzerland, paper FRC1I2, this conference") + .withField(StandardField.MONTH, "#oct#") + .withField(StandardField.YEAR, "2023") + .withField(StandardField.COMMENT, "[14] S. Kwon et al., “High beam current operation with beam di-agnostics at LIPAc”, presented at HB’23, Geneva, Switzer- land, Oct. 2023, paper FRC1I2, this conference."), + "14", + "S. Kwon et al., “High beam current operation with beam di-agnostics at LIPAc”, presented at HB’23, Geneva, Switzer- land, Oct. 2023, paper FRC1I2, this conference." + ), + Arguments.of( + new BibEntry(StandardEntryType.InProceedings) + .withField(StandardField.AUTHOR, "T. Akagi and others") + .withField(StandardField.TITLE, "Achievement of high-current continuouswave deuteron injector for Linear IFMIF Prototype Accelerator (LIPAc)") + .withField(StandardField.BOOKTITLE, "IAEA FEC’23, London, UK, https://www.iaea.org/events/fec2023") + .withField(StandardField.MONTH, "#oct#") + .withField(StandardField.YEAR, "2023") + .withField(StandardField.COMMENT, "[15] T. Akagi et al., “Achievement of high-current continuous- wave deuteron injector for Linear IFMIF Prototype Accelera- tor (LIPAc)”, to be presented at IAEA FEC’23, London, UK, Oct. 2023. https://www.iaea.org/events/fec2023"), + "15", + "T. Akagi et al., “Achievement of high-current continuous- wave deuteron injector for Linear IFMIF Prototype Accelera- tor (LIPAc)”, to be presented at IAEA FEC’23, London, UK, Oct. 2023. https://www.iaea.org/events/fec2023" + ), + Arguments.of( + new BibEntry(StandardEntryType.TechReport) + .withField(StandardField.TITLE, "AF4.1.1 SRF Linac Engineering Design Report") + .withField(StandardField.NOTE, "Internal note") + .withField(StandardField.COMMENT, "[16] “AF4.1.1 SRF Linac Engineering Design Report”, Internal note."), + "16", + "“AF4.1.1 SRF Linac Engineering Design Report”, Internal note." + ) + ); + } + + @ParameterizedTest + @MethodSource + void references(BibEntry expectedEntry, String number, String reference) { + assertEquals(expectedEntry, bibliopgraphyFromPdfImporter.parseReference(number, reference)); + } +} diff --git a/src/test/resources/org/jabref/logic/importer/fileformat/tua3i2refpage.pdf b/src/test/resources/org/jabref/logic/importer/fileformat/tua3i2refpage.pdf new file mode 100644 index 0000000000000000000000000000000000000000..5f98c97a533e98ca601c66f3f08df2429b5532f3 GIT binary patch literal 43796 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℘G3#$eqR7QfL%*oJJJ}JnD3+YjeMKT$*x5jLjW< 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 BrI!Ez literal 0 HcmV?d00001 From 3af4e24c1195abf167b418f75222a37a865e48ff Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Sun, 7 Apr 2024 10:25:53 +0200 Subject: [PATCH 02/18] Remove unused method --- .../java/org/jabref/gui/actions/SimpleCommand.java | 4 ---- .../org/jabref/gui/menus/ChangeEntryTypeAction.java | 11 ----------- 2 files changed, 15 deletions(-) diff --git a/src/main/java/org/jabref/gui/actions/SimpleCommand.java b/src/main/java/org/jabref/gui/actions/SimpleCommand.java index 39e3b7ae1ad..89ef7ad100a 100644 --- a/src/main/java/org/jabref/gui/actions/SimpleCommand.java +++ b/src/main/java/org/jabref/gui/actions/SimpleCommand.java @@ -15,10 +15,6 @@ public abstract class SimpleCommand extends CommandBase { protected ReadOnlyStringWrapper statusMessage = new ReadOnlyStringWrapper(""); - public String getStatusMessage() { - return statusMessage.get(); - } - public ReadOnlyStringProperty statusMessageProperty() { return statusMessage.getReadOnlyProperty(); } diff --git a/src/main/java/org/jabref/gui/menus/ChangeEntryTypeAction.java b/src/main/java/org/jabref/gui/menus/ChangeEntryTypeAction.java index 97037881c5d..2b08e7b9ed3 100644 --- a/src/main/java/org/jabref/gui/menus/ChangeEntryTypeAction.java +++ b/src/main/java/org/jabref/gui/menus/ChangeEntryTypeAction.java @@ -4,7 +4,6 @@ import javax.swing.undo.UndoManager; -import javafx.beans.property.ReadOnlyStringProperty; import javafx.beans.property.ReadOnlyStringWrapper; import org.jabref.gui.EntryTypeView; @@ -36,14 +35,4 @@ public void execute() { .ifPresent(change -> compound.addEdit(new UndoableChangeType(change)))); undoManager.addEdit(compound); } - - @Override - public String getStatusMessage() { - return statusMessage.get(); - } - - @Override - public ReadOnlyStringProperty statusMessageProperty() { - return statusMessageProperty.getReadOnlyProperty(); - } } From cb360ef7f96a71873646a83667ec24469e05db88 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Sun, 7 Apr 2024 10:48:21 +0200 Subject: [PATCH 03/18] Wire in ExtractReferencesAction --- .../maintable/ExtractReferencesAction.java | 141 +++++++++++++++--- src/main/resources/l10n/JabRef_en.properties | 6 +- 2 files changed, 124 insertions(+), 23 deletions(-) diff --git a/src/main/java/org/jabref/gui/maintable/ExtractReferencesAction.java b/src/main/java/org/jabref/gui/maintable/ExtractReferencesAction.java index d46854c5f55..1f33c5f0708 100644 --- a/src/main/java/org/jabref/gui/maintable/ExtractReferencesAction.java +++ b/src/main/java/org/jabref/gui/maintable/ExtractReferencesAction.java @@ -1,9 +1,13 @@ package org.jabref.gui.maintable; import java.nio.file.Path; -import java.util.LinkedList; +import java.util.Iterator; import java.util.List; +import java.util.Optional; +import java.util.StringJoiner; import java.util.concurrent.Callable; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.jabref.gui.DialogService; import org.jabref.gui.StateManager; @@ -13,13 +17,22 @@ import org.jabref.gui.util.BackgroundTask; import org.jabref.gui.util.TaskExecutor; import org.jabref.logic.importer.ParserResult; +import org.jabref.logic.importer.fileformat.BibliopgraphyFromPdfImporter; import org.jabref.logic.importer.util.GrobidService; import org.jabref.logic.l10n.Localization; import org.jabref.logic.util.io.FileUtil; +import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.LinkedFile; +import org.jabref.model.entry.field.StandardField; import org.jabref.preferences.PreferencesService; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; + +/** + * SIDE EFFECT: Sets the "cites" field of the entry having the linked files + */ public class ExtractReferencesAction extends SimpleCommand { private final int FILES_LIMIT = 10; @@ -29,6 +42,7 @@ public class ExtractReferencesAction extends SimpleCommand { private final BibEntry entry; private final LinkedFile linkedFile; private final TaskExecutor taskExecutor; + private final BibliopgraphyFromPdfImporter bibliopgraphyFromPdfImporter; public ExtractReferencesAction(DialogService dialogService, StateManager stateManager, @@ -37,24 +51,30 @@ public ExtractReferencesAction(DialogService dialogService, this(dialogService, stateManager, preferencesService, null, null, taskExecutor); } - public ExtractReferencesAction(DialogService dialogService, - StateManager stateManager, - PreferencesService preferencesService, - BibEntry entry, - LinkedFile linkedFile, - TaskExecutor taskExecutor) { + /** + * Can be used to bind the action on a context menu in the linked file view (future work) + * + * @param entry the entry to handle (can be null) + * @param linkedFile the linked file (can be null) + */ + private ExtractReferencesAction(@NonNull DialogService dialogService, + @NonNull StateManager stateManager, + @NonNull PreferencesService preferencesService, + @Nullable BibEntry entry, + @Nullable LinkedFile linkedFile, + @NonNull TaskExecutor taskExecutor) { this.dialogService = dialogService; this.stateManager = stateManager; this.preferencesService = preferencesService; this.entry = entry; this.linkedFile = linkedFile; this.taskExecutor = taskExecutor; + bibliopgraphyFromPdfImporter = new BibliopgraphyFromPdfImporter(preferencesService.getCitationKeyPatternPreferences()); if (this.linkedFile == null) { this.executable.bind( ActionHelper.needsEntriesSelected(stateManager) .and(ActionHelper.hasLinkedFileForSelectedEntries(stateManager)) - .and(this.preferencesService.getGrobidPreferences().grobidEnabledProperty()) ); } else { this.setExecutable(true); @@ -68,34 +88,113 @@ public void execute() { private void extractReferences() { stateManager.getActiveDatabase().ifPresent(databaseContext -> { - List selectedEntries = new LinkedList<>(); + boolean online = this.preferencesService.getGrobidPreferences().isGrobidEnabled(); + + List selectedEntries; if (entry == null) { selectedEntries = stateManager.getSelectedEntries(); } else { - selectedEntries.add(entry); + selectedEntries = List.of(entry); } - List fileList = FileUtil.getListOfLinkedFiles(selectedEntries, databaseContext.getFileDirectories(preferencesService.getFilePreferences())); - if (fileList.size() > FILES_LIMIT) { - boolean continueOpening = dialogService.showConfirmationDialogAndWait(Localization.lang("Processing a large number of files"), - Localization.lang("You are about to process %0 files. Continue?", fileList.size()), - Localization.lang("Continue"), Localization.lang("Cancel")); - if (!continueOpening) { + Callable parserResultCallable; + if (online) { + parserResultCallable = getParserResultCallableOnline(databaseContext, selectedEntries); + if (parserResultCallable == null) { return; } + } else { + parserResultCallable = getParserResultCallableOffline(databaseContext, selectedEntries); } - - Callable parserResultCallable = () -> new ParserResult( - new GrobidService(this.preferencesService.getGrobidPreferences()).processReferences(fileList, preferencesService.getImportFormatPreferences()) - ); BackgroundTask task = BackgroundTask.wrap(parserResultCallable) .withInitialMessage(Localization.lang("Processing PDF(s)")); task.onFailure(dialogService::showErrorDialogAndWait); ImportEntriesDialog dialog = new ImportEntriesDialog(stateManager.getActiveDatabase().get(), task); - dialog.setTitle(Localization.lang("Extract References")); + String title; + if (online) { + title = Localization.lang("Extract References (online)"); + } else { + title = Localization.lang("Extract References (offline)"); + } + dialog.setTitle(title); dialogService.showCustomDialogAndWait(dialog); }); } + + private @NonNull Callable getParserResultCallableOffline(BibDatabaseContext databaseContext, List selectedEntries) { + return () -> { + BibEntry currentEntry = selectedEntries.getFirst(); + List fileList = FileUtil.getListOfLinkedFiles(selectedEntries, databaseContext.getFileDirectories(preferencesService.getFilePreferences())); + + // We need to have ParserResult handled at the importer, because it imports the meta data (library type, encoding, ...) + ParserResult result = bibliopgraphyFromPdfImporter.importDatabase(fileList.getFirst()); + + // subsequent files are just appended to result + Iterator fileListIterator = fileList.iterator(); + fileListIterator.next(); // skip first file + extractReferences(fileListIterator, result, currentEntry); + + // handle subsequent entries + Iterator selectedEntriesIterator = selectedEntries.iterator(); + selectedEntriesIterator.next(); // skip first entry + while (selectedEntriesIterator.hasNext()) { + currentEntry = selectedEntriesIterator.next(); + fileList = FileUtil.getListOfLinkedFiles(List.of(currentEntry), databaseContext.getFileDirectories(preferencesService.getFilePreferences())); + fileListIterator = fileList.iterator(); + extractReferences(fileListIterator, result, currentEntry); + } + + return result; + }; + } + + private void extractReferences(Iterator fileListIterator, ParserResult result, BibEntry currentEntry) { + while (fileListIterator.hasNext()) { + result.getDatabase().insertEntries(bibliopgraphyFromPdfImporter.importDatabase(fileListIterator.next()).getDatabase().getEntries()); + } + + StringJoiner cites = new StringJoiner(","); + int count = 0; + for (BibEntry importedEntry : result.getDatabase().getEntries()) { + count++; + Optional citationKey = importedEntry.getCitationKey(); + if (citationKey.isPresent()) { + cites.add(citationKey.get()); + } else { + String sourceCitationKey = currentEntry.getCitationKey().orElse("unknown"); + String newCitationKey; + // Could happen if no author and no year is present + // We use the number of the comment field (because there is no other way to get the number reliable) + Pattern pattern = Pattern.compile("^\\[(\\d+)\\]"); + Matcher matcher = pattern.matcher(importedEntry.getField(StandardField.COMMENT).orElse("")); + if (matcher.hasMatch()) { + newCitationKey = sourceCitationKey + "-" + matcher.group(1); + } else { + newCitationKey = sourceCitationKey + "-" + count; + } + importedEntry.setCitationKey(newCitationKey); + cites.add(newCitationKey); + } + } + currentEntry.setField(StandardField.CITES, cites.toString()); + } + + private @Nullable Callable getParserResultCallableOnline(BibDatabaseContext databaseContext, List selectedEntries) { + Callable parserResultCallable; + List fileList = FileUtil.getListOfLinkedFiles(selectedEntries, databaseContext.getFileDirectories(preferencesService.getFilePreferences())); + if (fileList.size() > FILES_LIMIT) { + boolean continueOpening = dialogService.showConfirmationDialogAndWait(Localization.lang("Processing a large number of files"), + Localization.lang("You are about to process %0 files. Continue?", fileList.size()), + Localization.lang("Continue"), Localization.lang("Cancel")); + if (!continueOpening) { + return null; + } + } + parserResultCallable = () -> new ParserResult( + new GrobidService(this.preferencesService.getGrobidPreferences()).processReferences(fileList, preferencesService.getImportFormatPreferences()) + ); + return parserResultCallable; + } } diff --git a/src/main/resources/l10n/JabRef_en.properties b/src/main/resources/l10n/JabRef_en.properties index 666996eb73a..4b9dbb499a5 100644 --- a/src/main/resources/l10n/JabRef_en.properties +++ b/src/main/resources/l10n/JabRef_en.properties @@ -329,8 +329,10 @@ Export\ preferences\ to\ file=Export preferences to file Export\ to\ clipboard=Export to clipboard Export\ to\ text\ file.=Export to text file. -Extract\ references\ from\ file=Extract references from file -Extract\ References=Extract References +Extract\ references\ from\ file\ (online)=Extract references from file (online) +Extract\ references\ from\ file\ (offline)=Extract references from file (offline) +Extract\ References\ (online)=Extract References (online) +Extract\ References\ (offline)=Extract References (offline) Processing\ PDF(s)=Processing PDF(s) Processing\ a\ large\ number\ of\ files=Processing a large number of files You\ are\ about\ to\ process\ %0\ files.\ Continue?=You are about to process %0 files. Continue? From 3a401beff495131f0ef1a4079f23aead960b7eec Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Sun, 7 Apr 2024 10:58:07 +0200 Subject: [PATCH 04/18] Add CHANGELOG.md entry --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 99d2ef06715..ea1a361f348 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv ### Added +- We added support for parsing the "References" section of IEEE papers offline. [#11156](https://github.com/JabRef/jabref/pull/11156) + ### Changed ### Fixed From a944a65652668f856a2bfb0e25f486f3d41d6ec9 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Sun, 7 Apr 2024 10:59:49 +0200 Subject: [PATCH 05/18] Fix reviewdog --- .../importer/fileformat/BibliopgraphyFromPdfImporter.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/jabref/logic/importer/fileformat/BibliopgraphyFromPdfImporter.java b/src/main/java/org/jabref/logic/importer/fileformat/BibliopgraphyFromPdfImporter.java index 38d2d3b20b8..69cc6b41a8a 100644 --- a/src/main/java/org/jabref/logic/importer/fileformat/BibliopgraphyFromPdfImporter.java +++ b/src/main/java/org/jabref/logic/importer/fileformat/BibliopgraphyFromPdfImporter.java @@ -103,8 +103,8 @@ private record IntermediateData(String number, String reference) { } /** - * In: "[1] ...\n...\n...[2]...\n...\n...\n[3]..." - * Out: List = ["[1] ...", "[2]...", "[3]..."] + * In: "[1] ...\n...\n...[2]...\n...\n...\n[3]..."
+ * Out: List<String> = ["[1] ...", "[2]...", "[3]..."] */ private List getEntriesFromPDFContent(String contents) { List referencesStrings = new ArrayList<>(); From c01dc0bd114a38e2ba51a78e8843bb9641892fac Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Sun, 7 Apr 2024 11:30:15 +0200 Subject: [PATCH 06/18] Switch "... (online)" and "... (offline)" in context meno --- .../org/jabref/gui/actions/ActionFactory.java | 7 +++--- .../jabref/gui/actions/StandardActions.java | 3 ++- .../maintable/ExtractReferencesAction.java | 22 +++++++++++++++---- .../jabref/gui/maintable/RightClickMenu.java | 14 +++++++++++- 4 files changed, 37 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/jabref/gui/actions/ActionFactory.java b/src/main/java/org/jabref/gui/actions/ActionFactory.java index 3deb6d068ef..2e0ce9b1be5 100644 --- a/src/main/java/org/jabref/gui/actions/ActionFactory.java +++ b/src/main/java/org/jabref/gui/actions/ActionFactory.java @@ -78,8 +78,11 @@ private static Label getAssociatedNode(MenuItem menuItem) { public MenuItem configureMenuItem(Action action, Command command, MenuItem menuItem) { ActionUtils.configureMenuItem(new JabRefAction(action, command, keyBindingRepository, Sources.FromMenu), menuItem); setGraphic(menuItem, action); + enableTooltips(command, menuItem); + return menuItem; + } - // Show tooltips + private static void enableTooltips(Command command, MenuItem menuItem) { if (command instanceof SimpleCommand simpleCommand) { EasyBind.subscribe( simpleCommand.statusMessageProperty(), @@ -96,8 +99,6 @@ public MenuItem configureMenuItem(Action action, Command command, MenuItem menuI } ); } - - return menuItem; } public MenuItem createMenuItem(Action action, Command command) { diff --git a/src/main/java/org/jabref/gui/actions/StandardActions.java b/src/main/java/org/jabref/gui/actions/StandardActions.java index 949dac28b29..92d05fa3d4e 100644 --- a/src/main/java/org/jabref/gui/actions/StandardActions.java +++ b/src/main/java/org/jabref/gui/actions/StandardActions.java @@ -32,7 +32,8 @@ public enum StandardActions implements Action { REBUILD_FULLTEXT_SEARCH_INDEX(Localization.lang("Rebuild fulltext search index"), IconTheme.JabRefIcons.FILE), REDOWNLOAD_MISSING_FILES(Localization.lang("Redownload missing files"), IconTheme.JabRefIcons.DOWNLOAD), OPEN_EXTERNAL_FILE(Localization.lang("Open file"), IconTheme.JabRefIcons.FILE, KeyBinding.OPEN_FILE), - EXTRACT_FILE_REFERENCES(Localization.lang("Extract references from file"), IconTheme.JabRefIcons.FILE_STAR), + EXTRACT_FILE_REFERENCES_ONLINE(Localization.lang("Extract references from file (online)"), IconTheme.JabRefIcons.FILE_STAR), + EXTRACT_FILE_REFERENCES_OFFLINE(Localization.lang("Extract references from file (offline)"), IconTheme.JabRefIcons.FILE_STAR), OPEN_URL(Localization.lang("Open URL or DOI"), IconTheme.JabRefIcons.WWW, KeyBinding.OPEN_URL_OR_DOI), SEARCH_SHORTSCIENCE(Localization.lang("Search ShortScience")), MERGE_WITH_FETCHED_ENTRY(Localization.lang("Get bibliographic data from %0", "DOI/ISBN/...")), diff --git a/src/main/java/org/jabref/gui/maintable/ExtractReferencesAction.java b/src/main/java/org/jabref/gui/maintable/ExtractReferencesAction.java index 1f33c5f0708..6092ecdf2a5 100644 --- a/src/main/java/org/jabref/gui/maintable/ExtractReferencesAction.java +++ b/src/main/java/org/jabref/gui/maintable/ExtractReferencesAction.java @@ -17,6 +17,7 @@ import org.jabref.gui.util.BackgroundTask; import org.jabref.gui.util.TaskExecutor; import org.jabref.logic.importer.ParserResult; +import org.jabref.logic.importer.fetcher.GrobidPreferences; import org.jabref.logic.importer.fileformat.BibliopgraphyFromPdfImporter; import org.jabref.logic.importer.util.GrobidService; import org.jabref.logic.l10n.Localization; @@ -32,23 +33,31 @@ /** * SIDE EFFECT: Sets the "cites" field of the entry having the linked files + * + * Mode choice A: online or offline + * Mode choice B: complete entry or single file (the latter is not implemented) + * + * The different modes should be implemented as sub classes. However, this was too complicated, thus we use variables at the constructor to parameterize this class. */ public class ExtractReferencesAction extends SimpleCommand { private final int FILES_LIMIT = 10; + private final boolean online; private final DialogService dialogService; private final StateManager stateManager; private final PreferencesService preferencesService; private final BibEntry entry; private final LinkedFile linkedFile; private final TaskExecutor taskExecutor; + private final BibliopgraphyFromPdfImporter bibliopgraphyFromPdfImporter; - public ExtractReferencesAction(DialogService dialogService, + public ExtractReferencesAction(boolean online, + DialogService dialogService, StateManager stateManager, PreferencesService preferencesService, TaskExecutor taskExecutor) { - this(dialogService, stateManager, preferencesService, null, null, taskExecutor); + this(online, dialogService, stateManager, preferencesService, null, null, taskExecutor); } /** @@ -57,12 +66,14 @@ public ExtractReferencesAction(DialogService dialogService, * @param entry the entry to handle (can be null) * @param linkedFile the linked file (can be null) */ - private ExtractReferencesAction(@NonNull DialogService dialogService, + private ExtractReferencesAction(boolean online, + @NonNull DialogService dialogService, @NonNull StateManager stateManager, @NonNull PreferencesService preferencesService, @Nullable BibEntry entry, @Nullable LinkedFile linkedFile, @NonNull TaskExecutor taskExecutor) { + this.online = online; this.dialogService = dialogService; this.stateManager = stateManager; this.preferencesService = preferencesService; @@ -71,6 +82,9 @@ private ExtractReferencesAction(@NonNull DialogService dialogService, this.taskExecutor = taskExecutor; bibliopgraphyFromPdfImporter = new BibliopgraphyFromPdfImporter(preferencesService.getCitationKeyPatternPreferences()); + String text; + GrobidPreferences grobidPreferences = preferencesService.getGrobidPreferences(); + if (this.linkedFile == null) { this.executable.bind( ActionHelper.needsEntriesSelected(stateManager) @@ -88,7 +102,7 @@ public void execute() { private void extractReferences() { stateManager.getActiveDatabase().ifPresent(databaseContext -> { - boolean online = this.preferencesService.getGrobidPreferences().isGrobidEnabled(); + assert online == this.preferencesService.getGrobidPreferences().isGrobidEnabled(); List selectedEntries; if (entry == null) { diff --git a/src/main/java/org/jabref/gui/maintable/RightClickMenu.java b/src/main/java/org/jabref/gui/maintable/RightClickMenu.java index aa8a5477902..f19ad726454 100644 --- a/src/main/java/org/jabref/gui/maintable/RightClickMenu.java +++ b/src/main/java/org/jabref/gui/maintable/RightClickMenu.java @@ -4,6 +4,7 @@ import javafx.scene.control.ContextMenu; import javafx.scene.control.Menu; +import javafx.scene.control.MenuItem; import javafx.scene.control.SeparatorMenuItem; import org.jabref.gui.ClipBoardManager; @@ -34,6 +35,8 @@ import org.jabref.preferences.PreferencesService; import org.jabref.preferences.PreviewPreferences; +import com.tobiasdiez.easybind.EasyBind; + public class RightClickMenu { public static ContextMenu create(BibEntryTableViewModel entry, @@ -50,6 +53,9 @@ public static ContextMenu create(BibEntryTableViewModel entry, ActionFactory factory = new ActionFactory(keyBindingRepository); ContextMenu contextMenu = new ContextMenu(); + MenuItem extractFileReferencesOnline = factory.createMenuItem(StandardActions.EXTRACT_FILE_REFERENCES_ONLINE, new ExtractReferencesAction(true, dialogService, stateManager, preferencesService, taskExecutor)); + MenuItem extractFileReferencesOffline = factory.createMenuItem(StandardActions.EXTRACT_FILE_REFERENCES_OFFLINE, new ExtractReferencesAction(false, dialogService, stateManager, preferencesService, taskExecutor)); + contextMenu.getItems().addAll( factory.createMenuItem(StandardActions.COPY, new EditAction(StandardActions.COPY, () -> libraryTab, stateManager, undoManager)), createCopySubMenu(factory, dialogService, stateManager, preferencesService, clipBoardManager, abbreviationRepository, taskExecutor), @@ -75,7 +81,8 @@ public static ContextMenu create(BibEntryTableViewModel entry, factory.createMenuItem(StandardActions.ATTACH_FILE_FROM_URL, new AttachFileFromURLAction(dialogService, stateManager, taskExecutor, preferencesService)), factory.createMenuItem(StandardActions.OPEN_FOLDER, new OpenFolderAction(dialogService, stateManager, preferencesService, taskExecutor)), factory.createMenuItem(StandardActions.OPEN_EXTERNAL_FILE, new OpenExternalFileAction(dialogService, stateManager, preferencesService, taskExecutor)), - factory.createMenuItem(StandardActions.EXTRACT_FILE_REFERENCES, new ExtractReferencesAction(dialogService, stateManager, preferencesService, taskExecutor)), + extractFileReferencesOnline, + extractFileReferencesOffline, factory.createMenuItem(StandardActions.OPEN_URL, new OpenUrlAction(dialogService, stateManager, preferencesService)), factory.createMenuItem(StandardActions.SEARCH_SHORTSCIENCE, new SearchShortScienceAction(dialogService, stateManager, preferencesService)), @@ -86,6 +93,11 @@ public static ContextMenu create(BibEntryTableViewModel entry, factory.createMenuItem(StandardActions.MERGE_WITH_FETCHED_ENTRY, new MergeWithFetchedEntryAction(dialogService, stateManager, taskExecutor, preferencesService, undoManager)) ); + EasyBind.subscribe(preferencesService.getGrobidPreferences().grobidEnabledProperty(), enabled -> { + extractFileReferencesOnline.setVisible(enabled); + extractFileReferencesOffline.setVisible(!enabled); + }); + return contextMenu; } From 07054710324a495ea8b5fee33438d20affa09d73 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Sun, 7 Apr 2024 11:34:33 +0200 Subject: [PATCH 07/18] Fix filename --- .../gui/maintable/ExtractReferencesAction.java | 10 +++++----- ...mporter.java => BibliographyFromPdfImporter.java} | 6 +++--- ...est.java => BibliographyFromPdfImporterTest.java} | 12 ++++++------ 3 files changed, 14 insertions(+), 14 deletions(-) rename src/main/java/org/jabref/logic/importer/fileformat/{BibliopgraphyFromPdfImporter.java => BibliographyFromPdfImporter.java} (98%) rename src/test/java/org/jabref/logic/importer/fileformat/{BibliopgraphyFromPdfImporterTest.java => BibliographyFromPdfImporterTest.java} (95%) diff --git a/src/main/java/org/jabref/gui/maintable/ExtractReferencesAction.java b/src/main/java/org/jabref/gui/maintable/ExtractReferencesAction.java index 6092ecdf2a5..95465165d4e 100644 --- a/src/main/java/org/jabref/gui/maintable/ExtractReferencesAction.java +++ b/src/main/java/org/jabref/gui/maintable/ExtractReferencesAction.java @@ -18,7 +18,7 @@ import org.jabref.gui.util.TaskExecutor; import org.jabref.logic.importer.ParserResult; import org.jabref.logic.importer.fetcher.GrobidPreferences; -import org.jabref.logic.importer.fileformat.BibliopgraphyFromPdfImporter; +import org.jabref.logic.importer.fileformat.BibliographyFromPdfImporter; import org.jabref.logic.importer.util.GrobidService; import org.jabref.logic.l10n.Localization; import org.jabref.logic.util.io.FileUtil; @@ -50,7 +50,7 @@ public class ExtractReferencesAction extends SimpleCommand { private final LinkedFile linkedFile; private final TaskExecutor taskExecutor; - private final BibliopgraphyFromPdfImporter bibliopgraphyFromPdfImporter; + private final BibliographyFromPdfImporter bibliographyFromPdfImporter; public ExtractReferencesAction(boolean online, DialogService dialogService, @@ -80,7 +80,7 @@ private ExtractReferencesAction(boolean online, this.entry = entry; this.linkedFile = linkedFile; this.taskExecutor = taskExecutor; - bibliopgraphyFromPdfImporter = new BibliopgraphyFromPdfImporter(preferencesService.getCitationKeyPatternPreferences()); + bibliographyFromPdfImporter = new BibliographyFromPdfImporter(preferencesService.getCitationKeyPatternPreferences()); String text; GrobidPreferences grobidPreferences = preferencesService.getGrobidPreferences(); @@ -143,7 +143,7 @@ private void extractReferences() { List fileList = FileUtil.getListOfLinkedFiles(selectedEntries, databaseContext.getFileDirectories(preferencesService.getFilePreferences())); // We need to have ParserResult handled at the importer, because it imports the meta data (library type, encoding, ...) - ParserResult result = bibliopgraphyFromPdfImporter.importDatabase(fileList.getFirst()); + ParserResult result = bibliographyFromPdfImporter.importDatabase(fileList.getFirst()); // subsequent files are just appended to result Iterator fileListIterator = fileList.iterator(); @@ -166,7 +166,7 @@ private void extractReferences() { private void extractReferences(Iterator fileListIterator, ParserResult result, BibEntry currentEntry) { while (fileListIterator.hasNext()) { - result.getDatabase().insertEntries(bibliopgraphyFromPdfImporter.importDatabase(fileListIterator.next()).getDatabase().getEntries()); + result.getDatabase().insertEntries(bibliographyFromPdfImporter.importDatabase(fileListIterator.next()).getDatabase().getEntries()); } StringJoiner cites = new StringJoiner(","); diff --git a/src/main/java/org/jabref/logic/importer/fileformat/BibliopgraphyFromPdfImporter.java b/src/main/java/org/jabref/logic/importer/fileformat/BibliographyFromPdfImporter.java similarity index 98% rename from src/main/java/org/jabref/logic/importer/fileformat/BibliopgraphyFromPdfImporter.java rename to src/main/java/org/jabref/logic/importer/fileformat/BibliographyFromPdfImporter.java index 69cc6b41a8a..06e69d03eda 100644 --- a/src/main/java/org/jabref/logic/importer/fileformat/BibliopgraphyFromPdfImporter.java +++ b/src/main/java/org/jabref/logic/importer/fileformat/BibliographyFromPdfImporter.java @@ -39,14 +39,14 @@ * Currently, IEEE two column format is supported. *

*/ -public class BibliopgraphyFromPdfImporter extends Importer { +public class BibliographyFromPdfImporter extends Importer { - private static final Logger LOGGER = LoggerFactory.getLogger(BibliopgraphyFromPdfImporter.class); + private static final Logger LOGGER = LoggerFactory.getLogger(BibliographyFromPdfImporter.class); private static final Pattern REFERENCE_PATTERN = Pattern.compile("\\[(\\d+)\\](.*?)(?=\\[|$)", Pattern.DOTALL); private final CitationKeyPatternPreferences citationKeyPatternPreferences; - public BibliopgraphyFromPdfImporter(CitationKeyPatternPreferences citationKeyPatternPreferences) { + public BibliographyFromPdfImporter(CitationKeyPatternPreferences citationKeyPatternPreferences) { this.citationKeyPatternPreferences = citationKeyPatternPreferences; } diff --git a/src/test/java/org/jabref/logic/importer/fileformat/BibliopgraphyFromPdfImporterTest.java b/src/test/java/org/jabref/logic/importer/fileformat/BibliographyFromPdfImporterTest.java similarity index 95% rename from src/test/java/org/jabref/logic/importer/fileformat/BibliopgraphyFromPdfImporterTest.java rename to src/test/java/org/jabref/logic/importer/fileformat/BibliographyFromPdfImporterTest.java index 0a4c0970d01..1bee96b208b 100644 --- a/src/test/java/org/jabref/logic/importer/fileformat/BibliopgraphyFromPdfImporterTest.java +++ b/src/test/java/org/jabref/logic/importer/fileformat/BibliographyFromPdfImporterTest.java @@ -20,9 +20,9 @@ import static org.jabref.logic.citationkeypattern.CitationKeyGenerator.DEFAULT_UNWANTED_CHARACTERS; import static org.junit.jupiter.api.Assertions.assertEquals; -class BibliopgraphyFromPdfImporterTest { +class BibliographyFromPdfImporterTest { - private BibliopgraphyFromPdfImporter bibliopgraphyFromPdfImporter; + private BibliographyFromPdfImporter bibliographyFromPdfImporter; @BeforeEach void setup() { @@ -38,13 +38,13 @@ void setup() { globalCitationKeyPattern, "", ','); - bibliopgraphyFromPdfImporter = new BibliopgraphyFromPdfImporter(citationKeyPatternPreferences); + bibliographyFromPdfImporter = new BibliographyFromPdfImporter(citationKeyPatternPreferences); } @Test void tua3i2refpage() throws Exception { - Path file = Path.of(BibliopgraphyFromPdfImporterTest.class.getResource("tua3i2refpage.pdf").toURI()); - ParserResult parserResult = bibliopgraphyFromPdfImporter.importDatabase(file); + Path file = Path.of(BibliographyFromPdfImporterTest.class.getResource("tua3i2refpage.pdf").toURI()); + ParserResult parserResult = bibliographyFromPdfImporter.importDatabase(file); BibEntry entry01 = new BibEntry(); assertEquals(List.of(entry01), parserResult.getDatabase().getEntries()); } @@ -151,6 +151,6 @@ static Stream references() { @ParameterizedTest @MethodSource void references(BibEntry expectedEntry, String number, String reference) { - assertEquals(expectedEntry, bibliopgraphyFromPdfImporter.parseReference(number, reference)); + assertEquals(expectedEntry, bibliographyFromPdfImporter.parseReference(number, reference)); } } From 37b7959f298d8fa87b5d401adae7b417603c6b17 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Sun, 7 Apr 2024 11:38:04 +0200 Subject: [PATCH 08/18] Make Patterns static --- .../BibliographyFromPdfImporter.java | 37 ++++++++++--------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/src/main/java/org/jabref/logic/importer/fileformat/BibliographyFromPdfImporter.java b/src/main/java/org/jabref/logic/importer/fileformat/BibliographyFromPdfImporter.java index 06e69d03eda..27f965cbd6a 100644 --- a/src/main/java/org/jabref/logic/importer/fileformat/BibliographyFromPdfImporter.java +++ b/src/main/java/org/jabref/logic/importer/fileformat/BibliographyFromPdfImporter.java @@ -44,6 +44,16 @@ public class BibliographyFromPdfImporter extends Importer { private static final Logger LOGGER = LoggerFactory.getLogger(BibliographyFromPdfImporter.class); private static final Pattern REFERENCE_PATTERN = Pattern.compile("\\[(\\d+)\\](.*?)(?=\\[|$)", Pattern.DOTALL); + private static final Pattern YEAR_AT_END = Pattern.compile(", (\\d{4})\\.$"); + private static final Pattern PAGES = Pattern.compile(", pp\\. (\\d+--?\\d+)\\.?(.*)"); + private static final Pattern PAGE = Pattern.compile(", p\\. (\\d+)(.*)"); + private static final Pattern MONTH_RANGE_AND_YEAR = Pattern.compile(", ([A-Z][a-z]{2,7}\\.?)-[A-Z][a-z]{2,7}\\.? (\\d+)(.*)"); + private static final Pattern MONTH_AND_YEAR = Pattern.compile(", ([A-Z][a-z]{2,7}\\.? \\d+),? ?(.*)"); + private static final Pattern VOLUME = Pattern.compile(", vol\\. (\\d+)(.*)"); + private static final Pattern NO = Pattern.compile(", no\\. (\\d+)(.*)"); + private static final Pattern AUTHORS_AND_TITLE_AT_BEGINNING = Pattern.compile("^([^“]+), “(.*?)”, "); + private static final Pattern TITLE = Pattern.compile("“(.*?)”, (.*)"); + private final CitationKeyPatternPreferences citationKeyPatternPreferences; public BibliographyFromPdfImporter(CitationKeyPatternPreferences citationKeyPatternPreferences) { @@ -155,23 +165,19 @@ BibEntry parseReference(String number, String reference) { // J. Knaster et al., “Overview of the IFMIF/EVEDA project”, Nucl. Fusion, vol. 57, p. 102016, 2017. // Y. Shimosaki et al., “Lattice design for 5 MeV – 125 mA CW RFQ operation in LIPAc”, in Proc. IPAC’19, Mel- bourne, Australia, May 2019, pp. 977-979 - Pattern yearAtEnd = Pattern.compile(", (\\d{4})\\.$"); - Matcher matcher = yearAtEnd.matcher(reference); + Matcher matcher = YEAR_AT_END.matcher(reference); if (matcher.find()) { result.setField(StandardField.YEAR, matcher.group(1)); reference = reference.substring(0, matcher.start()).trim(); } - Pattern pages = Pattern.compile(", pp\\. (\\d+--?\\d+)\\.?(.*)"); - reference = updateEntryAndReferenceIfMatches(reference, pages, result, StandardField.PAGES); + reference = updateEntryAndReferenceIfMatches(reference, PAGES, result, StandardField.PAGES); // J. Knaster et al., “Overview of the IFMIF/EVEDA project”, Nucl. Fusion, vol. 57, p. 102016 // Y. Shimosaki et al., “Lattice design for 5 MeV – 125 mA CW RFQ operation in LIPAc”, in Proc. IPAC’19, Mel- bourne, Australia, May 2019 - Pattern page = Pattern.compile(", p\\. (\\d+)(.*)"); - reference = updateEntryAndReferenceIfMatches(reference, page, result, StandardField.PAGES); + reference = updateEntryAndReferenceIfMatches(reference, PAGE, result, StandardField.PAGES); - Pattern monthRangeAndYear = Pattern.compile(", ([A-Z][a-z]{2,7}\\.?)-[A-Z][a-z]{2,7}\\.? (\\d+)(.*)"); - matcher = monthRangeAndYear.matcher(reference); + matcher = MONTH_RANGE_AND_YEAR.matcher(reference); if (matcher.find()) { // strip out second month reference = reference.substring(0, matcher.start()) + ", " + matcher.group(1) + " " + matcher.group(2) + matcher.group(3); @@ -179,8 +185,7 @@ BibEntry parseReference(String number, String reference) { // J. Knaster et al., “Overview of the IFMIF/EVEDA project”, Nucl. Fusion, vol. 57 // Y. Shimosaki et al., “Lattice design for 5 MeV – 125 mA CW RFQ operation in LIPAc”, in Proc. IPAC’19, Mel- bourne, Australia, May 2019 - Pattern monthAndYear = Pattern.compile(", ([A-Z][a-z]{2,7}\\.? \\d+),? ?(.*)"); - matcher = monthAndYear.matcher(reference); + matcher = MONTH_AND_YEAR.matcher(reference); if (matcher.find()) { Optional parsedDate = Date.parse(matcher.group(1)); if (parsedDate.isPresent()) { @@ -201,16 +206,13 @@ BibEntry parseReference(String number, String reference) { // J. Knaster et al., “Overview of the IFMIF/EVEDA project”, Nucl. Fusion, vol. 57 // Y. Shimosaki et al., “Lattice design for 5 MeV – 125 mA CW RFQ operation in LIPAc”, in Proc. IPAC’19, Mel- bourne, Australia - Pattern volume = Pattern.compile(", vol\\. (\\d+)(.*)"); - reference = updateEntryAndReferenceIfMatches(reference, volume, result, StandardField.VOLUME); + reference = updateEntryAndReferenceIfMatches(reference, VOLUME, result, StandardField.VOLUME); - Pattern no = Pattern.compile(", no\\. (\\d+)(.*)"); - reference = updateEntryAndReferenceIfMatches(reference, no, result, StandardField.NUMBER); + reference = updateEntryAndReferenceIfMatches(reference, NO, result, StandardField.NUMBER); // J. Knaster et al., “Overview of the IFMIF/EVEDA project”, Nucl. Fusion // Y. Shimosaki et al., “Lattice design for 5 MeV – 125 mA CW RFQ operation in LIPAc”, in Proc. IPAC’19, Mel- bourne, Australia - Pattern authorsAndTitleAtBeginning = Pattern.compile("^([^“]+), “(.*?)”, "); - matcher = authorsAndTitleAtBeginning.matcher(reference); + matcher = AUTHORS_AND_TITLE_AT_BEGINNING.matcher(reference); if (matcher.find()) { result.setField(StandardField.AUTHOR, matcher.group(1) .replace("- ", "") @@ -222,8 +224,7 @@ BibEntry parseReference(String number, String reference) { } else { // No authors present // Example: “AF4.1.1 SRF Linac Engineering Design Report”, Internal note. - Pattern title = Pattern.compile("“(.*?)”, (.*)"); - reference = updateEntryAndReferenceIfMatches(reference, title, result, StandardField.TITLE); + reference = updateEntryAndReferenceIfMatches(reference, TITLE, result, StandardField.TITLE); } // Nucl. Fusion From 2b77e60bb4a78150f5afc366ddd0e0c81f1461df Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Sun, 7 Apr 2024 12:24:22 +0200 Subject: [PATCH 09/18] Refine AuthorListParser for IEEE formatting Example: "I. Podadera, J. M. Carmona, A. Ibarra, and J. Molla" --- .../logic/importer/AuthorListParser.java | 31 +++++++++++++++++++ .../logic/importer/AuthorListParserTest.java | 13 +++++++- .../jabref/model/entry/AuthorListTest.java | 3 ++ 3 files changed, 46 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/jabref/logic/importer/AuthorListParser.java b/src/main/java/org/jabref/logic/importer/AuthorListParser.java index 6ef699d3db1..9e2b0649f2b 100644 --- a/src/main/java/org/jabref/logic/importer/AuthorListParser.java +++ b/src/main/java/org/jabref/logic/importer/AuthorListParser.java @@ -8,6 +8,9 @@ import java.util.Locale; import java.util.Optional; import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; import org.jabref.model.entry.Author; import org.jabref.model.entry.AuthorList; @@ -33,6 +36,9 @@ public class AuthorListParser { // Constant HashSet containing names of TeX special characters private static final Set TEX_NAMES = Set.of( "aa", "ae", "l", "o", "oe", "i", "AA", "AE", "L", "O", "OE", "j"); + + private static final Pattern STARTS_WITH_CAPITAL_LETTER_DOT = Pattern.compile("^[A-Z]\\. "); + /** * the raw bibtex author/editor field */ @@ -108,6 +114,8 @@ public AuthorList parse(@NonNull String listOfNames) { andOthersPresent = false; } + listOfNames = checkNamesCommaSeparated(listOfNames); + // Handle case names in order lastname, firstname and separated by "," // E.g., Ali Babar, M., Dingsøyr, T., Lago, P., van der Vliet, H. final boolean authorsContainAND = listOfNames.toUpperCase(Locale.ENGLISH).contains(" AND "); @@ -170,6 +178,29 @@ public AuthorList parse(@NonNull String listOfNames) { return AuthorList.of(authors); } + /** + * Handle cases names in order Firstname Lastname, separated by "," and a final ", and " + * E.g, "I. Podadera, J. M. Carmona, A. Ibarra, and J. Molla" + * + * @return the original or patched version of listOfNames + */ + private static String checkNamesCommaSeparated(String listOfNames) { + int commandAndPos = listOfNames.lastIndexOf(", and "); + if (commandAndPos >= 0) { + String lastContainedName = listOfNames.substring(commandAndPos + ", and ".length()); + Matcher matcher = STARTS_WITH_CAPITAL_LETTER_DOT.matcher(lastContainedName); + if (matcher.find()) { + String namesBeforeAndString = listOfNames.substring(0, commandAndPos); + String[] namesBeforeAnd = namesBeforeAndString.split(", "); + if (Arrays.stream(namesBeforeAnd).allMatch(name -> STARTS_WITH_CAPITAL_LETTER_DOT.matcher(name).find())) { + // Format found + listOfNames = Arrays.stream(namesBeforeAnd).collect(Collectors.joining(" and ", "", " and " + lastContainedName)); + } + } + } + return listOfNames; + } + /** * Parses one author name and returns preformatted information. * diff --git a/src/test/java/org/jabref/logic/importer/AuthorListParserTest.java b/src/test/java/org/jabref/logic/importer/AuthorListParserTest.java index 2943fc5fec6..aa983ab6ab3 100644 --- a/src/test/java/org/jabref/logic/importer/AuthorListParserTest.java +++ b/src/test/java/org/jabref/logic/importer/AuthorListParserTest.java @@ -11,6 +11,9 @@ import static org.junit.jupiter.api.Assertions.assertEquals; +/** + * Similar tests are available in {@link org.jabref.model.entry.AuthorListTest} + */ class AuthorListParserTest { AuthorListParser parser = new AuthorListParser(); @@ -50,7 +53,15 @@ private static Stream parseMultipleCorrectly() { new Author("Alexander", "A.", null, "Artemenko", null), Author.OTHERS ), - "Alexander Artemenko and others") + "Alexander Artemenko and others"), + Arguments.of( + AuthorList.of( + new Author("I.", "I.", null, "Podadera", null), + new Author("J. M.", "J. M.", null, "Carmona", null), + new Author("A.", "A.", null, "Ibarra", null), + new Author("J.", "J.", null, "Molla", null) + ), + "I. Podadera, J. M. Carmona, A. Ibarra, and J. Molla") ); } diff --git a/src/test/java/org/jabref/model/entry/AuthorListTest.java b/src/test/java/org/jabref/model/entry/AuthorListTest.java index 6f49c711bae..57617913ddd 100644 --- a/src/test/java/org/jabref/model/entry/AuthorListTest.java +++ b/src/test/java/org/jabref/model/entry/AuthorListTest.java @@ -14,6 +14,9 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +/** + * Other parsing tests are available in {@link org.jabref.logic.importer.AuthorListParserTest} + */ public class AuthorListTest { /* From 4912361c710c5e6b29f1727b2b4c22a8e7442821 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Sun, 7 Apr 2024 12:24:34 +0200 Subject: [PATCH 10/18] Fix authorlist --- .../importer/fileformat/BibliographyFromPdfImporter.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/jabref/logic/importer/fileformat/BibliographyFromPdfImporter.java b/src/main/java/org/jabref/logic/importer/fileformat/BibliographyFromPdfImporter.java index 27f965cbd6a..f1348f19ce6 100644 --- a/src/main/java/org/jabref/logic/importer/fileformat/BibliographyFromPdfImporter.java +++ b/src/main/java/org/jabref/logic/importer/fileformat/BibliographyFromPdfImporter.java @@ -21,6 +21,7 @@ import org.jabref.logic.util.StandardFileType; import org.jabref.logic.xmp.EncryptedPdfsNotSupportedException; import org.jabref.logic.xmp.XmpUtilReader; +import org.jabref.model.entry.AuthorList; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.Date; import org.jabref.model.entry.field.Field; @@ -214,9 +215,10 @@ BibEntry parseReference(String number, String reference) { // Y. Shimosaki et al., “Lattice design for 5 MeV – 125 mA CW RFQ operation in LIPAc”, in Proc. IPAC’19, Mel- bourne, Australia matcher = AUTHORS_AND_TITLE_AT_BEGINNING.matcher(reference); if (matcher.find()) { - result.setField(StandardField.AUTHOR, matcher.group(1) - .replace("- ", "") - .replaceAll("et al\\.?", "and others")); + String authors = matcher.group(1) + .replace("- ", "") + .replaceAll("et al\\.?", "and others"); + result.setField(StandardField.AUTHOR, AuthorList.fixAuthorFirstNameFirst(authors)); result.setField(StandardField.TITLE, matcher.group(2) .replace("- ", "") .replaceAll("et al\\.?", "and others")); From ca33931a8487a0f2e649b437830e3bfe371ca817 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Sun, 7 Apr 2024 12:56:22 +0200 Subject: [PATCH 11/18] Complete test for tua3i2refpage() --- .../BibliographyFromPdfImporterTest.java | 254 +++++++++++++----- 1 file changed, 186 insertions(+), 68 deletions(-) diff --git a/src/test/java/org/jabref/logic/importer/fileformat/BibliographyFromPdfImporterTest.java b/src/test/java/org/jabref/logic/importer/fileformat/BibliographyFromPdfImporterTest.java index 1bee96b208b..9318d99e977 100644 --- a/src/test/java/org/jabref/logic/importer/fileformat/BibliographyFromPdfImporterTest.java +++ b/src/test/java/org/jabref/logic/importer/fileformat/BibliographyFromPdfImporterTest.java @@ -22,6 +22,67 @@ class BibliographyFromPdfImporterTest { + private static final BibEntry KNASTER_2017 = new BibEntry(StandardEntryType.Article) + .withField(StandardField.AUTHOR, "J. Knaster and others") + .withField(StandardField.TITLE, "Overview of the IFMIF/EVEDA project") + .withField(StandardField.JOURNAL, "Nucl. Fusion") + .withField(StandardField.VOLUME, "57") + .withField(StandardField.PAGES, "102016") + .withField(StandardField.YEAR, "2017") + .withField(StandardField.DOI, "10.1088/1741-4326/aa6a6a") + .withField(StandardField.COMMENT, "[1] J. Knaster et al., “Overview of the IFMIF/EVEDA project”, Nucl. Fusion, vol. 57, p. 102016, 2017. doi:10.1088/ 1741-4326/aa6a6a"); + private static final BibEntry SHIMOSAKI_2019 = new BibEntry(StandardEntryType.InProceedings) + .withField(StandardField.AUTHOR, "Y. Shimosaki and others") + .withField(StandardField.TITLE, "Lattice design for 5 MeV – 125 mA CW RFQ operation in LIPAc") + .withField(StandardField.BOOKTITLE, "Proc. IPAC’19, Melbourne, Australia") + .withField(StandardField.MONTH, "#may#") + .withField(StandardField.YEAR, "2019") + .withField(StandardField.PAGES, "977-979") + .withField(StandardField.DOI, "10.18429/JACoW-IPAC2019-MOPTS051") + .withField(StandardField.COMMENT, "[3] Y. Shimosaki et al., “Lattice design for 5 MeV – 125 mA CW RFQ operation in LIPAc”, in Proc. IPAC’19, Mel- bourne, Australia, May 2019, pp. 977-979. doi:10.18429/ JACoW-IPAC2019-MOPTS051"); + private static final BibEntry BELLAN_2021 = new BibEntry(StandardEntryType.InProceedings) + .withField(StandardField.AUTHOR, "L. Bellan and others") + .withField(StandardField.TITLE, "Acceleration of the high current deuteron beam through the IFMIF-EVEDA beam dynamics performances") + .withField(StandardField.BOOKTITLE, "Proc. HB’21, Batavia, IL, USA") + .withField(StandardField.MONTH, "#oct#") + .withField(StandardField.YEAR, "2021") + .withField(StandardField.PAGES, "197-202") + .withField(StandardField.DOI, "10.18429/JACoW-HB2021-WEDC2") + .withField(StandardField.COMMENT, "[6] L. Bellan et al., “Acceleration of the high current deuteron beam through the IFMIF-EVEDA beam dynamics perfor- mances”, in Proc. HB’21, Batavia, IL, USA, Oct. 2021, pp. 197-202. doi:10.18429/JACoW-HB2021-WEDC2"); + private static final BibEntry MASUDA_2022 = new BibEntry(StandardEntryType.InProceedings) + .withField(StandardField.AUTHOR, "K. Masuda and others") + .withField(StandardField.TITLE, "Commissioning of IFMIF Prototype Accelerator towards CW operation") + .withField(StandardField.BOOKTITLE, "Proc. LINAC’22, Liverpool, UK") + .withField(StandardField.MONTH, "#aug#") + .withField(StandardField.YEAR, "2022") + .withField(StandardField.PAGES, "319-323") + .withField(StandardField.DOI, "10.18429/JACoW-LINAC2022-TU2AA04") + .withField(StandardField.COMMENT, "[7] K. Masuda et al., “Commissioning of IFMIF Prototype Ac- celerator towards CW operation”, in Proc. LINAC’22, Liv- erpool, UK, Aug.-Sep. 2022, pp. 319-323. doi:10.18429/ JACoW-LINAC2022-TU2AA04"); + private static final BibEntry PODADERA_2012 = new BibEntry(StandardEntryType.InProceedings) + .withField(StandardField.AUTHOR, "I. Podadera and J. M. Carmona and A. Ibarra and J. Molla") + .withField(StandardField.TITLE, "Beam position monitor development for LIPAc") + .withField(StandardField.BOOKTITLE, "th 8th DITANET Topical Workshop on Beam Position Monitors, CERN, Geneva, Switzreland") + .withField(StandardField.MONTH, "#jan#") + .withField(StandardField.YEAR, "2012") + .withField(StandardField.COMMENT, "[11] I. Podadera, J. M. Carmona, A. Ibarra, and J. Molla, “Beam position monitor development for LIPAc”, presented at th 8th DITANET Topical Workshop on Beam Position Monitors, CERN, Geneva, Switzreland, Jan. 2012."); + private static final BibEntry AKAGI_2023 = new BibEntry(StandardEntryType.InProceedings) + .withField(StandardField.AUTHOR, "T. Akagi and others") + .withField(StandardField.TITLE, "Achievement of high-current continuouswave deuteron injector for Linear IFMIF Prototype Accelerator (LIPAc)") + .withField(StandardField.BOOKTITLE, "IAEA FEC’23, London, UK, https://www.iaea.org/events/fec2023") + .withField(StandardField.MONTH, "#oct#") + .withField(StandardField.YEAR, "2023") + .withField(StandardField.COMMENT, "[15] T. Akagi et al., “Achievement of high-current continuous- wave deuteron injector for Linear IFMIF Prototype Accelera- tor (LIPAc)”, to be presented at IAEA FEC’23, London, UK, Oct. 2023. https://www.iaea.org/events/fec2023"); + private static final BibEntry INTERNAL_NOTE = new BibEntry(StandardEntryType.TechReport) + .withField(StandardField.TITLE, "AF4.1.1 SRF Linac Engineering Design Report") + .withField(StandardField.NOTE, "Internal note") + .withField(StandardField.COMMENT, "[16] “AF4.1.1 SRF Linac Engineering Design Report”, Internal note."); + private static final BibEntry KWON_2023 = new BibEntry(StandardEntryType.InProceedings) + .withField(StandardField.AUTHOR, "S. Kwon and others") + .withField(StandardField.TITLE, "High beam current operation with beam di-agnostics at LIPAc") + .withField(StandardField.BOOKTITLE, "HB’23, Geneva, Switzerland, paper FRC1I2, this conference") + .withField(StandardField.MONTH, "#oct#") + .withField(StandardField.YEAR, "2023") + .withField(StandardField.COMMENT, "[14] S. Kwon et al., “High beam current operation with beam di-agnostics at LIPAc”, presented at HB’23, Geneva, Switzer- land, Oct. 2023, paper FRC1I2, this conference."); private BibliographyFromPdfImporter bibliographyFromPdfImporter; @BeforeEach @@ -45,103 +106,160 @@ void setup() { void tua3i2refpage() throws Exception { Path file = Path.of(BibliographyFromPdfImporterTest.class.getResource("tua3i2refpage.pdf").toURI()); ParserResult parserResult = bibliographyFromPdfImporter.importDatabase(file); - BibEntry entry01 = new BibEntry(); - assertEquals(List.of(entry01), parserResult.getDatabase().getEntries()); + BibEntry entry02 = new BibEntry(StandardEntryType.Article) + .withCitationKey("Kondo2020") + .withField(StandardField.AUTHOR, "K. Kondo and others") + .withField(StandardField.TITLE, "Validation of the Linear IFMIF Prototype Accelerator (LIPAc) in Rokkasho") + .withField(StandardField.JOURNAL, "Fusion Eng. Des") // TODO: Final dot should be kept + .withField(StandardField.VOLUME, "153") + .withField(StandardField.YEAR, "2020") + .withField(StandardField.PAGES, "111503") + .withField(StandardField.DOI, "10.1016/j.fusengdes.2020.111503") + .withField(StandardField.COMMENT, "[2] K. Kondo et al., “Validation of the Linear IFMIF Prototype Accelerator (LIPAc) in Rokkasho”, Fusion Eng. Des., vol. 153, p. 111503, 2020. doi:10.1016/j.fusengdes.2020. 111503"); + + BibEntry entry04 = new BibEntry(StandardEntryType.InProceedings) + .withCitationKey("Devanz2017") + .withField(StandardField.AUTHOR, "G. Devanz and others") + .withField(StandardField.TITLE, "Manufacturing and validation tests of IFMIF low-beta HWRs") + .withField(StandardField.BOOKTITLE, "Proc. IPAC’17, Copenhagen, Denmark") + .withField(StandardField.MONTH, "#may#") + .withField(StandardField.YEAR, "2017") + .withField(StandardField.PAGES, "942-944") + .withField(StandardField.DOI, "10.18429/JACoW-IPAC2017-MOPVA039") + .withField(StandardField.COMMENT, "[4] G. Devanz et al., “Manufacturing and validation tests of IFMIF low-beta HWRs”, in Proc. IPAC’17, Copen- hagen, Denmark, May 2017, pp. 942-944. doi:10.18429/ JACoW-IPAC2017-MOPVA039"); + + BibEntry entry05 = new BibEntry(StandardEntryType.Article) + .withCitationKey("Branas2018") + .withField(StandardField.AUTHOR, "B. Brañas and others") + .withField(StandardField.TITLE, "The LIPAc Beam Dump") + .withField(StandardField.JOURNAL, "Fusion Eng. Des") + .withField(StandardField.VOLUME, "127") + .withField(StandardField.PAGES, "127-138") + .withField(StandardField.YEAR, "2018") + .withField(StandardField.DOI, "10.1016/j.fusengdes.2017.12.018") + .withField(StandardField.COMMENT, "[5] B. Brañas et al., “The LIPAc Beam Dump”, Fusion Eng. Des., vol. 127, pp. 127-138, 2018. doi:10.1016/j.fusengdes. 2017.12.018"); + + BibEntry entry08 = new BibEntry(StandardEntryType.InProceedings) + .withCitationKey("Scantamburlo2023") + .withField(StandardField.AUTHOR, "F. Scantamburlo and others") + .withField(StandardField.TITLE, "Linear IFMIF Prototype Accelera-tor (LIPAc) Radio Frequency Quadrupole’s (RFQ) RF couplers enhancement towards CW operation at nominal voltage") + .withField(StandardField.BOOKTITLE, "Proc. ISFNT’23, Las Palmas de Gran Canaria, Spain.") + .withField(StandardField.MONTH, "#sep#") + .withField(StandardField.YEAR, "2023") + .withField(StandardField.COMMENT, "[8] F. Scantamburlo et al., “Linear IFMIF Prototype Accelera-tor (LIPAc) Radio Frequency Quadrupole’s (RFQ) RF couplers enhancement towards CW operation at nominal voltage”, in Proc. ISFNT’23, Sep. 2023, Las Palmas de Gran Canaria, Spain."); + + BibEntry entry09 = new BibEntry(StandardEntryType.InProceedings) + .withCitationKey("Franco2023") + .withField(StandardField.AUTHOR, "A. De Franco and others") + .withField(StandardField.BOOKTITLE, "Proc. IPAC’23, Venice, Italy") + .withField(StandardField.TITLE, "RF conditioning towards continuous wave of the FRQ of the Linear IFMIF Prototype Accelerator") + .withField(StandardField.PAGES, "2345-2348") + .withField(StandardField.MONTH, "#may#") + .withField(StandardField.YEAR, "2023") + .withField(StandardField.DOI, "10.18429/JACoW-IPAC2023-TUPM065") + .withField(StandardField.COMMENT, "[9] A. De Franco et al., “RF conditioning towards continuous wave of the FRQ of the Linear IFMIF Prototype Accelerator”, in Proc. IPAC’23, Venice, Italy, May 2023, pp. 2345-2348. doi:10.18429/JACoW-IPAC2023-TUPM065"); + + BibEntry entry10 = new BibEntry(StandardEntryType.InProceedings) + .withCitationKey("Hirosawa") + .withField(StandardField.AUTHOR, "K. Hirosawa and others") + .withField(StandardField.BOOKTITLE, "Proc. PASJ’23, 2023, Japan.") + .withField(StandardField.TITLE, "High-Power RF tests of repaired circulator for LIPAc RFQ") + .withField(StandardField.COMMENT, "[10] K. Hirosawa et al., “High-Power RF tests of repaired circu- lator for LIPAc RFQ”, in Proc. PASJ’23, 2023, Japan."); + + BibEntry entry12 = new BibEntry(StandardEntryType.InProceedings) + .withCitationKey("Podadera2019") + .withField(StandardField.AUTHOR, "I. Podadera and others") + .withField(StandardField.TITLE, "Beam commissioning of beam position and phase monitors for LIPAc") + .withField(StandardField.BOOKTITLE, "Proc. IBIC’19, Malmö, Sweden") + .withField(StandardField.PAGES, "534-538") + .withField(StandardField.MONTH, "#sep#") + .withField(StandardField.YEAR, "2019") + .withField(StandardField.DOI, "10.18429/JACoW-IBIC2019-WEPP013") + .withField(StandardField.COMMENT, "[12] I. Podadera et al., “Beam commissioning of beam posi- tion and phase monitors for LIPAc”, in Proc. IBIC’19, Malmö, Sweden, Sep. 2019, pp. 534-538. doi:10.18429/ JACoW-IBIC2019-WEPP013"); + + BibEntry entry13 = new BibEntry(StandardEntryType.Article) + .withCitationKey("Kondo2021") + .withField(StandardField.AUTHOR, "K. Kondo and others") + .withField(StandardField.TITLE, "Neutron production measurement in the 125 mA 5 MeV Deuteron beam commissioning of Linear IFMIF Prototype Accelerator (LIPAc) RFQ") + .withField(StandardField.JOURNAL, "Nucl. Fusion") + .withField(StandardField.VOLUME, "61") + .withField(StandardField.NUMBER, "1") + .withField(StandardField.PAGES, "116002") + .withField(StandardField.YEAR, "2021") + .withField(StandardField.DOI, "82310.1088/1741-4326/ac233c") + .withField(StandardField.COMMENT, "[13] K. Kondo et al., “Neutron production measurement in the 125 mA 5 MeV Deuteron beam commissioning of Linear IFMIF Prototype Accelerator (LIPAc) RFQ”, Nucl. Fusion, vol. 61, no. 1, p. 116002, 2021. doi:82310.1088/1741-4326/ ac233c"); + + BibEntry entry17 = new BibEntry(StandardEntryType.InProceedings) + .withCitationKey("Bellan2021a") + .withField(StandardField.AUTHOR, "L. Bellan and others") + .withField(StandardField.BOOKTITLE, "Proc. ICIS’21, TRIUMF, Vancouver, BC, Canada, https://indico.cern.ch/event/1027296/") + .withField(StandardField.COMMENT, "[17] L. Bellan et al., “Extraction and low energy beam transport models used for the IFMIF/EVEDA RFQ commissioning”, in Proc. ICIS’21, TRIUMF, Vancouver, BC, Canada, Sep. 2021. https://indico.cern.ch/event/1027296/") + .withField(StandardField.MONTH, "#sep#") + .withField(StandardField.TITLE, "Extraction and low energy beam transport models used for the IFMIF/EVEDA RFQ commissioning") + .withField(StandardField.YEAR, "2021"); + + // We use the existing test entries, but add a citation key (which is added by the importer) + // We need to clone to keep the static entries unmodified + assertEquals(List.of( + ((BibEntry) KNASTER_2017.clone()).withCitationKey("Knaster2017"), + entry02, + ((BibEntry) SHIMOSAKI_2019.clone()).withCitationKey("Shimosaki2019"), + entry04, + entry05, + ((BibEntry) BELLAN_2021.clone()).withCitationKey("Bellan2021"), + ((BibEntry) MASUDA_2022.clone()).withCitationKey("Masuda2022"), + entry08, + entry09, + entry10, + ((BibEntry) PODADERA_2012.clone()).withCitationKey("Podadera2012"), + entry12, + entry13, + ((BibEntry) KWON_2023.clone()).withCitationKey("Kwon2023"), + ((BibEntry) AKAGI_2023.clone()).withCitationKey("Akagi2023"), + ((BibEntry) INTERNAL_NOTE.clone()), + entry17), + parserResult.getDatabase().getEntries()); } static Stream references() { return Stream.of( Arguments.of( - new BibEntry(StandardEntryType.Article) - .withField(StandardField.AUTHOR, "J. Knaster and others") - .withField(StandardField.TITLE, "Overview of the IFMIF/EVEDA project") - .withField(StandardField.JOURNAL, "Nucl. Fusion") - .withField(StandardField.VOLUME, "57") - .withField(StandardField.PAGES, "102016") - .withField(StandardField.YEAR, "2017") - .withField(StandardField.DOI, "10.1088/1741-4326/aa6a6a") - .withField(StandardField.COMMENT, "[1] J. Knaster et al., “Overview of the IFMIF/EVEDA project”, Nucl. Fusion, vol. 57, p. 102016, 2017. doi:10.1088/ 1741-4326/aa6a6a"), + KNASTER_2017, "1", "J. Knaster et al., “Overview of the IFMIF/EVEDA project”, Nucl. Fusion, vol. 57, p. 102016, 2017. doi:10.1088/ 1741-4326/aa6a6a" ), Arguments.of( - new BibEntry(StandardEntryType.InProceedings) - .withField(StandardField.AUTHOR, "Y. Shimosaki and others") - .withField(StandardField.TITLE, "Lattice design for 5 MeV – 125 mA CW RFQ operation in LIPAc") - .withField(StandardField.BOOKTITLE, "Proc. IPAC’19, Melbourne, Australia") - .withField(StandardField.MONTH, "#may#") - .withField(StandardField.YEAR, "2019") - .withField(StandardField.PAGES, "977-979") - .withField(StandardField.DOI, "10.18429/JACoW-IPAC2019-MOPTS051") - .withField(StandardField.COMMENT, "[2] Y. Shimosaki et al., “Lattice design for 5 MeV – 125 mA CW RFQ operation in LIPAc”, in Proc. IPAC’19, Mel- bourne, Australia, May 2019, pp. 977-979. doi:10.18429/ JACoW-IPAC2019-MOPTS051"), - "2", + SHIMOSAKI_2019, + "3", "Y. Shimosaki et al., “Lattice design for 5 MeV – 125 mA CW RFQ operation in LIPAc”, in Proc. IPAC’19, Mel- bourne, Australia, May 2019, pp. 977-979. doi:10.18429/ JACoW-IPAC2019-MOPTS051" ), Arguments.of( - new BibEntry(StandardEntryType.InProceedings) - .withField(StandardField.AUTHOR, "L. Bellan and others") - .withField(StandardField.TITLE, "Acceleration of the high current deuteron beam through the IFMIF-EVEDA beam dynamics performances") - .withField(StandardField.BOOKTITLE, "Proc. HB’21, Batavia, IL, USA") - .withField(StandardField.MONTH, "#oct#") - .withField(StandardField.YEAR, "2021") - .withField(StandardField.PAGES, "197-202") - .withField(StandardField.DOI, "10.18429/JACoW-HB2021-WEDC2") - .withField(StandardField.COMMENT, "[5] L. Bellan et al., “Acceleration of the high current deuteron beam through the IFMIF-EVEDA beam dynamics perfor- mances”, in Proc. HB’21, Batavia, IL, USA, Oct. 2021, pp. 197-202. doi:10.18429/JACoW-HB2021-WEDC2"), - "5", + BELLAN_2021, + "6", "L. Bellan et al., “Acceleration of the high current deuteron beam through the IFMIF-EVEDA beam dynamics perfor- mances”, in Proc. HB’21, Batavia, IL, USA, Oct. 2021, pp. 197-202. doi:10.18429/JACoW-HB2021-WEDC2" ), Arguments.of( - new BibEntry(StandardEntryType.InProceedings) - .withField(StandardField.AUTHOR, "K. Masuda and others") - .withField(StandardField.TITLE, "Commissioning of IFMIF Prototype Accelerator towards CW operation") - .withField(StandardField.BOOKTITLE, "Proc. LINAC’22, Liverpool, UK") - .withField(StandardField.MONTH, "#aug#") - .withField(StandardField.YEAR, "2022") - .withField(StandardField.PAGES, "319-323") - .withField(StandardField.DOI, "10.18429/JACoW-LINAC2022-TU2AA04") - .withField(StandardField.COMMENT, "[6] K. Masuda et al., “Commissioning of IFMIF Prototype Ac- celerator towards CW operation”, in Proc. LINAC’22, Liv- erpool, UK, Aug.-Sep. 2022, pp. 319-323. doi:10.18429/ JACoW-LINAC2022-TU2AA04"), - - "6", + MASUDA_2022, + "7", "K. Masuda et al., “Commissioning of IFMIF Prototype Ac- celerator towards CW operation”, in Proc. LINAC’22, Liv- erpool, UK, Aug.-Sep. 2022, pp. 319-323. doi:10.18429/ JACoW-LINAC2022-TU2AA04" ), Arguments.of( - new BibEntry(StandardEntryType.InProceedings) - .withField(StandardField.AUTHOR, "I. Podadera, J. M. Carmona, A. Ibarra, and J. Molla") - .withField(StandardField.TITLE, "Beam position monitor development for LIPAc") - .withField(StandardField.BOOKTITLE, "th 8th DITANET Topical Workshop on Beam Position Monitors, CERN, Geneva, Switzreland") - .withField(StandardField.MONTH, "#jan#") - .withField(StandardField.YEAR, "2012") - .withField(StandardField.COMMENT, "[10] I. Podadera, J. M. Carmona, A. Ibarra, and J. Molla, “Beam position monitor development for LIPAc”, presented at th 8th DITANET Topical Workshop on Beam Position Monitors, CERN, Geneva, Switzreland, Jan. 2012."), - "10", + PODADERA_2012, + "11", "I. Podadera, J. M. Carmona, A. Ibarra, and J. Molla, “Beam position monitor development for LIPAc”, presented at th 8th DITANET Topical Workshop on Beam Position Monitors, CERN, Geneva, Switzreland, Jan. 2012." ), Arguments.of( - new BibEntry(StandardEntryType.InProceedings) - .withField(StandardField.AUTHOR, "S. Kwon and others") - .withField(StandardField.TITLE, "High beam current operation with beam di-agnostics at LIPAc") - .withField(StandardField.BOOKTITLE, "HB’23, Geneva, Switzerland, paper FRC1I2, this conference") - .withField(StandardField.MONTH, "#oct#") - .withField(StandardField.YEAR, "2023") - .withField(StandardField.COMMENT, "[14] S. Kwon et al., “High beam current operation with beam di-agnostics at LIPAc”, presented at HB’23, Geneva, Switzer- land, Oct. 2023, paper FRC1I2, this conference."), + KWON_2023, "14", "S. Kwon et al., “High beam current operation with beam di-agnostics at LIPAc”, presented at HB’23, Geneva, Switzer- land, Oct. 2023, paper FRC1I2, this conference." ), Arguments.of( - new BibEntry(StandardEntryType.InProceedings) - .withField(StandardField.AUTHOR, "T. Akagi and others") - .withField(StandardField.TITLE, "Achievement of high-current continuouswave deuteron injector for Linear IFMIF Prototype Accelerator (LIPAc)") - .withField(StandardField.BOOKTITLE, "IAEA FEC’23, London, UK, https://www.iaea.org/events/fec2023") - .withField(StandardField.MONTH, "#oct#") - .withField(StandardField.YEAR, "2023") - .withField(StandardField.COMMENT, "[15] T. Akagi et al., “Achievement of high-current continuous- wave deuteron injector for Linear IFMIF Prototype Accelera- tor (LIPAc)”, to be presented at IAEA FEC’23, London, UK, Oct. 2023. https://www.iaea.org/events/fec2023"), + AKAGI_2023, "15", "T. Akagi et al., “Achievement of high-current continuous- wave deuteron injector for Linear IFMIF Prototype Accelera- tor (LIPAc)”, to be presented at IAEA FEC’23, London, UK, Oct. 2023. https://www.iaea.org/events/fec2023" ), Arguments.of( - new BibEntry(StandardEntryType.TechReport) - .withField(StandardField.TITLE, "AF4.1.1 SRF Linac Engineering Design Report") - .withField(StandardField.NOTE, "Internal note") - .withField(StandardField.COMMENT, "[16] “AF4.1.1 SRF Linac Engineering Design Report”, Internal note."), + INTERNAL_NOTE, "16", "“AF4.1.1 SRF Linac Engineering Design Report”, Internal note." ) From ce430fa9144f24890d61df3ab514deb6585f3a2b Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Sun, 7 Apr 2024 12:59:00 +0200 Subject: [PATCH 12/18] Use "Optional" instead of null --- .../gui/maintable/ExtractReferencesAction.java | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/jabref/gui/maintable/ExtractReferencesAction.java b/src/main/java/org/jabref/gui/maintable/ExtractReferencesAction.java index 95465165d4e..6a422f04224 100644 --- a/src/main/java/org/jabref/gui/maintable/ExtractReferencesAction.java +++ b/src/main/java/org/jabref/gui/maintable/ExtractReferencesAction.java @@ -113,10 +113,11 @@ private void extractReferences() { Callable parserResultCallable; if (online) { - parserResultCallable = getParserResultCallableOnline(databaseContext, selectedEntries); - if (parserResultCallable == null) { + Optional> parserResultCallableOnline = getParserResultCallableOnline(databaseContext, selectedEntries); + if (parserResultCallableOnline.isEmpty()) { return; } + parserResultCallable = parserResultCallableOnline.get(); } else { parserResultCallable = getParserResultCallableOffline(databaseContext, selectedEntries); } @@ -195,20 +196,18 @@ private void extractReferences(Iterator fileListIterator, ParserResult res currentEntry.setField(StandardField.CITES, cites.toString()); } - private @Nullable Callable getParserResultCallableOnline(BibDatabaseContext databaseContext, List selectedEntries) { - Callable parserResultCallable; + private Optional> getParserResultCallableOnline(BibDatabaseContext databaseContext, List selectedEntries) { List fileList = FileUtil.getListOfLinkedFiles(selectedEntries, databaseContext.getFileDirectories(preferencesService.getFilePreferences())); if (fileList.size() > FILES_LIMIT) { boolean continueOpening = dialogService.showConfirmationDialogAndWait(Localization.lang("Processing a large number of files"), Localization.lang("You are about to process %0 files. Continue?", fileList.size()), Localization.lang("Continue"), Localization.lang("Cancel")); if (!continueOpening) { - return null; + return Optional.empty(); } } - parserResultCallable = () -> new ParserResult( + return Optional.of(() -> new ParserResult( new GrobidService(this.preferencesService.getGrobidPreferences()).processReferences(fileList, preferencesService.getImportFormatPreferences()) - ); - return parserResultCallable; + )); } } From 7adb33471f3985ceb7a2cf0e80e34f8dfad444eb Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Sun, 7 Apr 2024 13:05:01 +0200 Subject: [PATCH 13/18] Rewrite --- .../gui/maintable/ExtractReferencesAction.java | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/jabref/gui/maintable/ExtractReferencesAction.java b/src/main/java/org/jabref/gui/maintable/ExtractReferencesAction.java index 6a422f04224..2d0277fef25 100644 --- a/src/main/java/org/jabref/gui/maintable/ExtractReferencesAction.java +++ b/src/main/java/org/jabref/gui/maintable/ExtractReferencesAction.java @@ -171,13 +171,14 @@ private void extractReferences(Iterator fileListIterator, ParserResult res } StringJoiner cites = new StringJoiner(","); - int count = 0; + // required because of "orElseGet" + var ref = new Object() { + int count = 0; + }; for (BibEntry importedEntry : result.getDatabase().getEntries()) { - count++; + ref.count++; Optional citationKey = importedEntry.getCitationKey(); - if (citationKey.isPresent()) { - cites.add(citationKey.get()); - } else { + cites.add(citationKey.orElseGet(() -> { String sourceCitationKey = currentEntry.getCitationKey().orElse("unknown"); String newCitationKey; // Could happen if no author and no year is present @@ -187,11 +188,11 @@ private void extractReferences(Iterator fileListIterator, ParserResult res if (matcher.hasMatch()) { newCitationKey = sourceCitationKey + "-" + matcher.group(1); } else { - newCitationKey = sourceCitationKey + "-" + count; + newCitationKey = sourceCitationKey + "-" + ref.count; } importedEntry.setCitationKey(newCitationKey); - cites.add(newCitationKey); - } + return newCitationKey; + })); } currentEntry.setField(StandardField.CITES, cites.toString()); } From 021e84d77564f7f518e99db36c4ebe350c5b3d60 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Sun, 7 Apr 2024 13:27:22 +0200 Subject: [PATCH 14/18] Remove empty lines --- CHANGELOG.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b625a6d498f..ec0dd74227e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,9 +11,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv ### Added - - We added support for parsing the "References" section of IEEE papers offline. [#11156](https://github.com/JabRef/jabref/pull/11156) - - We added a new keyboard shortcut ctrl + , to open the preferences. [#11154](https://github.com/JabRef/jabref/pull/11154) From d6ebcbacbff73197e93842d7a53c1d512369012c Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Sun, 7 Apr 2024 13:30:08 +0200 Subject: [PATCH 15/18] Replace magic number by "constant" --- .../logic/importer/fileformat/BibliographyFromPdfImporter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/jabref/logic/importer/fileformat/BibliographyFromPdfImporter.java b/src/main/java/org/jabref/logic/importer/fileformat/BibliographyFromPdfImporter.java index f1348f19ce6..f2605594e13 100644 --- a/src/main/java/org/jabref/logic/importer/fileformat/BibliographyFromPdfImporter.java +++ b/src/main/java/org/jabref/logic/importer/fileformat/BibliographyFromPdfImporter.java @@ -158,7 +158,7 @@ BibEntry parseReference(String number, String reference) { // Y. Shimosaki et al., “Lattice design for 5 MeV – 125 mA CW RFQ operation in LIPAc”, in Proc. IPAC’19, Mel- bourne, Australia, May 2019, pp. 977-979. doi:10.18429/ JACoW-IPAC2019-MOPTS051 int pos = reference.indexOf("doi:"); if (pos >= 0) { - String doi = reference.substring(pos + 4).trim(); + String doi = reference.substring(pos + "doi:".length()).trim(); doi = doi.replace(" ", ""); result.setField(StandardField.DOI, doi); reference = reference.substring(0, pos).trim(); From b33346c78a6d5a2f285d062707460b5c18672707 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Sun, 7 Apr 2024 20:48:51 +0200 Subject: [PATCH 16/18] Revert "Rewrite" This reverts commit 7adb33471f3985ceb7a2cf0e80e34f8dfad444eb. --- .../gui/maintable/ExtractReferencesAction.java | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/jabref/gui/maintable/ExtractReferencesAction.java b/src/main/java/org/jabref/gui/maintable/ExtractReferencesAction.java index 2d0277fef25..6a422f04224 100644 --- a/src/main/java/org/jabref/gui/maintable/ExtractReferencesAction.java +++ b/src/main/java/org/jabref/gui/maintable/ExtractReferencesAction.java @@ -171,14 +171,13 @@ private void extractReferences(Iterator fileListIterator, ParserResult res } StringJoiner cites = new StringJoiner(","); - // required because of "orElseGet" - var ref = new Object() { - int count = 0; - }; + int count = 0; for (BibEntry importedEntry : result.getDatabase().getEntries()) { - ref.count++; + count++; Optional citationKey = importedEntry.getCitationKey(); - cites.add(citationKey.orElseGet(() -> { + if (citationKey.isPresent()) { + cites.add(citationKey.get()); + } else { String sourceCitationKey = currentEntry.getCitationKey().orElse("unknown"); String newCitationKey; // Could happen if no author and no year is present @@ -188,11 +187,11 @@ private void extractReferences(Iterator fileListIterator, ParserResult res if (matcher.hasMatch()) { newCitationKey = sourceCitationKey + "-" + matcher.group(1); } else { - newCitationKey = sourceCitationKey + "-" + ref.count; + newCitationKey = sourceCitationKey + "-" + count; } importedEntry.setCitationKey(newCitationKey); - return newCitationKey; - })); + cites.add(newCitationKey); + } } currentEntry.setField(StandardField.CITES, cites.toString()); } From 2875851e8d15c35a429d73ba216c1e005de4e2bc Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Sun, 7 Apr 2024 21:42:00 +0200 Subject: [PATCH 17/18] Add more comments --- .../jabref/gui/maintable/ExtractReferencesAction.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/jabref/gui/maintable/ExtractReferencesAction.java b/src/main/java/org/jabref/gui/maintable/ExtractReferencesAction.java index 6a422f04224..c8d82333817 100644 --- a/src/main/java/org/jabref/gui/maintable/ExtractReferencesAction.java +++ b/src/main/java/org/jabref/gui/maintable/ExtractReferencesAction.java @@ -175,9 +175,14 @@ private void extractReferences(Iterator fileListIterator, ParserResult res for (BibEntry importedEntry : result.getDatabase().getEntries()) { count++; Optional citationKey = importedEntry.getCitationKey(); + String citationKeyToAdd; if (citationKey.isPresent()) { - cites.add(citationKey.get()); + citationKeyToAdd = citationKey.get(); } else { + // No key present -> generate one based on + // the citation key of the entry holding the files and + // the number of the current entry (extracted from the reference; fallback: current number of the entry (count variable)) + String sourceCitationKey = currentEntry.getCitationKey().orElse("unknown"); String newCitationKey; // Could happen if no author and no year is present @@ -190,8 +195,9 @@ private void extractReferences(Iterator fileListIterator, ParserResult res newCitationKey = sourceCitationKey + "-" + count; } importedEntry.setCitationKey(newCitationKey); - cites.add(newCitationKey); + citationKeyToAdd = newCitationKey; } + cites.add(citationKeyToAdd); } currentEntry.setField(StandardField.CITES, cites.toString()); } From e6e8512c15329ae80cc8a502af587f28eec19948 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Mon, 8 Apr 2024 09:06:34 +0200 Subject: [PATCH 18/18] Update CHANGELOG.md --- CHANGELOG.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ec0dd74227e..e1e59e5e77f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,10 +11,9 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv ### Added -- We added support for parsing the "References" section of IEEE papers offline. [#11156](https://github.com/JabRef/jabref/pull/11156) +- We added support for offline extracting refereferences from PDFs following the IEEE format. [#11156](https://github.com/JabRef/jabref/pull/11156) - We added a new keyboard shortcut ctrl + , to open the preferences. [#11154](https://github.com/JabRef/jabref/pull/11154) - ### Changed - We replaced the word "Key bindings" with "Keyboard shortcuts" in the Preferences tab. [#11153](https://github.com/JabRef/jabref/pull/11153)