Skip to content

LO: Fix zotero mode bibliography rendering, optimize performance - #16555

Open
subhramit wants to merge 12 commits into
JabRef:mainfrom
subhramit:fix-zotero-mode-bibliography
Open

LO: Fix zotero mode bibliography rendering, optimize performance#16555
subhramit wants to merge 12 commits into
JabRef:mainfrom
subhramit:fix-zotero-mode-bibliography

Conversation

@subhramit

@subhramit subhramit commented Aug 12, 2026

Copy link
Copy Markdown
Member

Summary

  • Use jabref-like rendering logic as citeproc-java doesn't support direct RTF, which cause a tab gap after the number.
  • Also use flexible body format as per JabRef's selectable bibliography properties, and disable irrelevant properties in Zotero mode such as Reference heading and heading format.
  • Optimize bulk-bibliography generation - we were individually sending each entry as List.of(entry) to citeproc to get the bibliography text, then edit its number. Since we are already refreshing/rewriting citations before generating bibliography, and the order of collection is guaranteed to be the order of appearance in the document, we call citeproc for all the entries together and don't have to edit any numbers.
  • Some refactoring to extract duplicated code and code quality improvements

Before:
image

After:
image

Steps to test

  • Generate IEEE bibliography with zotero compatibility mode enabled. Notice there is just one space after the number.
  • Change bibliography properties to "hanging indent". Refresh bibliography - see that hanging indent is applied.
image
  • See that irrelevant options are disabled.
image
  • Cite multiple entries in any mode using any numeric style, ensure that bibliography generated follows the order of citations in the document

Related issues and pull requests

Closes NA
Follow-up to #16282

AI usage

NA

Checklist

  • I own the copyright of the code submitted and I license it under the MIT license
  • [/] If AI tools were used, I disclosed them in the "AI usage" section and reviewed, understood, and take full ownership of all AI-generated code
  • 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

Signed-off-by: subhramit <subhramit.bb@live.in>
@qodo-free-for-open-source-projects

Copy link
Copy Markdown
Contributor

PR Summary by Qodo

Fix Zotero-mode bibliography number spacing in LibreOffice output

🐞 Bug fix 🕐 10-20 Minutes

Grey Divider

AI Description

• Fix extra gap after numeric bibliography labels in Zotero compatibility mode
• Normalize CSL left/right margin HTML parsing to tolerate whitespace/newlines
• Trim leading whitespace in right-inline blocks to ensure single-space separation
Diagram

graph TD
gen["CitationStyleGenerator"] -->|"CSL HTML"| html["Bibliography entry"] -->|"Zotero spacing fix"| adapter["CSLCitationOOAdapter"] -->|"HTML cleanup"| utils["CSLFormatUtils"] -->|"OO write"| writer["OOTextIntoOO"] -->|"UNO insert"| lo{{"LibreOffice"}}
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Use an HTML parser (e.g., jsoup) instead of regex
  • ➕ More robust handling of whitespace/newlines/nesting changes in citeproc output
  • ➕ Easier to extend for future CSL HTML quirks
  • ➖ Adds dependency and runtime overhead (even if small)
  • ➖ Might be overkill for a single known pattern; higher change surface
2. Keep tab alignment but normalize right-inline leading whitespace only
  • ➕ Preserves tab-stop based second-field alignment semantics
  • ➕ Minimal behavior change outside trimming
  • ➖ Still relies on tab rendering path that triggered the visible gap issue
  • ➖ May remain sensitive to how OOTextIntoOO/RTF-like conversion treats tabs
3. Fix upstream at generation layer (CitationStyleGenerator/citeproc config)
  • ➕ Centralizes formatting so all consumers benefit
  • ➕ Potentially eliminates per-consumer post-processing
  • ➖ Harder due to citeproc-java limitations and output format constraints
  • ➖ Risk of impacting non-LibreOffice outputs or other bibliography flows

Recommendation: The PR’s targeted post-processing (tolerating inter-div whitespace and stripping leading right-inline whitespace while using a single-space join) is the best trade-off: it fixes the Zotero-mode visual regression without adding dependencies or changing the broader CSL HTML transformation pipeline. If future CSL HTML variations expand, consider migrating the left/right margin handling to a small DOM-based normalizer.

Files changed (2) +11 / -4

Bug fix (2) +11 / -4
CSLCitationOOAdapter.javaFix Zotero bibliography left/right margin join to avoid extra gap +10/-3

Fix Zotero bibliography left/right margin join to avoid extra gap

• Updates the left/right margin matching to tolerate whitespace between CSL divs and introduces a helper to join the blocks with a single space. Strips leading whitespace (including nbsp variants) from the right-inline block to prevent doubled spacing in Zotero compatibility bibliography entries.

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

CSLFormatUtils.javaHarden CSL margin replacement against whitespace/newlines +1/-1

Harden CSL margin replacement against whitespace/newlines

• Adjusts the margin-handling regex in transformHTML to allow whitespace/newlines between left-margin and right-inline divs and to ignore leading whitespace inside the right-inline block. This improves consistency of spacing after numeric labels across generated CSL HTML variants.

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

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Second-field align broken ⊘ Outdated 🐞 Bug ≡ Correctness
Description
In Zotero bibliography rendering, replaceLeftRightMarginBlocksWithSpace() now inserts a plain space
between the left-margin label and the right-inline text, but insertZoteroBibliography() still
configures ParaTabStops based on Bibliography.getSecondFieldAlign(). Without a tab character,
LibreOffice will not use the configured tab stops, so first-line text alignment can vary with label
width (e.g., “[9]” vs “[10]”) and SecondFieldAlign formatting won’t be applied as intended.
Code

jablib/src/main/java/org/jabref/logic/openoffice/oocsltext/CSLCitationOOAdapter.java[R382-385]

+        return CSL_LEFT_RIGHT_MARGIN_PATTERN.matcher(bibliographyEntry)
+                                            .replaceAll(matchResult -> matchResult.group(1)
+                                                    + " "
+                                                    + LEADING_RIGHT_INLINE_WHITESPACE_PATTERN.matcher(matchResult.group(2)).replaceFirst(""));
Evidence
The Zotero bibliography paragraph style is explicitly configured with tab stops derived from
SecondFieldAlign, which only take effect when the text contains tab characters. The new
replacement method inserts a normal space instead, so the configured ParaTabStops cannot be
applied to align the right-inline content.

jablib/src/main/java/org/jabref/logic/openoffice/oocsltext/CSLCitationOOAdapter.java[329-365]
jablib/src/main/java/org/jabref/logic/openoffice/oocsltext/CSLCitationOOAdapter.java[368-386]

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

## Issue description
`insertZoteroBibliography()` configures LibreOffice paragraph `ParaTabStops` to support CSL `SecondFieldAlign`, but the bibliography text no longer contains a `\t` between the left-margin number and the right-inline text (it uses a normal space). As a result, the tab stop configuration is ineffective and alignment can regress for styles relying on second-field alignment.
### Issue Context
The Zotero bibliography style configuration computes `firstLineIndent/indent` and sets `ParaTabStops` based on `generatedBibliography.getSecondFieldAlign()`. The text generation path should emit a tab character when tab stops are intended to be used.
### Fix Focus Areas
- jablib/src/main/java/org/jabref/logic/openoffice/oocsltext/CSLCitationOOAdapter.java[329-365]
- jablib/src/main/java/org/jabref/logic/openoffice/oocsltext/CSLCitationOOAdapter.java[368-386]
### Suggested fix approach
- Make the separator depend on `SecondFieldAlign` (available in `createZoteroBibliographyText(Bibliography)`):
- If `bibliography.getSecondFieldAlign() != SecondFieldAlign.FALSE`, keep using a `\t` between left/right blocks, but keep the new trimming of leading whitespace/`&nbsp;`/`&#160;` on the right-inline group to avoid the original “tab gap”.
- Otherwise, use a single normal space.
- Add/adjust a unit test (if feasible) to cover a Zotero bibliography entry where the right-inline starts with spaces/`&nbsp;` and verify the produced string contains `\t` when second-field alignment is enabled.

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



Remediation recommended

2. Duplicated CSL margin replacement ⊘ Outdated 📘 Rule violation ⚙ Maintainability
Description
The PR introduces a second implementation for converting CSL left/right margin `` blocks into inline
spacing, duplicating existing logic in CSLFormatUtils.transformHTML. The two implementations
already diverge in whitespace handling, increasing the risk of inconsistent bibliography rendering
and future bug fixes being applied to only one path.
Code

jablib/src/main/java/org/jabref/logic/openoffice/oocsltext/CSLCitationOOAdapter.java[R381-384]

+    static String replaceLeftRightMarginBlocksWithSpace(String bibliographyEntry) {
+        return CSL_LEFT_RIGHT_MARGIN_PATTERN.matcher(bibliographyEntry)
+                                            .replaceAll(matchResult -> matchResult.group(1)
+                                                    + " "
Evidence
Rule 39 requires equivalent operations to share a single helper/implementation to avoid divergent
semantics. The PR adds replaceLeftRightMarginBlocksWithSpace(...) in CSLCitationOOAdapter while
CSLFormatUtils.transformHTML(...) continues to perform an equivalent left/right margin
transformation separately, and the two approaches already differ in how they treat leading
whitespace in the right-inline block.

jablib/src/main/java/org/jabref/logic/openoffice/oocsltext/CSLCitationOOAdapter.java[368-386]
jablib/src/main/java/org/jabref/logic/openoffice/oocsltext/CSLFormatUtils.java[71-77]
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 PR now has two separate code paths that both rewrite `<div class="csl-left-margin">...` + `<div class="csl-right-inline">...` into inline text, but with different whitespace semantics. This duplication risks future behavioral drift and inconsistent output between Zotero bibliography rendering and general HTML transformation.
## Issue Context
- `CSLCitationOOAdapter.replaceLeftRightMarginBlocksWithSpace` trims leading whitespace/`&nbsp;`/`&#160;` from the right-inline content.
- `CSLFormatUtils.transformHTML` performs a similar replacement using `String.replaceAll(...)` and only tolerates `\s*` around the right-inline content.
## Fix Focus Areas
- jablib/src/main/java/org/jabref/logic/openoffice/oocsltext/CSLCitationOOAdapter.java[368-386]
- jablib/src/main/java/org/jabref/logic/openoffice/oocsltext/CSLFormatUtils.java[71-80]

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


Grey Divider

Tip of the day
💡 Did you know, you can type 'qodo, fix this' on a finding and the fix lands right on your PR

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Signed-off-by: subhramit <subhramit.bb@live.in>
Signed-off-by: subhramit <subhramit.bb@live.in>
@github-actions github-actions Bot added the status: changes-required Pull requests that are not yet complete label Aug 12, 2026
Signed-off-by: subhramit <subhramit.bb@live.in>
Signed-off-by: subhramit <subhramit.bb@live.in>
Signed-off-by: subhramit <subhramit.bb@live.in>
@github-actions github-actions Bot added status: no-bot-comments and removed status: changes-required Pull requests that are not yet complete labels Aug 12, 2026
Comment on lines -335 to -343
if (Boolean.TRUE.equals(generatedBibliography.getHangingIndent())) {
indent = 720;
firstLineIndent = -720;
} else if (secondFieldAlign != SecondFieldAlign.FALSE) {
int alignAt = 24 + maxOffset * 120;
firstLineIndent = -alignAt;
if (secondFieldAlign == SecondFieldAlign.MARGIN) {
tabStops = createTabStops(0);
} else {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Like JabRef-only mode, we will use LO's natural Hanging Indent formatting instead of these hardcodings, which appear to work in RTF-based mode of zotero but not here.

@subhramit
subhramit requested a review from Siedlerchr August 13, 2026 00:00
Signed-off-by: subhramit <subhramit.bb@live.in>
@subhramit subhramit changed the title LO: Fix zotero mode bibliography rendering LO: Fix zotero mode bibliography rendering, optimize performance Aug 13, 2026
Signed-off-by: subhramit <subhramit.bb@live.in>
@github-actions github-actions Bot added status: changes-required Pull requests that are not yet complete and removed status: no-bot-comments labels Aug 13, 2026
Signed-off-by: subhramit <subhramit.bb@live.in>
@subhramit subhramit added the dev: code-quality Issues related to code or architecture decisions label Aug 13, 2026
@github-actions github-actions Bot added status: no-bot-comments and removed status: changes-required Pull requests that are not yet complete labels Aug 13, 2026
@subhramit
subhramit requested a review from calixtus August 13, 2026 01:00
@subhramit

subhramit commented Aug 13, 2026

Copy link
Copy Markdown
Member Author

Tested thoroughly for regressions.

JabRef mode:

non-numeric (APA)

image

numeric (IEEE) + hanging indent

image

Zotero compatibility mode:

non-numeric (APA)

image

numeric (ieee) + hanging indent

image

@subhramit subhramit added this to the 6.0-beta milestone Aug 13, 2026
Signed-off-by: subhramit <subhramit.bb@live.in>
@subhramit subhramit added the status: ready-for-review Pull Requests that are ready to be reviewed by the maintainers label Aug 13, 2026
bodyFormats.itemsProperty().bind(viewModel.bodyFormatListProperty());
bodyFormats.valueProperty().bindBidirectional(viewModel.cslBibliographySelectedBodyFormatProperty());

titleLabel.disableProperty().bind(viewModel.shouldDisableHeadingControlsProperty());

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.

A label can also be disabled? Never thought of that

InAnYan
InAnYan previously approved these changes Aug 13, 2026
@subhramit
subhramit added this pull request to the merge queue Aug 13, 2026
@pluto-han

pluto-han commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

@subhramit Did you also try the style that you fixed for citeproc-java? The bibliography is in another format

@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch Aug 13, 2026
@subhramit

Copy link
Copy Markdown
Member Author

@subhramit Did you also try the style that you fixed for citeproc-java? The bibliography is in another format

Zotero mode:
image

JabRef mode:
image

Is there a different expectation/some gap here?

@pluto-han

Copy link
Copy Markdown
Collaborator

@subhramit Did you also try the style that you fixed for citeproc-java? The bibliography is in another format

Zotero mode:

image

JabRef mode:

image

Is there a different expectation/some gap here?

Yes. Create a new doc and insert using zotero

@subhramit

Copy link
Copy Markdown
Member Author

Yes. Create a new doc and insert using zotero

I see
image

But it seems to be a citeproc-js vs citeproc-java issue, I don't see how that relates to this PR.

@subhramit

subhramit commented Aug 13, 2026

Copy link
Copy Markdown
Member Author

But it seems to be a citeproc-js vs citeproc-java issue

Indeed. Logs:

WARN: CSL bibliography generation started. styleTitle='Advanced Functional Materials', styleId='http://www.zotero.org/styles/advanced-functional-materials', stylePath='advanced-functional-materials.csl', entries=[Macht_2007]
2026-08-13 19:31:27 [JavaFX Application Thread] org.jabref.logic.openoffice.oocsltext.CSLCitationOOAdapter.writeBibliographyEntries()
WARN: CSL bibliography pipeline output. citeprocHtml='  <div class="csl-entry">
    <div class="csl-left-margin">[1] </div><div class="csl-right-inline">M. Macht, J. Mueller, <span style="font-style: italic">Appetite</span> 2007, <span style="font-style: italic">49</span>, 667.</div>
  </div>
', transformedHtml='[1]  M. Macht, J. Mueller, <i>Appetite</i> 2007, <i>49</i>, 667.<p></p>', ooText='<span lang="zxx">[1]  M. Macht, J. Mueller, <i>Appetite</i> 2007, <i>49</i>, 667.<p></p></span>'

Meaning, citeproc is not making the year bold.
Can you follow-up on this with Michel?

@subhramit

Copy link
Copy Markdown
Member Author

@InAnYan could you reapprove, had to resolve a merge conflict

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

Labels

component: libre-office dev: code-quality Issues related to code or architecture decisions dev: performance status: no-bot-comments status: ready-for-review Pull Requests that are ready to be reviewed by the maintainers

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants