Skip to content

Commit

Permalink
[IMP] Add re-distribution of numeric CSL citations (#11712)
Browse files Browse the repository at this point in the history
* 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 <siedlerkiller@gmail.com>
  • Loading branch information
subhramit and Siedlerchr committed Sep 7, 2024
1 parent e801f41 commit bcbe126
Show file tree
Hide file tree
Showing 15 changed files with 375 additions and 195 deletions.
28 changes: 14 additions & 14 deletions src/main/java/org/jabref/gui/openoffice/OOBibBase.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -161,9 +161,9 @@ void showDialog(String errorTitle, OOError err) {

OOVoidResult<OOError> collectResults(String errorTitle, List<OOVoidResult<OOError>> 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 {
Expand Down Expand Up @@ -233,10 +233,10 @@ private static OOVoidResult<OOError> 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) {
Expand Down Expand Up @@ -322,7 +322,7 @@ OOResult<OOFrontend, OOError> 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));
}
}
Expand Down Expand Up @@ -601,7 +601,7 @@ public void guiActionInsertEntry(List<BibEntry> 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
Expand Down Expand Up @@ -813,7 +813,7 @@ public Optional<BibDatabase> exportCitedHelper(List<BibDatabase> 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);
}
Expand Down Expand Up @@ -881,8 +881,8 @@ public void guiActionUpdateDocument(List<BibDatabase> 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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ private List<String> generateCitations() throws IOException {
}

if (styleSource != null) {
return CitationStyleGenerator.generateCitations(
return CitationStyleGenerator.generateBibliographies(
selectedEntries,
styleSource,
outputFormat,
Expand Down
6 changes: 3 additions & 3 deletions src/main/java/org/jabref/logic/citationstyle/CSLAdapter.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
* <p>
* 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.
*
* <p>
* 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
Expand Down Expand Up @@ -51,7 +51,7 @@ public synchronized List<String> makeBibliography(List<BibEntry> bibEntries, Str
return Arrays.asList(bibliography.getEntries());
}

public synchronized Citation makeInText(List<BibEntry> bibEntries, String style, CitationStyleOutputFormat outputFormat, BibDatabaseContext databaseContext, BibEntryTypesManager entryTypesManager) throws IOException {
public synchronized Citation makeCitation(List<BibEntry> bibEntries, String style, CitationStyleOutputFormat outputFormat, BibDatabaseContext databaseContext, BibEntryTypesManager entryTypesManager) throws IOException {
dataProvider.setData(bibEntries, databaseContext, entryTypesManager);
initialize(style, outputFormat);
cslInstance.registerCitationItems(dataProvider.getIds());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,38 +31,38 @@ 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<BibEntry> bibEntries, CitationStyle style, BibEntryTypesManager entryTypesManager) {
return generateCitation(bibEntries, style.getSource(), entryTypesManager);
protected static String generateBibliography(List<BibEntry> bibEntries, CitationStyle style, BibEntryTypesManager entryTypesManager) {
return generateBibliography(bibEntries, style.getSource(), entryTypesManager);
}

/**
* Generates a Citation based on a given list of entries and a style with a default {@link BibDatabaseContext}
*
* @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<BibEntry> bibEntries, String style, BibEntryTypesManager entryTypesManager) {
return generateCitation(bibEntries, style, CitationStyleOutputFormat.HTML, new BibDatabaseContext(), entryTypesManager).getFirst();
protected static String generateBibliography(List<BibEntry> bibEntries, String style, BibEntryTypesManager entryTypesManager) {
return generateBibliography(bibEntries, style, CitationStyleOutputFormat.HTML, new BibDatabaseContext(), entryTypesManager).getFirst();
}

/**
* Generates a Citation based on a given list of entries, style, and output format
*
* @implNote the citation is generated using JavaScript which may take some time, better call it from outside the main Thread
*/
public static List<String> generateCitation(List<BibEntry> bibEntries, String style, CitationStyleOutputFormat outputFormat, BibDatabaseContext databaseContext, BibEntryTypesManager entryTypesManager) {
return generateCitations(bibEntries, style, outputFormat, databaseContext, entryTypesManager);
public static List<String> generateBibliography(List<BibEntry> bibEntries, String style, CitationStyleOutputFormat outputFormat, BibDatabaseContext databaseContext, BibEntryTypesManager entryTypesManager) {
return generateBibliographies(bibEntries, style, outputFormat, databaseContext, entryTypesManager);
}

public static Citation generateInText(List<BibEntry> 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<BibEntry> bibEntries, String style, CitationStyleOutputFormat outputFormat, BibDatabaseContext databaseContext, BibEntryTypesManager entryTypesManager) throws IOException {
return CSL_ADAPTER.makeCitation(bibEntries, style, outputFormat, databaseContext, entryTypesManager);
}

/**
* Generates the citation for multiple entries at once.
*
* @implNote The citations are generated using JavaScript which may take some time, better call it from outside the main thread.
*/
public static List<String> generateCitations(List<BibEntry> bibEntries, String style, CitationStyleOutputFormat outputFormat, BibDatabaseContext databaseContext, BibEntryTypesManager entryTypesManager) {
public static List<String> generateBibliographies(List<BibEntry> bibEntries, String style, CitationStyleOutputFormat outputFormat, BibDatabaseContext databaseContext, BibEntryTypesManager entryTypesManager) {
try {
return CSL_ADAPTER.makeBibliography(bibEntries, style, outputFormat, databaseContext, entryTypesManager);
} catch (IllegalArgumentException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
80 changes: 45 additions & 35 deletions src/main/java/org/jabref/logic/openoffice/ReferenceMark.java
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -9,77 +11,85 @@
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<String> citationKeys;
private List<Integer> citationNumbers;
private String uniqueId;

/**
* @param name Format: <code>JABREF_{citationKey} CID_{citationNumber} {uniqueId}</code>
*/
public ReferenceMark(String name) {
this.name = name;
parse(name);
}

Matcher matcher = getMatcher(name);
public ReferenceMark(String name, List<String> citationKeys, List<Integer> 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() {
return name;
}

/**
* The BibTeX citation key
* The BibTeX citation keys
*/
public String getCitationKey() {
return citationKey;
public List<String> getCitationKeys() {
return citationKeys;
}

public int getCitationNumber() {
return citationNumber;
public List<Integer> getCitationNumbers() {
return citationNumbers;
}

public String getUniqueId() {
return uniqueId;
}

public static Optional<ReferenceMark> 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);
}
}
Loading

0 comments on commit bcbe126

Please sign in to comment.