Skip to content

Persist citation nature in the CSL reference mark#15437

Open
pluto-han wants to merge 4 commits intoJabRef:mainfrom
pluto-han:Persist-citation-nature
Open

Persist citation nature in the CSL reference mark#15437
pluto-han wants to merge 4 commits intoJabRef:mainfrom
pluto-han:Persist-citation-nature

Conversation

@pluto-han
Copy link
Copy Markdown
Contributor

Related issues and pull requests

Closes #15434

PR Description

This PR embeded citation nature into reference marks, and use enum to replace boolean variables.

Steps to test

  1. Insert normal citations.
image
  1. Insert an in-text citation, all citations become in-text citations
image
  1. Insert an empty citation, all citations become empty citations
image

Checklist

  • I own the copyright of the code submitted and I license it under the MIT license
  • I manually tested my changes in running JabRef (always required)
  • [/] I added JUnit tests for changes (if applicable)
  • I added screenshots in the PR description (if change is visible to the user)
  • [/] I added a screenshot in the PR description showing a library with a single entry with me as author and as title the issue number
  • I described the change in CHANGELOG.md in a way that can be understood by the average user (if change is visible to the user)
  • I checked the user documentation for up to dateness and submitted a pull request to our user documentation repository

@pluto-han pluto-han changed the title Persist in-text/empty nature in the CSL reference mark Persist citation nature in the CSL reference mark Mar 28, 2026
@qodo-free-for-open-source-projects
Copy link
Copy Markdown
Contributor

Review Summary by Qodo

Persist citation type in CSL reference marks using enum

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Replace boolean inText variable with CSLCitationType enum
• Embed citation type (NORMAL/IN_TEXT/EMPTY) into CSL reference marks
• Update reference mark format to persist citation nature across document operations
• Refactor citation type tracking throughout adapter and manager classes
Diagram
flowchart LR
  A["Boolean inText"] -->|Replace with| B["CSLCitationType Enum"]
  B -->|NORMAL| C["Reference Mark Format"]
  B -->|IN_TEXT| C
  B -->|EMPTY| C
  C -->|Embedded in| D["Citation Persistence"]
Loading

Grey Divider

File Changes

1. jablib/src/main/java/org/jabref/logic/openoffice/oocsltext/CSLCitationType.java ✨ Enhancement +7/-0

Create CSLCitationType enum for citation types

• New enum class created with three citation type values
• Replaces boolean variables for citation nature tracking
• Provides type-safe representation of citation states

jablib/src/main/java/org/jabref/logic/openoffice/oocsltext/CSLCitationType.java


2. jablib/src/main/java/org/jabref/logic/openoffice/ReferenceMark.java ✨ Enhancement +21/-10

Embed citation type into reference mark format

• Replace boolean inText field with CSLCitationType citationType
• Add EMPTY_MARKER and NORMAL_MARKER constants to reference mark format
• Update regex pattern to recognize all three citation type markers
• Modify parsing logic to map markers to enum values using switch expression
• Update constructor and getter methods to use enum instead of boolean

jablib/src/main/java/org/jabref/logic/openoffice/ReferenceMark.java


3. jablib/src/main/java/org/jabref/logic/openoffice/oocsltext/CSLReferenceMark.java ✨ Enhancement +14/-8

Update CSLReferenceMark to use citation type enum

• Replace boolean isInText field with CSLCitationType citationType
• Update of() factory method to accept enum parameter
• Modify buildReferenceName() to embed all three citation type markers
• Update constructor and method signatures to use enum

jablib/src/main/java/org/jabref/logic/openoffice/oocsltext/CSLReferenceMark.java


View more (4)
4. jablib/src/main/java/org/jabref/logic/openoffice/oocsltext/CSLReferenceMarkManager.java ✨ Enhancement +34/-26

Refactor manager to track citation type with enum

• Replace boolean isInTextCite with CSLCitationType citationType field
• Update createReferenceMark() and insertReferenceIntoOO() to accept enum
• Refactor updateMarkAndTextWithNewStyle() to handle all three citation types
• Add removeCitationMarker() helper method to strip old markers before adding new ones
• Replace hasInTextMarks() with getCitationType() getter
• Update citation type tracking during mark reading and updates

jablib/src/main/java/org/jabref/logic/openoffice/oocsltext/CSLReferenceMarkManager.java


5. jablib/src/main/java/org/jabref/logic/openoffice/oocsltext/CSLCitationOOAdapter.java ✨ Enhancement +46/-27

Refactor adapter to use citation type enum

• Replace boolean inTextUsed and boolean inTextNatureChanged with CSLCitationType citationType
 and boolean citationTypeIsChanged
• Update insertCitation(), insertInTextCitation(), and insertEmptyCitation() to use enum
• Refactor updateAllCitationsWithNewStyle() to handle EMPTY citation type
• Update all method signatures and internal logic to pass enum instead of boolean
• Simplify citation type change detection using enum comparison

jablib/src/main/java/org/jabref/logic/openoffice/oocsltext/CSLCitationOOAdapter.java


6. jablib/src/test/java/org/jabref/logic/openoffice/ReferenceMarkTest.java 🧪 Tests +31/-25

Update tests to use citation type enum

• Update test cases to use CSLCitationType enum instead of boolean
• Add NORMAL marker to all existing test reference mark names
• Add new test case for EMPTY citation type
• Update method signature and assertions to use enum

jablib/src/test/java/org/jabref/logic/openoffice/ReferenceMarkTest.java


7. CHANGELOG.md 📝 Documentation +1/-0

Document citation type persistence changes

• Document CSL reference format changes with citation type addition
• Reference related issue numbers for tracking

CHANGELOG.md


Grey Divider

Qodo Logo

@qodo-free-for-open-source-projects
Copy link
Copy Markdown
Contributor

qodo-free-for-open-source-projects bot commented Mar 28, 2026

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (1) 📎 Requirement gaps (0) 📐 Spec deviations (0)

Grey Divider


Action required

1. Changelog entry too technical 📘 Rule violation ⚙ Maintainability
Description
The new CHANGELOG.md entry includes developer/internal implementation details (enum/boolean method
signature) and has inconsistent issue link formatting (missing space between references). This is
not end-user oriented and reduces release note readability.
Code

CHANGELOG.md[28]

+- We changed CSL reference format by adding citation type at the end, and used enum to replace boolean variables in the method signature. [#15370](https://github.com/JabRef/jabref/issues/15370)[#15434](https://github.com/JabRef/jabref/issues/15434)
Evidence
PR Compliance requires CHANGELOG.md entries to be end-user oriented and professionally formatted.
The added entry describes internal refactoring details and concatenates two issue links without
spacing.

AGENTS.md
CHANGELOG.md[28-28]
Best Practice: Learned patterns

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new changelog line is written in developer-focused terms and the issue links are not consistently formatted (missing spacing), reducing end-user readability.
## Issue Context
Changelog entries should describe user-visible behavior changes in clear language and keep Markdown links consistently formatted.
## Fix Focus Areas
- CHANGELOG.md[28-28]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Legacy mark truncation🐞 Bug ✓ Correctness
Description
CSLReferenceMarkManager.removeCitationMarker assumes every reference mark ends with
IN_TEXT/EMPTY/NORMAL and will blindly strip NORMAL when no marker is present. Legacy marks without a
marker suffix will have their uniqueId truncated (or can throw StringIndexOutOfBounds for short
IDs), breaking subsequent mark updates.
Code

jablib/src/main/java/org/jabref/logic/openoffice/oocsltext/CSLReferenceMarkManager.java[R331-339]

+    private String removeCitationMarker(String referenceMarkName) {
+        if (referenceMarkName.endsWith(ReferenceMark.IN_TEXT_MARKER)) {
+            return referenceMarkName.substring(0, referenceMarkName.length() - ReferenceMark.IN_TEXT_MARKER.length() - 1);
+        }
+        if (referenceMarkName.endsWith(ReferenceMark.EMPTY_MARKER)) {
+            return referenceMarkName.substring(0, referenceMarkName.length() - ReferenceMark.EMPTY_MARKER.length() - 1);
+        }
+
+        return referenceMarkName.substring(0, referenceMarkName.length() - ReferenceMark.NORMAL_MARKER.length() - 1);
Evidence
ReferenceMark explicitly allows the trailing marker to be absent (optional group in the regex), so
legacy marks without a suffix are valid inputs; however updateMarkAndTextWithNewStyle always calls
removeCitationMarker first, which corrupts such legacy names by stripping "NORMAL" length
unconditionally.

jablib/src/main/java/org/jabref/logic/openoffice/ReferenceMark.java[15-76]
jablib/src/main/java/org/jabref/logic/openoffice/oocsltext/CSLReferenceMarkManager.java[265-340]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`removeCitationMarker` assumes every reference mark ends with a citation marker and truncates names for legacy marks that have no marker suffix.
### Issue Context
`ReferenceMark` parsing still permits the marker to be absent, so documents created before this PR can contain marks without `NORMAL`/`EMPTY`/`IN_TEXT` at the end.
### Fix Focus Areas
- jablib/src/main/java/org/jabref/logic/openoffice/oocsltext/CSLReferenceMarkManager.java[331-340]
- jablib/src/main/java/org/jabref/logic/openoffice/oocsltext/CSLReferenceMarkManager.java[265-277]
### Suggested fix
Update `removeCitationMarker` to remove the suffix only if the last token matches one of the known markers (e.g., split on space and check the last token, or use a regex like `\s+(IN_TEXT|EMPTY|NORMAL)$`). If no marker is present, return the original name unchanged.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Citation type derived from last 🐞 Bug ✓ Correctness
Description
CSLReferenceMarkManager.readAndUpdateExistingMarks overwrites the manager-wide citationType for each
mark and ends up returning the last mark’s type. If a document contains mixed/legacy marks,
CSLCitationOOAdapter can skip required global updates or apply the wrong global conversion, leaving
mixed citation types in the document.
Code

jablib/src/main/java/org/jabref/logic/openoffice/oocsltext/CSLReferenceMarkManager.java[R148-152]

                  CSLReferenceMark mark = new CSLReferenceMark(named, referenceMark);
                  marksByName.put(name, mark);
                  marksInOrder.add(mark);
-                    isInTextCite = isInTextCite || referenceMark.isInText();
+                    citationType = referenceMark.getCitationType();
Evidence
The manager sets citationType = referenceMark.getCitationType() inside the loop, so the final
value depends on iteration order rather than aggregating across marks. CSLCitationOOAdapter uses its
cached citationType to decide whether to call updateAllCitationsWithNewStyle before inserting a
NORMAL citation; if the cached type is wrong (e.g., manager returned NORMAL while some marks are
IN_TEXT), the adapter will not update existing citations and will insert a NORMAL mark, creating a
mixed-type document.

jablib/src/main/java/org/jabref/logic/openoffice/oocsltext/CSLReferenceMarkManager.java[129-152]
jablib/src/main/java/org/jabref/logic/openoffice/oocsltext/CSLCitationOOAdapter.java[80-100]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`readAndUpdateExistingMarks` sets `citationType` to the last mark’s type, which can be wrong when marks differ (common for legacy docs or corrupted state) and causes the adapter’s global-update decision to be incorrect.
### Issue Context
The adapter relies on a document-wide citation type to decide whether it must rewrite all existing citations before inserting a new one.
### Fix Focus Areas
- jablib/src/main/java/org/jabref/logic/openoffice/oocsltext/CSLReferenceMarkManager.java[129-163]
- jablib/src/main/java/org/jabref/logic/openoffice/oocsltext/CSLCitationOOAdapter.java[57-71]
- jablib/src/main/java/org/jabref/logic/openoffice/oocsltext/CSLCitationOOAdapter.java[80-100]
### Suggested fix
Compute document citationType by aggregating across all marks (e.g., if any EMPTY -> EMPTY, else if any IN_TEXT -> IN_TEXT, else NORMAL), and optionally log a warning if multiple types are found. Ensure the adapter refreshes its `citationType` from the manager after `readAndUpdateExistingMarks()` in workflows that depend on the current document state.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

4. Trivial citationType comments📘 Rule violation ⚙ Maintainability
Description
New comments restate what the variable names and condition already clearly express (e.g., `//
Current citation type in the doc, // True when..., and the comment above the if`). This adds
noise rather than explaining intent.
Code

jablib/src/main/java/org/jabref/logic/openoffice/oocsltext/CSLCitationOOAdapter.java[R52-55]

+    // Current citation type in the doc
+    private CSLCitationType citationType;
+    // True when user inserts a citation with different type
+    private boolean citationTypeIsChanged;
Evidence
PR Compliance requires comments to explain the "why" rather than paraphrase the code. The added
comments simply repeat the meaning of citationType / citationTypeIsChanged and the subsequent
if logic.

AGENTS.md
jablib/src/main/java/org/jabref/logic/openoffice/oocsltext/CSLCitationOOAdapter.java[52-55]
jablib/src/main/java/org/jabref/logic/openoffice/oocsltext/CSLCitationOOAdapter.java[88-91]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Several newly-added comments restate obvious information already conveyed by variable names and the `if` condition.
## Issue Context
Comments should focus on rationale/intent (the "why"), not paraphrase code.
## Fix Focus Areas
- jablib/src/main/java/org/jabref/logic/openoffice/oocsltext/CSLCitationOOAdapter.java[52-55]
- jablib/src/main/java/org/jabref/logic/openoffice/oocsltext/CSLCitationOOAdapter.java[88-91]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. Legacy format tests removed🐞 Bug ⚙ Maintainability
Description
ReferenceMarkTest no longer covers parsing of legacy NORMAL marks without a trailing marker, even
though ReferenceMark still accepts missing markers. This reduces coverage for the
backward-compatibility paths that the production code must support.
Code

jablib/src/test/java/org/jabref/logic/openoffice/ReferenceMarkTest.java[R29-49]

  private static Stream<Arguments> validParsing() {
      return Stream.of(
              // Single citation cases
              Arguments.of(
-                        "JABREF_key1 CID_12345 uniqueId1",
-                        List.of("key1"), List.of(12345), "uniqueId1", false
+                        "JABREF_key1 CID_12345 uniqueId1 NORMAL",
+                        List.of("key1"), List.of(12345), "uniqueId1", CSLCitationType.NORMAL
              ),
              Arguments.of(
-                        "JABREF_key2 CID_67890 uniqueId2",
-                        List.of("key2"), List.of(67890), "uniqueId2", false
+                        "JABREF_key2 CID_67890 uniqueId2 NORMAL",
+                        List.of("key2"), List.of(67890), "uniqueId2", CSLCitationType.NORMAL
              ),
              // Multiple citation cases
              Arguments.of(
-                        "JABREF_key3 CID_54321, JABREF_key4 CID_98765 uniqueId3",
-                        List.of("key3", "key4"), List.of(54321, 98765), "uniqueId3", false
+                        "JABREF_key3 CID_54321, JABREF_key4 CID_98765 uniqueId3 NORMAL",
+                        List.of("key3", "key4"), List.of(54321, 98765), "uniqueId3", CSLCitationType.NORMAL
              ),
              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", false
+                        "JABREF_key5 CID_11111, JABREF_key6 CID_22222, JABREF_key7 CID_33333 uniqueId4 NORMAL",
+                        List.of("key5", "key6", "key7"), List.of(11111, 22222, 33333), "uniqueId4", CSLCitationType.NORMAL
              ),
Evidence
The parsing regex makes the citation marker optional, but the updated tests only assert cases where
NORMAL is explicitly present, so regressions in legacy parsing/migration paths would not be caught
by unit tests.

jablib/src/main/java/org/jabref/logic/openoffice/ReferenceMark.java[23-25]
jablib/src/test/java/org/jabref/logic/openoffice/ReferenceMarkTest.java[29-49]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Unit tests no longer validate that legacy marks without a trailing citation marker still parse as NORMAL.
### Issue Context
`ReferenceMark` still allows the marker suffix to be absent, so legacy documents remain a supported input.
### Fix Focus Areas
- jablib/src/test/java/org/jabref/logic/openoffice/ReferenceMarkTest.java[29-49]
### Suggested fix
Add test cases for legacy NORMAL marks without the trailing `NORMAL` token (single and multiple citation forms) and assert `getCitationType() == CSLCitationType.NORMAL`. Consider adding focused tests for `removeCitationMarker`/renaming behavior on legacy names as well.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo


### Changed

- We changed CSL reference format by adding citation type at the end, and used enum to replace boolean variables in the method signature. [#15370](https://github.com/JabRef/jabref/issues/15370)[#15434](https://github.com/JabRef/jabref/issues/15434)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. Changelog entry too technical 📘 Rule violation ⚙ Maintainability

The new CHANGELOG.md entry includes developer/internal implementation details (enum/boolean method
signature) and has inconsistent issue link formatting (missing space between references). This is
not end-user oriented and reduces release note readability.
Agent Prompt
## Issue description
The new changelog line is written in developer-focused terms and the issue links are not consistently formatted (missing spacing), reducing end-user readability.

## Issue Context
Changelog entries should describe user-visible behavior changes in clear language and keep Markdown links consistently formatted.

## Fix Focus Areas
- CHANGELOG.md[28-28]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines 148 to 152
CSLReferenceMark mark = new CSLReferenceMark(named, referenceMark);
marksByName.put(name, mark);
marksInOrder.add(mark);
isInTextCite = isInTextCite || referenceMark.isInText();
citationType = referenceMark.getCitationType();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

3. Citation type derived from last 🐞 Bug ✓ Correctness

CSLReferenceMarkManager.readAndUpdateExistingMarks overwrites the manager-wide citationType for each
mark and ends up returning the last mark’s type. If a document contains mixed/legacy marks,
CSLCitationOOAdapter can skip required global updates or apply the wrong global conversion, leaving
mixed citation types in the document.
Agent Prompt
### Issue description
`readAndUpdateExistingMarks` sets `citationType` to the last mark’s type, which can be wrong when marks differ (common for legacy docs or corrupted state) and causes the adapter’s global-update decision to be incorrect.

### Issue Context
The adapter relies on a document-wide citation type to decide whether it must rewrite all existing citations before inserting a new one.

### Fix Focus Areas
- jablib/src/main/java/org/jabref/logic/openoffice/oocsltext/CSLReferenceMarkManager.java[129-163]
- jablib/src/main/java/org/jabref/logic/openoffice/oocsltext/CSLCitationOOAdapter.java[57-71]
- jablib/src/main/java/org/jabref/logic/openoffice/oocsltext/CSLCitationOOAdapter.java[80-100]

### Suggested fix
Compute document citationType by aggregating across all marks (e.g., if any EMPTY -> EMPTY, else if any IN_TEXT -> IN_TEXT, else NORMAL), and optionally log a warning if multiple types are found. Ensure the adapter refreshes its `citationType` from the manager after `readAndUpdateExistingMarks()` in workflows that depend on the current document state.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@testlens-app

This comment has been minimized.

@testlens-app

This comment has been minimized.

// Current citation type in the doc
private CSLCitationType citationType;
// True when user inserts a citation with different type
private boolean citationTypeIsChanged;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable names are sufficiently descriptive - so the comments seem superfluous.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comments removed

Comment on lines +179 to +180
String citationType = parts[parts.length - 1];
int uniqueIdIndex = parts.length - 2;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add some field-level javadoc with a reference mark example, so that the numbers easily make sense when reading code.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated.

Comment on lines +79 to +80
default ->
CSLCitationType.NORMAL;
Copy link
Copy Markdown
Member

@subhramit subhramit Mar 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The negative side of putting this in a default case is that (hypothetically), a refrence mark JABREF_Keen_2011 CID_1 xyzuzuw1u FOOBAR would also be parsed successfully in this case - which is not exactly a good/"strong" programming practice.

The positive side is that this will allow for backward compatibility.
I think the positive outweighs here, so it's fine.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a refrence mark JABREF_Keen_2011 CID_1 xyzuzuw1u FOOBAR would also be parsed successfully in this case

Can reference mark with different citation type (other than intext/empty/normal) be created?

@testlens-app
Copy link
Copy Markdown

testlens-app bot commented Mar 28, 2026

✅ All tests passed ✅

🏷️ Commit: 88abbdb
▶️ Tests: 10212 executed
⚪️ Checks: 59/59 completed


Learn more about TestLens at testlens.app.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

LO/OO: Persist in-text/empty nature in the CSL reference mark

2 participants