From bcbe126f5df732fed3ad80bf6490434539522eca Mon Sep 17 00:00:00 2001 From: Subhramit Basu Bhowmick Date: Sat, 7 Sep 2024 18:01:41 +0530 Subject: [PATCH] [IMP] Add re-distribution of numeric CSL citations (#11712) * Initial commit * Semi-fix: 0-based index * add method for getting multiple citations * Use XTextRangeCompare * Fix update order * Remove dead code, undo ai refactor changes * Fix remaining order issues, Remove dead code, undo ai refactor changes * Restore exceptions: UpdateCSLBibliography * Refine * Fix checkstyle * Remove experiment * Fix OOBibBase.java exceptions * Fix OpenOfficePanel.java exceptions * Fix adapter exception indent * Fix adapter exception order * Restore javadoc * Restore javadoc, remove dead code * Remove dead code * Improve implementation (general changes) * Change printStackTrace->Logger * Fix readExistingMarks() * Remove logs * Finish CSLReferenceMarkManager * openRewrite * fix unit test (std streams -> logger) * Redundant log * Safety, refactoring * Add log * Better log * Change log level (non-breaking) * Add handling for multiple entries * Better log * Restore javadoc * Restore javadoc * Use list instead of singleton * Adapt test * Add comment * Add stricter conditions for update * Add stricter conditions for reading * Make xtextcompare final * Null handling * Update javadoc * Refactor * Remove redundant condition * Fix non-numeric side effect * Enhance implementation * Add newline * Bug fix: no sync in refresh bibliography * fix logger openrewrite --------- Co-authored-by: Siedlerchr --- .../org/jabref/gui/openoffice/OOBibBase.java | 28 +-- .../gui/preview/CopyCitationAction.java | 2 +- .../logic/citationstyle/CSLAdapter.java | 6 +- .../citationstyle/CitationStyleGenerator.java | 18 +- .../CitationStylePreviewLayout.java | 2 +- .../logic/openoffice/ReferenceMark.java | 80 ++++--- .../oocsltext/CSLCitationOOAdapter.java | 58 ++--- .../openoffice/oocsltext/CSLFormatUtils.java | 6 +- .../oocsltext/CSLReferenceMark.java | 66 +++-- .../oocsltext/CSLReferenceMarkManager.java | 226 ++++++++++++++---- .../oocsltext/CSLUpdateBibliography.java | 5 +- .../CitationStyleGeneratorTest.java | 26 +- .../citationstyle/CitationStyleTest.java | 2 +- .../logic/openoffice/ReferenceMarkTest.java | 29 ++- .../oocsltext/CSLFormatUtilsTest.java | 16 +- 15 files changed, 375 insertions(+), 195 deletions(-) diff --git a/src/main/java/org/jabref/gui/openoffice/OOBibBase.java b/src/main/java/org/jabref/gui/openoffice/OOBibBase.java index 2a19cfe7149..40a37a69d9f 100644 --- a/src/main/java/org/jabref/gui/openoffice/OOBibBase.java +++ b/src/main/java/org/jabref/gui/openoffice/OOBibBase.java @@ -87,8 +87,8 @@ public OOBibBase(Path loPath, DialogService dialogService) } private void initializeCitationAdapter(XTextDocument doc) throws WrappedTargetException, NoSuchElementException { - this.cslCitationOOAdapter = new CSLCitationOOAdapter(doc); - this.cslCitationOOAdapter.readExistingMarks(); + this.cslCitationOOAdapter = new CSLCitationOOAdapter(doc); + this.cslCitationOOAdapter.readAndUpdateExistingMarks(); } public void guiActionSelectDocument(boolean autoSelectForSingle) throws WrappedTargetException, NoSuchElementException { @@ -161,9 +161,9 @@ void showDialog(String errorTitle, OOError err) { OOVoidResult collectResults(String errorTitle, List> results) { String msg = results.stream() - .filter(OOVoidResult::isError) - .map(e -> e.getError().getLocalizedMessage()) - .collect(Collectors.joining("\n\n")); + .filter(OOVoidResult::isError) + .map(e -> e.getError().getLocalizedMessage()) + .collect(Collectors.joining("\n\n")); if (msg.isEmpty()) { return OOVoidResult.ok(); } else { @@ -233,10 +233,10 @@ private static OOVoidResult checkRangeOverlaps(XTextDocument doc, OOFro int maxReportedOverlaps = 10; try { return frontend.checkRangeOverlaps(doc, - new ArrayList<>(), - requireSeparation, - maxReportedOverlaps) - .mapError(OOError::from); + new ArrayList<>(), + requireSeparation, + maxReportedOverlaps) + .mapError(OOError::from); } catch (NoDocumentException ex) { return OOVoidResult.error(OOError.from(ex).setTitle(errorTitle)); } catch (WrappedTargetException ex) { @@ -322,7 +322,7 @@ OOResult getFrontend(XTextDocument doc) { } catch (NoDocumentException ex) { return OOResult.error(OOError.from(ex).setTitle(errorTitle)); } catch (WrappedTargetException - | RuntimeException ex) { + | RuntimeException ex) { return OOResult.error(OOError.fromMisc(ex).setTitle(errorTitle)); } } @@ -601,7 +601,7 @@ public void guiActionInsertEntry(List entries, this.cslCitationOOAdapter.insertInTextCitation(cursor.get(), citationStyle, entries, bibDatabaseContext, bibEntryTypesManager); } else if (citationType == CitationType.INVISIBLE_CIT) { // "Insert empty citation" - this.cslCitationOOAdapter.insertEmpty(cursor.get(), entries); + this.cslCitationOOAdapter.insertEmpty(cursor.get(), citationStyle, entries); } // If "Automatically sync bibliography when inserting citations" is enabled @@ -813,7 +813,7 @@ public Optional exportCitedHelper(List databases, bool } catch (DisposedException ex) { OOError.from(ex).setTitle(errorTitle).showErrorDialog(dialogService); } catch (WrappedTargetException - | com.sun.star.lang.IllegalArgumentException ex) { + | com.sun.star.lang.IllegalArgumentException ex) { LOGGER.warn("Problem generating new database.", ex); OOError.fromMisc(ex).setTitle(errorTitle).showErrorDialog(dialogService); } @@ -881,8 +881,8 @@ public void guiActionUpdateDocument(List databases, OOStyle style) } catch (DisposedException ex) { OOError.from(ex).setTitle(errorTitle).showErrorDialog(dialogService); } catch (CreationException - | WrappedTargetException - | com.sun.star.lang.IllegalArgumentException ex) { + | WrappedTargetException + | com.sun.star.lang.IllegalArgumentException ex) { LOGGER.warn("Could not update JStyle bibliography", ex); OOError.fromMisc(ex).setTitle(errorTitle).showErrorDialog(dialogService); } diff --git a/src/main/java/org/jabref/gui/preview/CopyCitationAction.java b/src/main/java/org/jabref/gui/preview/CopyCitationAction.java index 175aa1f490c..bbf9af78fa1 100644 --- a/src/main/java/org/jabref/gui/preview/CopyCitationAction.java +++ b/src/main/java/org/jabref/gui/preview/CopyCitationAction.java @@ -88,7 +88,7 @@ private List generateCitations() throws IOException { } if (styleSource != null) { - return CitationStyleGenerator.generateCitations( + return CitationStyleGenerator.generateBibliographies( selectedEntries, styleSource, outputFormat, diff --git a/src/main/java/org/jabref/logic/citationstyle/CSLAdapter.java b/src/main/java/org/jabref/logic/citationstyle/CSLAdapter.java index 422b83b7078..734690679bb 100644 --- a/src/main/java/org/jabref/logic/citationstyle/CSLAdapter.java +++ b/src/main/java/org/jabref/logic/citationstyle/CSLAdapter.java @@ -17,12 +17,12 @@ /** * Provides an adapter class to CSL. It holds a CSL instance under the hood that is only recreated when * the style changes. - * + *

* Note on the API: The first call to {@link #makeBibliography} is expensive since the * CSL instance will be created. As long as the style stays the same, we can reuse this instance. On style-change, the * engine is re-instantiated. Therefore, the use-case of this class is many calls to {@link #makeBibliography} with the * same style. Changing the output format is cheap. - * + *

* Note on the implementation: * The main function {@link #makeBibliography} will enforce * synchronized calling. The main CSL engine under the hood is not thread-safe. Since this class is usually called from @@ -51,7 +51,7 @@ public synchronized List makeBibliography(List bibEntries, Str return Arrays.asList(bibliography.getEntries()); } - public synchronized Citation makeInText(List bibEntries, String style, CitationStyleOutputFormat outputFormat, BibDatabaseContext databaseContext, BibEntryTypesManager entryTypesManager) throws IOException { + public synchronized Citation makeCitation(List bibEntries, String style, CitationStyleOutputFormat outputFormat, BibDatabaseContext databaseContext, BibEntryTypesManager entryTypesManager) throws IOException { dataProvider.setData(bibEntries, databaseContext, entryTypesManager); initialize(style, outputFormat); cslInstance.registerCitationItems(dataProvider.getIds()); diff --git a/src/main/java/org/jabref/logic/citationstyle/CitationStyleGenerator.java b/src/main/java/org/jabref/logic/citationstyle/CitationStyleGenerator.java index 6e285e1d6d9..0a5a079c86e 100644 --- a/src/main/java/org/jabref/logic/citationstyle/CitationStyleGenerator.java +++ b/src/main/java/org/jabref/logic/citationstyle/CitationStyleGenerator.java @@ -31,8 +31,8 @@ private CitationStyleGenerator() { * * @implNote the citation is generated using JavaScript which may take some time, better call it from outside the main Thread */ - protected static String generateCitation(List bibEntries, CitationStyle style, BibEntryTypesManager entryTypesManager) { - return generateCitation(bibEntries, style.getSource(), entryTypesManager); + protected static String generateBibliography(List bibEntries, CitationStyle style, BibEntryTypesManager entryTypesManager) { + return generateBibliography(bibEntries, style.getSource(), entryTypesManager); } /** @@ -40,8 +40,8 @@ protected static String generateCitation(List bibEntries, CitationStyl * * @implNote the citation is generated using JavaScript which may take some time, better call it from outside the main Thread */ - protected static String generateCitation(List bibEntries, String style, BibEntryTypesManager entryTypesManager) { - return generateCitation(bibEntries, style, CitationStyleOutputFormat.HTML, new BibDatabaseContext(), entryTypesManager).getFirst(); + protected static String generateBibliography(List bibEntries, String style, BibEntryTypesManager entryTypesManager) { + return generateBibliography(bibEntries, style, CitationStyleOutputFormat.HTML, new BibDatabaseContext(), entryTypesManager).getFirst(); } /** @@ -49,12 +49,12 @@ protected static String generateCitation(List bibEntries, String style * * @implNote the citation is generated using JavaScript which may take some time, better call it from outside the main Thread */ - public static List generateCitation(List bibEntries, String style, CitationStyleOutputFormat outputFormat, BibDatabaseContext databaseContext, BibEntryTypesManager entryTypesManager) { - return generateCitations(bibEntries, style, outputFormat, databaseContext, entryTypesManager); + public static List generateBibliography(List bibEntries, String style, CitationStyleOutputFormat outputFormat, BibDatabaseContext databaseContext, BibEntryTypesManager entryTypesManager) { + return generateBibliographies(bibEntries, style, outputFormat, databaseContext, entryTypesManager); } - public static Citation generateInText(List bibEntries, String style, CitationStyleOutputFormat outputFormat, BibDatabaseContext databaseContext, BibEntryTypesManager entryTypesManager) throws IOException { - return CSL_ADAPTER.makeInText(bibEntries, style, outputFormat, databaseContext, entryTypesManager); + public static Citation generateCitation(List bibEntries, String style, CitationStyleOutputFormat outputFormat, BibDatabaseContext databaseContext, BibEntryTypesManager entryTypesManager) throws IOException { + return CSL_ADAPTER.makeCitation(bibEntries, style, outputFormat, databaseContext, entryTypesManager); } /** @@ -62,7 +62,7 @@ public static Citation generateInText(List bibEntries, String style, C * * @implNote The citations are generated using JavaScript which may take some time, better call it from outside the main thread. */ - public static List generateCitations(List bibEntries, String style, CitationStyleOutputFormat outputFormat, BibDatabaseContext databaseContext, BibEntryTypesManager entryTypesManager) { + public static List generateBibliographies(List bibEntries, String style, CitationStyleOutputFormat outputFormat, BibDatabaseContext databaseContext, BibEntryTypesManager entryTypesManager) { try { return CSL_ADAPTER.makeBibliography(bibEntries, style, outputFormat, databaseContext, entryTypesManager); } catch (IllegalArgumentException e) { diff --git a/src/main/java/org/jabref/logic/citationstyle/CitationStylePreviewLayout.java b/src/main/java/org/jabref/logic/citationstyle/CitationStylePreviewLayout.java index 9a584765aaf..12feae5483e 100644 --- a/src/main/java/org/jabref/logic/citationstyle/CitationStylePreviewLayout.java +++ b/src/main/java/org/jabref/logic/citationstyle/CitationStylePreviewLayout.java @@ -18,7 +18,7 @@ public CitationStylePreviewLayout(CitationStyle citationStyle, BibEntryTypesMana @Override public String generatePreview(BibEntry entry, BibDatabaseContext databaseContext) { - return CitationStyleGenerator.generateCitation(List.of(entry), citationStyle.getSource(), CitationStyleOutputFormat.HTML, databaseContext, bibEntryTypesManager).getFirst(); + return CitationStyleGenerator.generateBibliography(List.of(entry), citationStyle.getSource(), CitationStyleOutputFormat.HTML, databaseContext, bibEntryTypesManager).getFirst(); } @Override diff --git a/src/main/java/org/jabref/logic/openoffice/ReferenceMark.java b/src/main/java/org/jabref/logic/openoffice/ReferenceMark.java index e8b466d6965..a4f1bdae054 100644 --- a/src/main/java/org/jabref/logic/openoffice/ReferenceMark.java +++ b/src/main/java/org/jabref/logic/openoffice/ReferenceMark.java @@ -1,5 +1,7 @@ package org.jabref.logic.openoffice; +import java.util.ArrayList; +import java.util.List; import java.util.Optional; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -9,13 +11,16 @@ import org.slf4j.LoggerFactory; public class ReferenceMark { + public static final String[] PREFIXES = {"JABREF_", "CID_"}; + private static final Logger LOGGER = LoggerFactory.getLogger(ReferenceMark.class); - private static final Pattern REFERENCE_MARK_FORMAT = Pattern.compile("^JABREF_(\\w+) CID_(\\w+) (\\w+)$"); - private final String name; + private static final Pattern REFERENCE_MARK_FORMAT = Pattern.compile("^((?:JABREF_\\w+ CID_\\w+(?:,\\s*)?)+)(\\s*\\w+)?$"); + private static final Pattern ENTRY_PATTERN = Pattern.compile("JABREF_(\\w+) CID_(\\w+)"); - private String citationKey; - private Integer citationNumber; + private final String name; + private List citationKeys; + private List citationNumbers; private String uniqueId; /** @@ -23,36 +28,45 @@ public class ReferenceMark { */ public ReferenceMark(String name) { this.name = name; + parse(name); + } - Matcher matcher = getMatcher(name); + public ReferenceMark(String name, List citationKeys, List citationNumbers, String uniqueId) { + this.name = name; + this.citationKeys = citationKeys; + this.citationNumbers = citationNumbers; + this.uniqueId = uniqueId; + } + + private void parse(String name) { + Matcher matcher = REFERENCE_MARK_FORMAT.matcher(name); if (!matcher.matches()) { LOGGER.warn("CSLReferenceMark: name={} does not match pattern. Assuming random values", name); - this.citationKey = CUID.randomCUID2(8).toString(); - this.citationNumber = 0; - this.uniqueId = this.citationKey; + this.citationKeys = List.of(CUID.randomCUID2(8).toString()); + this.citationNumbers = List.of(0); + this.uniqueId = this.citationKeys.getFirst(); return; } - this.citationKey = matcher.group(1); - this.citationNumber = Integer.parseInt(matcher.group(2)); - this.uniqueId = matcher.group(3); + String entriesString = matcher.group(1).trim(); + this.uniqueId = matcher.group(2) != null ? matcher.group(2).trim() : CUID.randomCUID2(8).toString(); - LOGGER.debug("CSLReferenceMark: citationKey={} citationNumber={} uniqueId={}", getCitationKey(), getCitationNumber(), getUniqueId()); - } + this.citationKeys = new ArrayList<>(); + this.citationNumbers = new ArrayList<>(); - public ReferenceMark(String name, String citationKey, Integer citationNumber, String uniqueId) { - this.name = name; - this.citationKey = citationKey; - this.citationNumber = citationNumber; - this.uniqueId = uniqueId; - } + Matcher entryMatcher = ENTRY_PATTERN.matcher(entriesString); + while (entryMatcher.find()) { + this.citationKeys.add(entryMatcher.group(1)); + this.citationNumbers.add(Integer.parseInt(entryMatcher.group(2))); + } - private ReferenceMark(String name, String citationKey, String citationNumber, String uniqueId) { - this(name, citationKey, Integer.parseInt(citationNumber), uniqueId); - } + if (this.citationKeys.isEmpty() || this.citationNumbers.isEmpty()) { + LOGGER.warn("CSLReferenceMark: Failed to parse any entries from name={}. Assuming random values", name); + this.citationKeys = List.of(CUID.randomCUID2(8).toString()); + this.citationNumbers = List.of(0); + } - private static Matcher getMatcher(String name) { - return REFERENCE_MARK_FORMAT.matcher(name); + LOGGER.debug("CSLReferenceMark: citationKeys={} citationNumbers={} uniqueId={}", getCitationKeys(), getCitationNumbers(), getUniqueId()); } public String getName() { @@ -60,14 +74,14 @@ public String getName() { } /** - * The BibTeX citation key + * The BibTeX citation keys */ - public String getCitationKey() { - return citationKey; + public List getCitationKeys() { + return citationKeys; } - public int getCitationNumber() { - return citationNumber; + public List getCitationNumbers() { + return citationNumbers; } public String getUniqueId() { @@ -75,11 +89,7 @@ public String getUniqueId() { } public static Optional of(String name) { - Matcher matcher = getMatcher(name); - if (!matcher.matches()) { - return Optional.empty(); - } - - return Optional.of(new ReferenceMark(name, matcher.group(1), matcher.group(2), matcher.group(3))); + ReferenceMark mark = new ReferenceMark(name); + return mark.citationKeys.isEmpty() ? Optional.empty() : Optional.of(mark); } } diff --git a/src/main/java/org/jabref/logic/openoffice/oocsltext/CSLCitationOOAdapter.java b/src/main/java/org/jabref/logic/openoffice/oocsltext/CSLCitationOOAdapter.java index 9b529754431..865806947f3 100644 --- a/src/main/java/org/jabref/logic/openoffice/oocsltext/CSLCitationOOAdapter.java +++ b/src/main/java/org/jabref/logic/openoffice/oocsltext/CSLCitationOOAdapter.java @@ -41,8 +41,8 @@ public CSLCitationOOAdapter(XTextDocument doc) { this.markManager = new CSLReferenceMarkManager(doc); } - public void readExistingMarks() throws WrappedTargetException, NoSuchElementException { - markManager.readExistingMarks(); + public void readAndUpdateExistingMarks() throws WrappedTargetException, NoSuchElementException { + markManager.readAndUpdateExistingMarks(); } /** @@ -58,7 +58,7 @@ public void insertCitation(XTextCursor cursor, CitationStyle selectedStyle, List if (isAlphanumeric) { inTextCitation = CSLFormatUtils.generateAlphanumericCitation(entries, bibDatabaseContext); } else { - inTextCitation = CitationStyleGenerator.generateInText(entries, style, CSLFormatUtils.OUTPUT_FORMAT, bibDatabaseContext, bibEntryTypesManager).getText(); + inTextCitation = CitationStyleGenerator.generateCitation(entries, style, CSLFormatUtils.OUTPUT_FORMAT, bibDatabaseContext, bibEntryTypesManager).getText(); } String formattedCitation = CSLFormatUtils.transformHTML(inTextCitation); @@ -68,7 +68,7 @@ public void insertCitation(XTextCursor cursor, CitationStyle selectedStyle, List } OOText ooText = OOFormat.setLocaleNone(OOText.fromString(formattedCitation)); - insertMultipleReferenceMarks(cursor, entries, ooText); + insertReferences(cursor, entries, ooText, selectedStyle.isNumericStyle()); cursor.collapseToEnd(); } @@ -98,7 +98,7 @@ public void insertInTextCitation(XTextCursor cursor, CitationStyle selectedStyle // Combine author name with the citation inTextCitation = authorName + " " + inTextCitation; } else { - inTextCitation = CitationStyleGenerator.generateInText(List.of(currentEntry), style, CSLFormatUtils.OUTPUT_FORMAT, bibDatabaseContext, bibEntryTypesManager).getText(); + inTextCitation = CitationStyleGenerator.generateCitation(List.of(currentEntry), style, CSLFormatUtils.OUTPUT_FORMAT, bibDatabaseContext, bibEntryTypesManager).getText(); } String formattedCitation = CSLFormatUtils.transformHTML(inTextCitation); String finalText; @@ -118,7 +118,7 @@ public void insertInTextCitation(XTextCursor cursor, CitationStyle selectedStyle finalText += ","; } OOText ooText = OOFormat.setLocaleNone(OOText.fromString(finalText)); - insertMultipleReferenceMarks(cursor, List.of(currentEntry), ooText); + insertReferences(cursor, List.of(currentEntry), ooText, selectedStyle.isNumericStyle()); cursor.collapseToEnd(); } } @@ -127,13 +127,10 @@ public void insertInTextCitation(XTextCursor cursor, CitationStyle selectedStyle * Inserts "empty" citations for a list of entries at the cursor to the document. * Adds the entries to the list for which bibliography is to be generated. */ - public void insertEmpty(XTextCursor cursor, List entries) + public void insertEmpty(XTextCursor cursor, CitationStyle selectedStyle, List entries) throws CreationException, Exception { - for (BibEntry entry : entries) { - CSLReferenceMark mark = markManager.createReferenceMark(entry); - OOText emptyOOText = OOFormat.setLocaleNone(OOText.fromString("")); - mark.insertReferenceIntoOO(document, cursor, emptyOOText, false, false, true); - } + OOText emptyOOText = OOFormat.setLocaleNone(OOText.fromString("")); + insertReferences(cursor, entries, emptyOOText, selectedStyle.isNumericStyle()); // Move the cursor to the end of the inserted text - although no need as we don't insert any text, but a good practice cursor.collapseToEnd(); @@ -144,7 +141,9 @@ public void insertEmpty(XTextCursor cursor, List entries) * The list is generated based on the existing citations, in-text citations and empty citations in the document. */ public void insertBibliography(XTextCursor cursor, CitationStyle selectedStyle, List entries, BibDatabaseContext bibDatabaseContext, BibEntryTypesManager bibEntryTypesManager) - throws WrappedTargetException, CreationException { + throws WrappedTargetException, CreationException, NoSuchElementException { + markManager.setUpdateRequired(selectedStyle.isNumericStyle()); + readAndUpdateExistingMarks(); OOText title = OOFormat.paragraph(OOText.fromString(CSLFormatUtils.DEFAULT_BIBLIOGRAPHY_TITLE), CSLFormatUtils.DEFAULT_BIBLIOGRAPHY_HEADER_PARAGRAPH_FORMAT); OOTextIntoOO.write(document, cursor, OOText.fromString(title.toString())); @@ -158,7 +157,7 @@ public void insertBibliography(XTextCursor cursor, CitationStyle selectedStyle, entries.sort(Comparator.comparingInt(entry -> markManager.getCitationNumber(entry.getCitationKey().orElse("")))); for (BibEntry entry : entries) { - String citation = CitationStyleGenerator.generateCitation(List.of(entry), style, CSLFormatUtils.OUTPUT_FORMAT, bibDatabaseContext, bibEntryTypesManager).getFirst(); + String citation = CitationStyleGenerator.generateBibliography(List.of(entry), style, CSLFormatUtils.OUTPUT_FORMAT, bibDatabaseContext, bibEntryTypesManager).getFirst(); String citationKey = entry.getCitationKey().orElse(""); int currentNumber = markManager.getCitationNumber(citationKey); @@ -174,7 +173,7 @@ public void insertBibliography(XTextCursor cursor, CitationStyle selectedStyle, } } else { // Ordering will be according to citeproc item data provider (default) - List citations = CitationStyleGenerator.generateCitation(entries, style, CSLFormatUtils.OUTPUT_FORMAT, bibDatabaseContext, bibEntryTypesManager); + List citations = CitationStyleGenerator.generateBibliography(entries, style, CSLFormatUtils.OUTPUT_FORMAT, bibDatabaseContext, bibEntryTypesManager); for (String citation : citations) { String formattedCitation = CSLFormatUtils.transformHTML(citation); @@ -186,14 +185,9 @@ public void insertBibliography(XTextCursor cursor, CitationStyle selectedStyle, } /** - * Inserts multiple references and also adds a space before the citation if not already present ("smart space"). - * - * @implNote It is difficult to "segment" a single citation generated for a group of entries into distinct parts based on the entries such that each entry can be draped with its corresponding reference mark. - * This is because of the sheer variety in the styles of citations and the separators between them (when grouped) in case of Citation Style Language. - * Furthermore, it is also difficult to generate a "single" reference mark for a group of entries. - * Thus, in case of citations for a group of entries, we first insert the citation (text), then insert the invisible reference marks for each entry separately after it. + * Inserts references and also adds a space before the citation if not already present ("smart space"). */ - private void insertMultipleReferenceMarks(XTextCursor cursor, List entries, OOText ooText) + private void insertReferences(XTextCursor cursor, List entries, OOText ooText, boolean isNumericStyle) throws CreationException, Exception { boolean preceedingSpaceExists; XTextCursor checkCursor = cursor.getText().createTextCursorByRange(cursor.getStart()); @@ -211,23 +205,15 @@ private void insertMultipleReferenceMarks(XTextCursor cursor, List ent } } - if (entries.size() == 1) { - CSLReferenceMark mark = markManager.createReferenceMark(entries.getFirst()); - mark.insertReferenceIntoOO(document, cursor, ooText, !preceedingSpaceExists, false, true); - } else { - if (!preceedingSpaceExists) { - cursor.getText().insertString(cursor, " ", false); - } - OOTextIntoOO.write(document, cursor, ooText); - for (BibEntry entry : entries) { - CSLReferenceMark mark = markManager.createReferenceMark(entry); - OOText emptyOOText = OOFormat.setLocaleNone(OOText.fromString("")); - mark.insertReferenceIntoOO(document, cursor, emptyOOText, false, false, true); - } - } + CSLReferenceMark mark = markManager.createReferenceMark(entries); + mark.insertReferenceIntoOO(document, cursor, ooText, !preceedingSpaceExists, false); // Move the cursor to the end of the inserted text cursor.collapseToEnd(); + + markManager.setUpdateRequired(isNumericStyle); + readAndUpdateExistingMarks(); + cursor.collapseToEnd(); } /** diff --git a/src/main/java/org/jabref/logic/openoffice/oocsltext/CSLFormatUtils.java b/src/main/java/org/jabref/logic/openoffice/oocsltext/CSLFormatUtils.java index 0ae50cb4716..1ac64172287 100644 --- a/src/main/java/org/jabref/logic/openoffice/oocsltext/CSLFormatUtils.java +++ b/src/main/java/org/jabref/logic/openoffice/oocsltext/CSLFormatUtils.java @@ -36,7 +36,7 @@ public class CSLFormatUtils { /** * Transforms provided HTML into a format that can be fully parsed and inserted into an OO document. - * Context: The HTML produced by {@link org.jabref.logic.citationstyle.CitationStyleGenerator#generateCitation(List, String, CitationStyleOutputFormat, BibDatabaseContext, BibEntryTypesManager) generateCitation} or {@link org.jabref.logic.citationstyle.CitationStyleGenerator#generateInText(List, String, CitationStyleOutputFormat, BibDatabaseContext, BibEntryTypesManager) generateInText} is not directly (completely) parsable by by {@link OOTextIntoOO#write(XTextDocument, XTextCursor, OOText) write}. + * Context: The HTML produced by {@link org.jabref.logic.citationstyle.CitationStyleGenerator#generateBibliography(List, String, CitationStyleOutputFormat, BibDatabaseContext, BibEntryTypesManager) generateBibliography} or {@link org.jabref.logic.citationstyle.CitationStyleGenerator#generateCitation(List, String, CitationStyleOutputFormat, BibDatabaseContext, BibEntryTypesManager) generateCitation} is not directly (completely) parsable by by {@link OOTextIntoOO#write(XTextDocument, XTextCursor, OOText) write}. * For more details, read the documentation for the {@link OOTextIntoOO} class. * Additional Information. * @@ -76,7 +76,7 @@ public static String transformHTML(String html) { } /** - * Alphanumeric citations are not natively supported by citeproc-java (see {@link org.jabref.logic.citationstyle.CitationStyleGenerator#generateInText(List, String, CitationStyleOutputFormat, BibDatabaseContext, BibEntryTypesManager) generateInText}). + * Alphanumeric citations are not natively supported by citeproc-java (see {@link org.jabref.logic.citationstyle.CitationStyleGenerator#generateCitation(List, String, CitationStyleOutputFormat, BibDatabaseContext, BibEntryTypesManager) generateCitation}). * Thus, we manually format a citation to produce its alphanumeric form. * * @param entries the list of entries for which the alphanumeric citation is to be generated. @@ -113,7 +113,7 @@ public static String generateAlphanumericCitation(List entries, BibDat /** * Method to update citation number of a bibliographic entry (to be inserted in the list of references). - * By default, citeproc-java ({@link org.jabref.logic.citationstyle.CitationStyleGenerator#generateCitation(List, String, CitationStyleOutputFormat, BibDatabaseContext, BibEntryTypesManager) generateCitation} always start the numbering of a list of citations with "1". + * By default, citeproc-java ({@link org.jabref.logic.citationstyle.CitationStyleGenerator#generateBibliography(List, String, CitationStyleOutputFormat, BibDatabaseContext, BibEntryTypesManager) generateBibliography} always start the numbering of a list of citations with "1". * If a citation doesn't correspond to the first cited entry, the number should be changed to the relevant current citation number. * If an entries has been cited before, the colder number should be reused. * The number can be enclosed in different formats, such as "1", "1.", "1)", "(1)" or "[1]". diff --git a/src/main/java/org/jabref/logic/openoffice/oocsltext/CSLReferenceMark.java b/src/main/java/org/jabref/logic/openoffice/oocsltext/CSLReferenceMark.java index 49f11127c0c..85df85be987 100644 --- a/src/main/java/org/jabref/logic/openoffice/oocsltext/CSLReferenceMark.java +++ b/src/main/java/org/jabref/logic/openoffice/oocsltext/CSLReferenceMark.java @@ -1,5 +1,7 @@ package org.jabref.logic.openoffice.oocsltext; +import java.util.List; + import org.jabref.logic.openoffice.ReferenceMark; import org.jabref.model.openoffice.DocumentAnnotation; import org.jabref.model.openoffice.ootext.OOText; @@ -17,8 +19,6 @@ import com.sun.star.uno.Exception; import com.sun.star.uno.UnoRuntime; import io.github.thibaultmeyer.cuid.CUID; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import static org.jabref.logic.openoffice.backend.NamedRangeReferenceMark.safeInsertSpacesBetweenReferenceMarks; @@ -26,31 +26,57 @@ * Class to handle a reference mark. See {@link CSLReferenceMarkManager} for the management of all reference marks. */ public class CSLReferenceMark { - - private static final Logger LOGGER = LoggerFactory.getLogger(CSLReferenceMark.class); - - private final ReferenceMark referenceMark; - private final XTextContent textContent; + private ReferenceMark referenceMark; + private XTextContent textContent; + private final List citationKeys; + private List citationNumbers; public CSLReferenceMark(XNamed named, ReferenceMark referenceMark) { this.referenceMark = referenceMark; - textContent = UnoRuntime.queryInterface(XTextContent.class, named); - } - - public CSLReferenceMark(XNamed named, String name, String citationKey, Integer citationNumber, String uniqueId) { - referenceMark = new ReferenceMark(name, citationKey, citationNumber, uniqueId); this.textContent = UnoRuntime.queryInterface(XTextContent.class, named); + this.citationKeys = referenceMark.getCitationKeys(); + this.citationNumbers = referenceMark.getCitationNumbers(); } - public static CSLReferenceMark of(String citationKey, Integer citationNumber, XMultiServiceFactory factory) throws Exception { + public static CSLReferenceMark of(List citationKeys, List citationNumbers, XMultiServiceFactory factory) throws Exception { String uniqueId = CUID.randomCUID2(8).toString(); - String name = "JABREF_" + citationKey + " CID_" + citationNumber + " " + uniqueId; + String name = buildReferenceName(citationKeys, citationNumbers, uniqueId); XNamed named = UnoRuntime.queryInterface(XNamed.class, factory.createInstance("com.sun.star.text.ReferenceMark")); named.setName(name); - return new CSLReferenceMark(named, name, citationKey, citationNumber, uniqueId); + ReferenceMark referenceMark = new ReferenceMark(name, citationKeys, citationNumbers, uniqueId); + return new CSLReferenceMark(named, referenceMark); + } + + private static String buildReferenceName(List citationKeys, List citationNumbers, String uniqueId) { + StringBuilder nameBuilder = new StringBuilder(); + for (int i = 0; i < citationKeys.size(); i++) { + if (i > 0) { + nameBuilder.append(", "); + } + nameBuilder.append(ReferenceMark.PREFIXES[0]).append(citationKeys.get(i)) + .append(" ").append(ReferenceMark.PREFIXES[1]).append(citationNumbers.get(i)); + } + nameBuilder.append(" ").append(uniqueId); + return nameBuilder.toString(); + } + + public List getCitationKeys() { + return citationKeys; + } + + public void setCitationNumbers(List numbers) { + this.citationNumbers = numbers; } - public void insertReferenceIntoOO(XTextDocument doc, XTextCursor position, OOText ooText, boolean insertSpaceBefore, boolean insertSpaceAfter, boolean withoutBrackets) + public XTextContent getTextContent() { + return textContent; + } + + public String getName() { + return referenceMark.getName(); + } + + public void insertReferenceIntoOO(XTextDocument doc, XTextCursor position, OOText ooText, boolean insertSpaceBefore, boolean insertSpaceAfter) throws CreationException, WrappedTargetException { // Ensure the cursor is at the end of its range position.collapseToEnd(); @@ -100,11 +126,11 @@ public void insertReferenceIntoOO(XTextDocument doc, XTextCursor position, OOTex position.gotoRange(cursorAfter.getEnd(), false); } - public XTextContent getTextContent() { - return textContent; + public void updateTextContent(XTextContent newTextContent) { + this.textContent = newTextContent; } - public String getName() { - return referenceMark.getName(); + public void updateName(String newName) { + this.referenceMark = new ReferenceMark(newName, this.citationKeys, this.citationNumbers, this.referenceMark.getUniqueId()); } } diff --git a/src/main/java/org/jabref/logic/openoffice/oocsltext/CSLReferenceMarkManager.java b/src/main/java/org/jabref/logic/openoffice/oocsltext/CSLReferenceMarkManager.java index 42440572166..66ceb6d36ba 100644 --- a/src/main/java/org/jabref/logic/openoffice/oocsltext/CSLReferenceMarkManager.java +++ b/src/main/java/org/jabref/logic/openoffice/oocsltext/CSLReferenceMarkManager.java @@ -2,9 +2,11 @@ import java.util.ArrayList; import java.util.HashMap; -import java.util.IdentityHashMap; +import java.util.List; import java.util.Map; -import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; import org.jabref.logic.openoffice.ReferenceMark; import org.jabref.model.entry.BibEntry; @@ -12,10 +14,16 @@ import com.sun.star.container.NoSuchElementException; import com.sun.star.container.XNameAccess; import com.sun.star.container.XNamed; +import com.sun.star.lang.IllegalArgumentException; import com.sun.star.lang.WrappedTargetException; import com.sun.star.lang.XMultiServiceFactory; import com.sun.star.text.XReferenceMarksSupplier; +import com.sun.star.text.XText; +import com.sun.star.text.XTextContent; +import com.sun.star.text.XTextCursor; import com.sun.star.text.XTextDocument; +import com.sun.star.text.XTextRange; +import com.sun.star.text.XTextRangeCompare; import com.sun.star.uno.Exception; import com.sun.star.uno.UnoRuntime; import io.github.thibaultmeyer.cuid.CUID; @@ -25,73 +33,205 @@ public class CSLReferenceMarkManager { private static final Logger LOGGER = LoggerFactory.getLogger(CSLReferenceMarkManager.class); - private final HashMap marksByName; - private final ArrayList marksByID; - private final IdentityHashMap idsByMark; private final XTextDocument document; private final XMultiServiceFactory factory; - private final HashMap citationKeyToNumber; + private final Map marksByName = new HashMap<>(); + private final List marksInOrder = new ArrayList<>(); + private Map citationKeyToNumber = new HashMap<>(); + private final XTextRangeCompare textRangeCompare; private int highestCitationNumber = 0; - private final Map citationOrder = new HashMap<>(); + private boolean isUpdateRequired; public CSLReferenceMarkManager(XTextDocument document) { this.document = document; this.factory = UnoRuntime.queryInterface(XMultiServiceFactory.class, document); - this.marksByName = new HashMap<>(); - this.marksByID = new ArrayList<>(); - this.idsByMark = new IdentityHashMap<>(); - this.citationKeyToNumber = new HashMap<>(); + this.textRangeCompare = UnoRuntime.queryInterface(XTextRangeCompare.class, document.getText()); + this.isUpdateRequired = false; } - public CSLReferenceMark createReferenceMark(BibEntry entry) throws Exception { - String citationKey = entry.getCitationKey().orElse(CUID.randomCUID2(8).toString()); - int citationNumber = getCitationNumber(citationKey); - CSLReferenceMark referenceMark = CSLReferenceMark.of(citationKey, citationNumber, factory); - addMark(referenceMark); + public CSLReferenceMark createReferenceMark(List entries) throws Exception { + List citationKeys = entries.stream() + .map(entry -> entry.getCitationKey().orElse(CUID.randomCUID2(8).toString())) + .collect(Collectors.toList()); + + List citationNumbers = citationKeys.stream() + .map(this::getCitationNumber) + .collect(Collectors.toList()); + + CSLReferenceMark referenceMark = CSLReferenceMark.of(citationKeys, citationNumbers, factory); + marksByName.put(referenceMark.getName(), referenceMark); + marksInOrder.add(referenceMark); return referenceMark; } - public void addMark(CSLReferenceMark mark) { - marksByName.put(mark.getName(), mark); - idsByMark.put(mark, marksByID.size()); - marksByID.add(mark); - updateCitationInfo(mark.getName()); + public void setUpdateRequired(boolean isNumeric) { + this.isUpdateRequired = isNumeric; } - public void readExistingMarks() throws WrappedTargetException, NoSuchElementException { - XReferenceMarksSupplier supplier = UnoRuntime.queryInterface(XReferenceMarksSupplier.class, document); - XNameAccess marks = supplier.getReferenceMarks(); + public void updateAllCitationNumbers() throws Exception { + sortMarksInOrder(); + Map newCitationKeyToNumber = new HashMap<>(); + int currentNumber = 1; - citationOrder.clear(); - int citationCounter = 0; + for (CSLReferenceMark mark : marksInOrder) { + List citationKeys = mark.getCitationKeys(); + List assignedNumbers = new ArrayList<>(); - for (String name : marks.getElementNames()) { - Optional referenceMark = ReferenceMark.of(name); - if (!referenceMark.isEmpty()) { - citationOrder.putIfAbsent(referenceMark.map(ReferenceMark::getCitationKey).get(), ++citationCounter); - XNamed named = UnoRuntime.queryInterface(XNamed.class, marks.getByName(name)); - CSLReferenceMark mark = new CSLReferenceMark(named, referenceMark.get()); - addMark(mark); + for (String citationKey : citationKeys) { + int assignedNumber; + if (newCitationKeyToNumber.containsKey(citationKey)) { + assignedNumber = newCitationKeyToNumber.get(citationKey); + } else { + assignedNumber = currentNumber; + newCitationKeyToNumber.put(citationKey, assignedNumber); + currentNumber++; + } + assignedNumbers.add(assignedNumber); } + + mark.setCitationNumbers(assignedNumbers); + updateMarkAndText(mark, assignedNumbers); } + + citationKeyToNumber = newCitationKeyToNumber; + } + + private void sortMarksInOrder() { + marksInOrder.sort((m1, m2) -> compareTextRanges(m2.getTextContent().getAnchor(), m1.getTextContent().getAnchor())); } - private void updateCitationInfo(String name) { - Optional referenceMark = ReferenceMark.of(name); - if (referenceMark.isPresent()) { - int citationNumber = referenceMark.get().getCitationNumber(); - citationKeyToNumber.put(referenceMark.get().getCitationKey(), citationNumber); - highestCitationNumber = Math.max(highestCitationNumber, citationNumber); - } else { - LOGGER.warn("Could not parse ReferenceMark name: {}", name); + private int compareTextRanges(XTextRange r1, XTextRange r2) { + try { + return r1 != null && r2 != null ? textRangeCompare.compareRegionStarts(r1, r2) : 0; + } catch (IllegalArgumentException e) { + LOGGER.warn("Error comparing text ranges: {}", e.getMessage(), e); + return 0; } } - public boolean hasCitationForKey(String citationKey) { - return citationKeyToNumber.containsKey(citationKey); + private void updateMarkAndText(CSLReferenceMark mark, List newNumbers) throws Exception { + XTextContent oldContent = mark.getTextContent(); + XTextRange range = oldContent.getAnchor(); + + if (range != null) { + XText text = range.getText(); + + // Store the position of the mark + XTextCursor cursor = text.createTextCursorByRange(range); + + // Get the current text content + String currentText = range.getString(); + + // Update the citation numbers in the text + String updatedText = updateCitationText(currentText, newNumbers); + + // Remove the old reference mark without removing the text (The only way to edit a reference mark is to remove it and add a new one) + text.removeTextContent(oldContent); + + // Update the text + cursor.setString(updatedText); + + // Create a new reference mark with updated name + String updatedName = updateReferenceName(mark.getName(), newNumbers); + XNamed newNamed = UnoRuntime.queryInterface(XNamed.class, + factory.createInstance("com.sun.star.text.ReferenceMark")); + newNamed.setName(updatedName); + XTextContent newContent = UnoRuntime.queryInterface(XTextContent.class, newNamed); + + // Attach the new reference mark to the cursor range + newContent.attach(cursor); + + // Update our internal reference to the new text content and name + mark.updateTextContent(newContent); + mark.updateName(updatedName); + mark.setCitationNumbers(newNumbers); + } + } + + private String updateReferenceName(String oldName, List newNumbers) { + String[] parts = oldName.split(" "); + if (oldName.startsWith("JABREF_") && oldName.contains("CID") && parts.length >= 3) { + StringBuilder newName = new StringBuilder(); + for (int i = 0; i < parts.length - 1; i += 2) { + // Each iteration of the loop (incrementing by 2) represents one full citation (key + number) + if (i > 0) { + newName.append(", "); + } + newName.append(parts[i]).append(" "); + newName.append(ReferenceMark.PREFIXES[1]).append(newNumbers.get(i / 2)); + } + newName.append(" ").append(parts[parts.length - 1]); + return newName.toString(); + } + return oldName; + } + + private String updateCitationText(String currentText, List newNumbers) { + Pattern pattern = Pattern.compile("(\\D*)(\\d+)(\\D*)"); + Matcher matcher = pattern.matcher(currentText); + StringBuilder result = new StringBuilder(); + int lastEnd = 0; + int numberIndex = 0; + + while (matcher.find()) { + result.append(currentText, lastEnd, matcher.start(2)); + result.append(newNumbers.get(numberIndex++)); + lastEnd = matcher.end(2); + } + result.append(currentText.substring(lastEnd)); + + return result.toString(); } public int getCitationNumber(String citationKey) { return citationKeyToNumber.computeIfAbsent(citationKey, k -> ++highestCitationNumber); } + + public void readAndUpdateExistingMarks() throws WrappedTargetException, NoSuchElementException { + marksByName.clear(); + marksInOrder.clear(); + citationKeyToNumber.clear(); + + XReferenceMarksSupplier supplier = UnoRuntime.queryInterface(XReferenceMarksSupplier.class, document); + XNameAccess marks = supplier.getReferenceMarks(); + + for (String name : marks.getElementNames()) { + if (name.startsWith(ReferenceMark.PREFIXES[0]) && name.contains(ReferenceMark.PREFIXES[1]) && name.split(" ").length >= 3) { + XNamed named = UnoRuntime.queryInterface(XNamed.class, marks.getByName(name)); + + ReferenceMark referenceMark = new ReferenceMark(name); + List citationKeys = referenceMark.getCitationKeys(); + List citationNumbers = referenceMark.getCitationNumbers(); + + if (!citationKeys.isEmpty() && !citationNumbers.isEmpty()) { + CSLReferenceMark mark = new CSLReferenceMark(named, referenceMark); + marksByName.put(name, mark); + marksInOrder.add(mark); + + for (int i = 0; i < citationKeys.size(); i++) { + String key = citationKeys.get(i); + int number = citationNumbers.get(i); + citationKeyToNumber.put(key, number); + highestCitationNumber = Math.max(highestCitationNumber, number); + } + } else { + LOGGER.warn("Cannot parse reference mark - invalid format: {}", name); + } + } + } + + LOGGER.debug("Read {} existing marks", marksByName.size()); + + if (isUpdateRequired) { + try { + updateAllCitationNumbers(); + } catch (Exception e) { + LOGGER.warn("Error updating citation numbers", e); + } + } + } + + public boolean hasCitationForKey(String citationKey) { + return citationKeyToNumber.containsKey(citationKey); + } } diff --git a/src/main/java/org/jabref/logic/openoffice/oocsltext/CSLUpdateBibliography.java b/src/main/java/org/jabref/logic/openoffice/oocsltext/CSLUpdateBibliography.java index 21cb87b6cf6..107974934b4 100644 --- a/src/main/java/org/jabref/logic/openoffice/oocsltext/CSLUpdateBibliography.java +++ b/src/main/java/org/jabref/logic/openoffice/oocsltext/CSLUpdateBibliography.java @@ -12,6 +12,7 @@ import org.jabref.model.openoffice.uno.NoDocumentException; import org.jabref.model.openoffice.uno.UnoTextSection; +import com.sun.star.container.NoSuchElementException; import com.sun.star.lang.WrappedTargetException; import com.sun.star.text.XTextCursor; import com.sun.star.text.XTextDocument; @@ -41,7 +42,7 @@ public void rebuildCSLBibliography(XTextDocument doc, CitationStyle citationStyle, BibDatabaseContext bibDatabaseContext, BibEntryTypesManager bibEntryTypesManager) - throws WrappedTargetException, NoDocumentException, CreationException { + throws WrappedTargetException, NoDocumentException, CreationException, NoSuchElementException { LOGGER.debug("Starting to rebuild CSL bibliography"); // Ensure the bibliography section exists @@ -87,7 +88,7 @@ private void populateCSLBibTextSection(XTextDocument doc, CitationStyle citationStyle, BibDatabaseContext bibDatabaseContext, BibEntryTypesManager bibEntryTypesManager) - throws WrappedTargetException, NoDocumentException, CreationException { + throws WrappedTargetException, NoDocumentException, CreationException, NoSuchElementException { LOGGER.debug("Populating CSL bibliography section"); Optional sectionRange = getBibliographyRange(doc); diff --git a/src/test/java/org/jabref/logic/citationstyle/CitationStyleGeneratorTest.java b/src/test/java/org/jabref/logic/citationstyle/CitationStyleGeneratorTest.java index 3ee3a339fc0..d1a6891e874 100644 --- a/src/test/java/org/jabref/logic/citationstyle/CitationStyleGeneratorTest.java +++ b/src/test/java/org/jabref/logic/citationstyle/CitationStyleGeneratorTest.java @@ -34,7 +34,7 @@ class CitationStyleGeneratorTest { void aCMCitation() { context.setMode(BibDatabaseMode.BIBLATEX); CitationStyle style = styleList.stream().filter(e -> "ACM SIGGRAPH".equals(e.getTitle())).findAny().get(); - String citation = CitationStyleGenerator.generateCitation(List.of(testEntry), style.getSource(), CitationStyleOutputFormat.HTML, context, bibEntryTypesManager).getFirst(); + String citation = CitationStyleGenerator.generateBibliography(List.of(testEntry), style.getSource(), CitationStyleOutputFormat.HTML, context, bibEntryTypesManager).getFirst(); // if the acm-siggraph.csl citation style changes this has to be modified String expected = "

" @@ -49,7 +49,7 @@ void aCMCitation() { void aPACitation() { context.setMode(BibDatabaseMode.BIBLATEX); CitationStyle style = styleList.stream().filter(e -> "American Psychological Association 7th edition".equals(e.getTitle())).findAny().get(); - String citation = CitationStyleGenerator.generateCitation(List.of(testEntry), style.getSource(), CitationStyleOutputFormat.HTML, context, bibEntryTypesManager).getFirst(); + String citation = CitationStyleGenerator.generateBibliography(List.of(testEntry), style.getSource(), CitationStyleOutputFormat.HTML, context, bibEntryTypesManager).getFirst(); // if the apa-7th-citation.csl citation style changes this has to be modified String expected = "
" @@ -61,7 +61,7 @@ void aPACitation() { } /** - * Fails due to citeproc-java ({@link CitationStyleGenerator#generateInText(List, String, CitationStyleOutputFormat, BibDatabaseContext, BibEntryTypesManager) generateInText}) returning an empty citation. + * Fails due to citeproc-java ({@link CitationStyleGenerator#generateCitation(List, String, CitationStyleOutputFormat, BibDatabaseContext, BibEntryTypesManager) generateCitation}) returning an empty citation. * Alphanumeric citations are thus, currently manually generated by formatting (see {@link org.jabref.logic.openoffice.oocsltext.CSLFormatUtils#generateAlphanumericCitation(List, BibDatabaseContext) generateAlphaNumericCitation}). */ @Test @@ -69,7 +69,7 @@ void aPACitation() { void din1502AlphanumericInTextCitation() throws IOException { context.setMode(BibDatabaseMode.BIBLATEX); CitationStyle style = styleList.stream().filter(e -> "DIN 1505-2 (alphanumeric, Deutsch) - standard superseded by ISO-690".equals(e.getTitle())).findAny().get(); - Citation citation = CitationStyleGenerator.generateInText(List.of(testEntry), style.getSource(), CitationStyleOutputFormat.HTML, context, bibEntryTypesManager); + Citation citation = CitationStyleGenerator.generateCitation(List.of(testEntry), style.getSource(), CitationStyleOutputFormat.HTML, context, bibEntryTypesManager); String inTextCitationText = citation.getText(); assertEquals("[Smit2016]", inTextCitationText); @@ -84,7 +84,7 @@ void ignoreNewLine() { String expected = "
\n" + "
[1]
F. Last and J. Doe,
\n" + "
\n"; - String citation = CitationStyleGenerator.generateCitation(List.of(entry), CitationStyle.getDefault(), bibEntryTypesManager); + String citation = CitationStyleGenerator.generateBibliography(List.of(entry), CitationStyle.getDefault(), bibEntryTypesManager); assertEquals(expected, citation); } @@ -97,14 +97,14 @@ void ignoreCarriageReturnNewLine() { String expected = "
\n" + "
[1]
F. Last and J. Doe,
\n" + "
\n"; - String citation = CitationStyleGenerator.generateCitation(List.of(entry), CitationStyle.getDefault(), bibEntryTypesManager); + String citation = CitationStyleGenerator.generateBibliography(List.of(entry), CitationStyle.getDefault(), bibEntryTypesManager); assertEquals(expected, citation); } @Test void missingCitationStyle() { String expected = Localization.lang("Cannot generate preview based on selected citation style."); - String citation = CitationStyleGenerator.generateCitation(List.of(new BibEntry()), "faulty citation style", bibEntryTypesManager); + String citation = CitationStyleGenerator.generateBibliography(List.of(new BibEntry()), "faulty citation style", bibEntryTypesManager); assertEquals(expected, citation); } @@ -117,7 +117,7 @@ void htmlFormat() { String style = CitationStyle.getDefault().getSource(); CitationStyleOutputFormat format = CitationStyleOutputFormat.HTML; - String actualCitation = CitationStyleGenerator.generateCitation(List.of(testEntry), style, format, context, bibEntryTypesManager).getFirst(); + String actualCitation = CitationStyleGenerator.generateBibliography(List.of(testEntry), style, format, context, bibEntryTypesManager).getFirst(); assertEquals(expectedCitation, actualCitation); } @@ -128,7 +128,7 @@ void textFormat() { String style = CitationStyle.getDefault().getSource(); CitationStyleOutputFormat format = CitationStyleOutputFormat.TEXT; - String actualCitation = CitationStyleGenerator.generateCitation(List.of(testEntry), style, format, context, bibEntryTypesManager).getFirst(); + String actualCitation = CitationStyleGenerator.generateBibliography(List.of(testEntry), style, format, context, bibEntryTypesManager).getFirst(); assertEquals(expectedCitation, actualCitation); } @@ -142,7 +142,7 @@ void handleDiacritics() { String expected = "
\n" + "
[1]
F. Läst and J. Doe,
\n" + "
\n"; - String citation = CitationStyleGenerator.generateCitation(List.of(entry), CitationStyle.getDefault(), bibEntryTypesManager); + String citation = CitationStyleGenerator.generateBibliography(List.of(entry), CitationStyle.getDefault(), bibEntryTypesManager); assertEquals(expected, citation); } @@ -153,7 +153,7 @@ void handleAmpersand() { String style = CitationStyle.getDefault().getSource(); CitationStyleOutputFormat format = CitationStyleOutputFormat.TEXT; - String actualCitation = CitationStyleGenerator.generateCitation(List.of(testEntry), style, format, context, bibEntryTypesManager).getFirst(); + String actualCitation = CitationStyleGenerator.generateBibliography(List.of(testEntry), style, format, context, bibEntryTypesManager).getFirst(); assertEquals(expectedCitation, actualCitation); } @@ -178,7 +178,7 @@ void handleCrossRefFields() { BibDatabaseContext bibDatabaseContext = new BibDatabaseContext(new BibDatabase(List.of(firstEntry, secondEntry))); String style = CitationStyle.getDefault().getSource(); - String actualCitation = CitationStyleGenerator.generateCitation(List.of(firstEntry), style, CitationStyleOutputFormat.TEXT, bibDatabaseContext, bibEntryTypesManager).getFirst(); + String actualCitation = CitationStyleGenerator.generateBibliography(List.of(firstEntry), style, CitationStyleOutputFormat.TEXT, bibDatabaseContext, bibEntryTypesManager).getFirst(); assertEquals(expectedCitation, actualCitation); } @@ -591,7 +591,7 @@ static Stream cslMapping() { void cslMapping(String expected, BibDatabaseMode mode, BibEntry entry, String cslFileName) { context.setMode(mode); - String citation = CitationStyleGenerator.generateCitation( + String citation = CitationStyleGenerator.generateBibliography( List.of(entry), CitationStyle.createCitationStyleFromFile(cslFileName).orElseThrow().getSource(), CitationStyleOutputFormat.TEXT, diff --git a/src/test/java/org/jabref/logic/citationstyle/CitationStyleTest.java b/src/test/java/org/jabref/logic/citationstyle/CitationStyleTest.java index 8423e534659..d6ac7203d9b 100644 --- a/src/test/java/org/jabref/logic/citationstyle/CitationStyleTest.java +++ b/src/test/java/org/jabref/logic/citationstyle/CitationStyleTest.java @@ -30,7 +30,7 @@ void getDefault() { void defaultCitation() { BibDatabaseContext context = new BibDatabaseContext(new BibDatabase(List.of(TestEntry.getTestEntry()))); context.setMode(BibDatabaseMode.BIBLATEX); - String citation = CitationStyleGenerator.generateCitation(List.of(TestEntry.getTestEntry()), CitationStyle.getDefault().getSource(), CitationStyleOutputFormat.HTML, context, new BibEntryTypesManager()).getFirst(); + String citation = CitationStyleGenerator.generateBibliography(List.of(TestEntry.getTestEntry()), CitationStyle.getDefault().getSource(), CitationStyleOutputFormat.HTML, context, new BibEntryTypesManager()).getFirst(); // if the default citation style changes this has to be modified String expected = """ diff --git a/src/test/java/org/jabref/logic/openoffice/ReferenceMarkTest.java b/src/test/java/org/jabref/logic/openoffice/ReferenceMarkTest.java index 7ac3f127dec..a23895b1f93 100644 --- a/src/test/java/org/jabref/logic/openoffice/ReferenceMarkTest.java +++ b/src/test/java/org/jabref/logic/openoffice/ReferenceMarkTest.java @@ -1,5 +1,6 @@ package org.jabref.logic.openoffice; +import java.util.List; import java.util.stream.Stream; import org.junit.jupiter.api.DisplayName; @@ -14,19 +15,35 @@ class ReferenceMarkTest { @ParameterizedTest @MethodSource @DisplayName("Test parsing of valid reference marks") - void validParsing(String name, String expectedCitationKey, String expectedCitationNumber, String expectedUniqueId) { + void validParsing(String name, List expectedCitationKeys, List expectedCitationNumbers, String expectedUniqueId) { ReferenceMark referenceMark = new ReferenceMark(name); - assertEquals(expectedCitationKey, referenceMark.getCitationKey()); - assertEquals(expectedCitationNumber, String.valueOf(referenceMark.getCitationNumber())); + assertEquals(expectedCitationKeys, referenceMark.getCitationKeys()); + assertEquals(expectedCitationNumbers, referenceMark.getCitationNumbers()); assertEquals(expectedUniqueId, referenceMark.getUniqueId()); } private static Stream validParsing() { return Stream.of( - Arguments.of("JABREF_key1 CID_12345 uniqueId1", "key1", "12345", "uniqueId1"), - Arguments.of("JABREF_key2 CID_67890 uniqueId2", "key2", "67890", "uniqueId2"), - Arguments.of("JABREF_key3 CID_54321 uniqueId3", "key3", "54321", "uniqueId3") + // Single citation cases + Arguments.of( + "JABREF_key1 CID_12345 uniqueId1", + List.of("key1"), List.of(12345), "uniqueId1" + ), + Arguments.of( + "JABREF_key2 CID_67890 uniqueId2", + List.of("key2"), List.of(67890), "uniqueId2" + ), + + // Multiple citation cases + Arguments.of( + "JABREF_key3 CID_54321, JABREF_key4 CID_98765 uniqueId3", + List.of("key3", "key4"), List.of(54321, 98765), "uniqueId3" + ), + Arguments.of( + "JABREF_key5 CID_11111, JABREF_key6 CID_22222, JABREF_key7 CID_33333 uniqueId4", + List.of("key5", "key6", "key7"), List.of(11111, 22222, 33333), "uniqueId4" + ) ); } } diff --git a/src/test/java/org/jabref/logic/openoffice/oocsltext/CSLFormatUtilsTest.java b/src/test/java/org/jabref/logic/openoffice/oocsltext/CSLFormatUtilsTest.java index ada5ac67c42..57e054027fa 100644 --- a/src/test/java/org/jabref/logic/openoffice/oocsltext/CSLFormatUtilsTest.java +++ b/src/test/java/org/jabref/logic/openoffice/oocsltext/CSLFormatUtilsTest.java @@ -143,13 +143,13 @@ static Stream ooHTMLTransformFromRawHTML() { /** * Test to check correct transformation of raw CSL bibliography generated by citeproc-java methods into OO-ready text. *

- * Precondition: This test assumes that {@link CitationStyleGenerator#generateCitation(List, String, CitationStyleOutputFormat, BibDatabaseContext, BibEntryTypesManager) generateCitation} works as expected. + * Precondition: This test assumes that {@link CitationStyleGenerator#generateBibliography(List, String, CitationStyleOutputFormat, BibDatabaseContext, BibEntryTypesManager) generateBibliography} works as expected. *

*/ @ParameterizedTest @MethodSource void ooHTMLTransformFromRawBibliography(String expected, CitationStyle style) { - String citation = CitationStyleGenerator.generateCitation(List.of(testEntry), style.getSource(), CSLFormatUtils.OUTPUT_FORMAT, context, bibEntryTypesManager).getFirst(); + String citation = CitationStyleGenerator.generateBibliography(List.of(testEntry), style.getSource(), CSLFormatUtils.OUTPUT_FORMAT, context, bibEntryTypesManager).getFirst(); String actual = CSLFormatUtils.transformHTML(citation); assertEquals(expected, actual); } @@ -275,13 +275,13 @@ static Stream ooHTMLTransformFromRawBibliography() { /** * Test to check correct transformation of raw CSL citation with a single entry generated by citeproc-java methods into OO-ready text. *

- * Precondition: This test assumes that {@link CitationStyleGenerator#generateInText(List, String, CitationStyleOutputFormat, BibDatabaseContext, BibEntryTypesManager) generateInText} works as expected. + * Precondition: This test assumes that {@link CitationStyleGenerator#generateCitation(List, String, CitationStyleOutputFormat, BibDatabaseContext, BibEntryTypesManager) generateCitation} works as expected. *

*/ @ParameterizedTest @MethodSource void ooHTMLTransformFromCitationWithSingleEntry(String expected, CitationStyle style) throws IOException { - Citation citation = CitationStyleGenerator.generateInText(List.of(testEntry), style.getSource(), CSLFormatUtils.OUTPUT_FORMAT, context, bibEntryTypesManager); + Citation citation = CitationStyleGenerator.generateCitation(List.of(testEntry), style.getSource(), CSLFormatUtils.OUTPUT_FORMAT, context, bibEntryTypesManager); String inTextCitationText = citation.getText(); String actual = CSLFormatUtils.transformHTML(inTextCitationText); OOText ooText = OOText.fromString(actual); @@ -377,7 +377,7 @@ static Stream ooHTMLTransformFromCitationWithSingleEntry() { /** * Test to check correct transformation of raw CSL citations with multiple entries generated by citeproc-java methods into OO-ready text. *

- * Precondition: This test assumes that {@link CitationStyleGenerator#generateInText(List, String, CitationStyleOutputFormat, BibDatabaseContext, BibEntryTypesManager) generateInText} works as expected. + * Precondition: This test assumes that {@link CitationStyleGenerator#generateCitation(List, String, CitationStyleOutputFormat, BibDatabaseContext, BibEntryTypesManager) generateCitation} works as expected. *

*/ @ParameterizedTest @@ -408,7 +408,7 @@ void ooHTMLTransformFromCitationWithMultipleEntries(String expected, CitationSty List entries = List.of(entry1, entry2); BibDatabaseContext context = new BibDatabaseContext(new BibDatabase(entries)); context.setMode(BibDatabaseMode.BIBLATEX); - Citation citation = CitationStyleGenerator.generateInText(entries, style.getSource(), CSLFormatUtils.OUTPUT_FORMAT, context, bibEntryTypesManager); + Citation citation = CitationStyleGenerator.generateCitation(entries, style.getSource(), CSLFormatUtils.OUTPUT_FORMAT, context, bibEntryTypesManager); String inTextCitationText = citation.getText(); String actual = CSLFormatUtils.transformHTML(inTextCitationText); assertEquals(expected, actual); @@ -503,14 +503,14 @@ static Stream ooHTMLTransformFromCitationWithMultipleEntries() { * The numeric index should change to the provided "current number". * The rest of the citation should stay as it is (other numbers in the body shouldn't be affected). *

- * Precondition 1: This test assumes that {@link CitationStyleGenerator#generateCitation(List, String, CitationStyleOutputFormat, BibDatabaseContext, BibEntryTypesManager) generateCitation} works as expected.
+ * Precondition 1: This test assumes that {@link CitationStyleGenerator#generateBibliography(List, String, CitationStyleOutputFormat, BibDatabaseContext, BibEntryTypesManager) generateBibliography} works as expected.
* Precondition 2: This test assumes that the method {@link CSLFormatUtils#transformHTML(String) transformHTML} works as expected.
* Precondition 3: Run this test ONLY on numeric Citation Styles.

*/ @ParameterizedTest @MethodSource void updateSingleNumericCitation(String expected, CitationStyle style) { - String citation = CitationStyleGenerator.generateCitation(List.of(testEntry), style.getSource(), CSLFormatUtils.OUTPUT_FORMAT, context, bibEntryTypesManager).getFirst(); + String citation = CitationStyleGenerator.generateBibliography(List.of(testEntry), style.getSource(), CSLFormatUtils.OUTPUT_FORMAT, context, bibEntryTypesManager).getFirst(); String transformedCitation = CSLFormatUtils.transformHTML(citation); String actual = CSLFormatUtils.updateSingleBibliographyNumber(transformedCitation, 3); assertEquals(expected, actual);