Skip to content
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv

### Changed

- The LibreOffice integration's bibliography generation for CSL styles is now more performant for a large number of entries. [#16555](https://github.com/JabRef/jabref/pull/16555)
- We now identify Crossref requests with a configured email address, allowing them to use Crossref's polite pool. [#16535](https://github.com/JabRef/jabref/pull/16535)
- We improved user experience by making the welcome tab visible when no libraries are open. [#16451](https://github.com/JabRef/jabref/issues/16451)
- We made it possible to reopen the `Share this library to GitHub` dialog for saved local libraries with an existing Git remote, check a personal access token's push access, and pull from remotes with unrelated histories or no branches. [#16367](https://github.com/JabRef/jabref/pull/16367)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import javafx.fxml.FXML;
import javafx.scene.control.ButtonType;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.stage.Modality;

Expand All @@ -15,7 +16,9 @@

public class ModifyBibliographyPropertiesDialogView extends BaseDialog<Void> {

@FXML private Label titleLabel;
@FXML private TextField titleField;
@FXML private Label headerFormatLabel;
@FXML private ComboBox<String> headerFormats;
@FXML private ComboBox<String> bodyFormats;

Expand Down Expand Up @@ -55,5 +58,10 @@ public void initialize() {
.install(bodyFormats);
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

titleField.disableProperty().bind(viewModel.shouldDisableHeadingControlsProperty());
headerFormatLabel.disableProperty().bind(viewModel.shouldDisableHeadingControlsProperty());
headerFormats.disableProperty().bind(viewModel.shouldDisableHeadingControlsProperty());
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.jabref.gui.openoffice;

import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ReadOnlyListProperty;
import javafx.beans.property.ReadOnlyListWrapper;
import javafx.beans.property.SimpleStringProperty;
Expand All @@ -20,9 +21,11 @@ public class ModifyBibliographyPropertiesDialogViewModel {
new ReadOnlyListWrapper<>(FXCollections.observableArrayList(CSLFormatUtils.BIBLIOGRAPHY_BODY_FORMATS));

private final OpenOfficePreferences openOfficePreferences;
private final BooleanProperty shouldDisableHeadingControls;

public ModifyBibliographyPropertiesDialogViewModel(OpenOfficePreferences openOfficePreferences) {
this.openOfficePreferences = openOfficePreferences;
this.shouldDisableHeadingControls = openOfficePreferences.zoteroCompatibilityModeProperty();
Comment thread
InAnYan marked this conversation as resolved.

this.cslBibliographyTitle.set(openOfficePreferences.getCslBibliographyTitle());
this.cslBibliographySelectedHeaderFormat.set(openOfficePreferences.getCslBibliographyHeaderFormat());
Expand Down Expand Up @@ -54,4 +57,8 @@ public StringProperty cslBibliographySelectedBodyFormatProperty() {
public ReadOnlyListProperty<String> bodyFormatListProperty() {
return bodyFormatListProperty;
}

public BooleanProperty shouldDisableHeadingControlsProperty() {
return shouldDisableHeadingControls;
}
Comment thread
InAnYan marked this conversation as resolved.
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
xmlns="http://javafx.com/javafx/8.0.121" fx:controller="org.jabref.gui.openoffice.ModifyBibliographyPropertiesDialogView">
<content>
<GridPane hgap="10.0" vgap="4.0">
<Label text="%Bibliography title" GridPane.rowIndex="0" GridPane.columnIndex="0" />
<Label fx:id="titleLabel" text="%Bibliography title" GridPane.rowIndex="0" GridPane.columnIndex="0" />
<TextField fx:id="titleField" prefWidth="300.0" GridPane.rowIndex="0" GridPane.columnIndex="1" />
<Label text="%Title format" GridPane.rowIndex="1" GridPane.columnIndex="0" />
<Label fx:id="headerFormatLabel" text="%Title format" GridPane.rowIndex="1" GridPane.columnIndex="0" />
<ComboBox fx:id="headerFormats" prefWidth="300.0" GridPane.rowIndex="1" GridPane.columnIndex="1" />
<Label text="%Body format" GridPane.rowIndex="2" GridPane.columnIndex="0" />
<ComboBox fx:id="bodyFormats" prefWidth="300.0" GridPane.rowIndex="2" GridPane.columnIndex="1" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,23 +32,12 @@

import com.sun.star.beans.XPropertySet;
import com.sun.star.container.NoSuchElementException;
import com.sun.star.container.XNameAccess;
import com.sun.star.container.XNameContainer;
import com.sun.star.lang.WrappedTargetException;
import com.sun.star.lang.XMultiServiceFactory;
import com.sun.star.style.LineSpacing;
import com.sun.star.style.LineSpacingMode;
import com.sun.star.style.TabAlign;
import com.sun.star.style.TabStop;
import com.sun.star.style.XStyle;
import com.sun.star.style.XStyleFamiliesSupplier;
import com.sun.star.text.XTextCursor;
import com.sun.star.text.XTextDocument;
import com.sun.star.uno.Exception;
import com.sun.star.uno.UnoRuntime;
import com.sun.star.uno.XComponentContext;
import de.undercouch.citeproc.output.Bibliography;
import de.undercouch.citeproc.output.SecondFieldAlign;
import org.jspecify.annotations.NonNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -67,15 +56,8 @@ public class CSLCitationOOAdapter {
private static final Logger LOGGER = LoggerFactory.getLogger(CSLCitationOOAdapter.class);

private static final String CITATION_DELIMITER = ", ";
private static final String ZOTERO_BIBLIOGRAPHY_PARAGRAPH_STYLE = "Bibliography 1";
private static final String PARAGRAPH_STYLES = "ParagraphStyles";
private static final CitationStyleOutputFormat HTML_OUTPUT_FORMAT = CitationStyleOutputFormat.HTML;
private static final Pattern CITATION_NUMBER_PATTERN = Pattern.compile("(\\D*)(\\d+)(\\D*)");
private static final Pattern CSL_LEFT_RIGHT_MARGIN_PATTERN = Pattern.compile(
"<div class=\"csl-left-margin\">(.*?)</div><div class=\"csl-right-inline\">(.*?)</div>");
private static final Pattern EMPTY_PARAGRAPHS_AT_BIBLIOGRAPHY_ENTRY_EDGES_PATTERN = Pattern.compile(
"^(?:\\s*<p(?: oo:ParaStyleName=\"[^\"]*\")?>\\s*</p>)+\\s*|(?:\\s*<p(?: oo:ParaStyleName=\"[^\"]*\")?>\\s*</p>)+\\s*$");
private static final double MM_PER_100_TWIP = 25.4 / 1440 * 100;

private final XComponentContext componentContext;
private final XTextDocument document;
Expand Down Expand Up @@ -227,46 +209,14 @@ public void insertJabRefBibliography(XTextCursor cursor, CitationStyle selectedS
return;
}

boolean isNumericStyle = selectedStyle.isNumericStyle();

markManager.setRealTimeNumberUpdateRequired(isNumericStyle);
markManager.readAndUpdateExistingMarks();
updateAllCitationsWithNewStyle(selectedStyle, citationType, selectedDatabases);
markManager.readAndUpdateExistingMarks();
List<BibEntry> bibliographyEntries = prepareBibliographyEntries(selectedStyle, entries, selectedDatabases);

OOText title = OOFormat.paragraph(OOText.fromString(openOfficePreferences.getCslBibliographyTitle()), openOfficePreferences.getCslBibliographyHeaderFormat());
OOTextIntoOO.write(document, cursor, OOText.fromString(title.toString()));
OOText ooBreak = OOFormat.paragraph(OOText.fromString(""), openOfficePreferences.getCslBibliographyBodyFormat());
OOTextIntoOO.write(document, cursor, ooBreak);

BibDatabaseContext currentEntryContext = new BibDatabaseContext(new BibDatabase(entries));
String style = selectedStyle.getSource();

if (isNumericStyle) {
// Sort entries based on their order of appearance in the document
entries.sort(Comparator.comparingInt(entry -> markManager.getCitationNumber(entry.getCitationKey().orElse(""))));

for (BibEntry entry : entries) {
String bibliographyEntry = CitationStyleGenerator.generateBibliography(List.of(entry), style, HTML_OUTPUT_FORMAT, currentEntryContext, bibEntryTypesManager).getFirst();
String citationKey = entry.getCitationKey().orElse("");
int currentNumber = markManager.getCitationNumber(citationKey);
String formattedBibliographyEntry = CSLFormatUtils.transformHTML(bibliographyEntry);
formattedBibliographyEntry = CSLFormatUtils.updateSingleBibliographyNumber(formattedBibliographyEntry, currentNumber);

OOText ooText = OOFormat.setLocaleNone(OOText.fromString(formattedBibliographyEntry));
OOTextIntoOO.write(document, cursor, ooText);
}
} else {
// Ordering will be according to citeproc item data provider (default)
List<String> bibliographyEntries = CitationStyleGenerator.generateBibliography(entries, style, HTML_OUTPUT_FORMAT, currentEntryContext, bibEntryTypesManager);

for (String bibliographyEntry : bibliographyEntries) {
String formattedBibliographyEntry = CSLFormatUtils.transformHTML(bibliographyEntry);

OOText ooText = OOFormat.setLocaleNone(OOText.fromString(formattedBibliographyEntry));
OOTextIntoOO.write(document, cursor, ooText);
}
}
writeBibliographyEntries(cursor, selectedStyle, bibliographyEntries);
}

public void insertZoteroBibliography(XTextCursor cursor, CitationStyle selectedStyle, List<BibEntry> entries, List<BibDatabase> selectedDatabases)
Expand All @@ -275,117 +225,54 @@ public void insertZoteroBibliography(XTextCursor cursor, CitationStyle selectedS
return;
}

List<BibEntry> bibliographyEntries = prepareBibliographyEntries(selectedStyle, entries, selectedDatabases);

XPropertySet cursorProperties = UnoRuntime.queryInterface(XPropertySet.class, cursor);
cursorProperties.setPropertyValue("ParaStyleName", openOfficePreferences.getCslBibliographyBodyFormat());
writeBibliographyEntries(cursor, selectedStyle, bibliographyEntries);
}

private List<BibEntry> prepareBibliographyEntries(CitationStyle selectedStyle, List<BibEntry> entries, List<BibDatabase> selectedDatabases)
throws com.sun.star.uno.Exception, CreationException {
boolean isNumericStyle = selectedStyle.isNumericStyle();

markManager.setRealTimeNumberUpdateRequired(isNumericStyle);
markManager.readAndUpdateExistingMarks();
updateAllCitationsWithNewStyle(selectedStyle, citationType, selectedDatabases);
markManager.readAndUpdateExistingMarks();

BibDatabaseContext currentEntryContext = new BibDatabaseContext(new BibDatabase(entries));
List<BibEntry> bibliographyEntries = new ArrayList<>(entries);
if (isNumericStyle) {
bibliographyEntries.sort(
Comparator.comparingInt(entry -> markManager.getCitationNumber(entry.getCitationKey().orElse(""))));
sortBibliographyEntriesByCitationNumber(bibliographyEntries);
}
return bibliographyEntries;
}

Optional<Bibliography> bibliography = CitationStyleGenerator.generateBibliographyObject(
private void sortBibliographyEntriesByCitationNumber(List<BibEntry> bibliographyEntries) {
bibliographyEntries.sort(Comparator.comparingInt(entry -> markManager.getCitationNumber(entry.getCitationKey().orElse(""))));
}

/// Writes bibliography entries using JabRef's standard CSL bibliography rendering.
///
/// For numeric styles, callers are expected to provide entries already sorted in order of appearance
/// in the document so citeproc-java emits matching bibliography numbers. For non-numeric styles,
/// ordering is left to the citeproc item data provider.
private void writeBibliographyEntries(XTextCursor cursor, CitationStyle selectedStyle, List<BibEntry> bibliographyEntries)
throws com.sun.star.uno.Exception, CreationException {
BibDatabaseContext currentEntryContext = new BibDatabaseContext(new BibDatabase(bibliographyEntries));
List<String> bibliographyEntriesText = CitationStyleGenerator.generateBibliography(
bibliographyEntries,
selectedStyle.getSource(),
HTML_OUTPUT_FORMAT,
currentEntryContext,
bibEntryTypesManager);
if (bibliography.isEmpty()) {
return;
}
Bibliography generatedBibliography = bibliography.orElseThrow();

XStyleFamiliesSupplier styleFamilies = UnoRuntime.queryInterface(XStyleFamiliesSupplier.class, document);
XNameAccess styleFamilyNames = styleFamilies.getStyleFamilies();
XNameAccess paragraphStyles = UnoRuntime.queryInterface(
XNameAccess.class,
styleFamilyNames.getByName(PARAGRAPH_STYLES));
XPropertySet styleProperties;

try {
styleProperties = UnoRuntime.queryInterface(
XPropertySet.class,
paragraphStyles.getByName(ZOTERO_BIBLIOGRAPHY_PARAGRAPH_STYLE));
} catch (NoSuchElementException e) {
LOGGER.debug("Bibliography paragraph style not found. Creating it.", e);
XMultiServiceFactory documentFactory = UnoRuntime.queryInterface(XMultiServiceFactory.class, document);
XStyle style = UnoRuntime.queryInterface(
XStyle.class,
documentFactory.createInstance("com.sun.star.style.ParagraphStyle"));
XNameContainer paragraphStyleNames = UnoRuntime.queryInterface(
XNameContainer.class,
paragraphStyles);
paragraphStyleNames.insertByName(ZOTERO_BIBLIOGRAPHY_PARAGRAPH_STYLE, style);
style.setParentStyle("Default");
styleProperties = UnoRuntime.queryInterface(XPropertySet.class, style);
}

int firstLineIndent = 0;
int indent = 0;
int maxOffset = Optional.ofNullable(generatedBibliography.getMaxOffset()).orElse(0);
TabStop[] tabStops = new TabStop[0];
SecondFieldAlign secondFieldAlign = Optional.ofNullable(generatedBibliography.getSecondFieldAlign())
.orElse(SecondFieldAlign.FALSE);

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 {
Comment on lines -335 to -343

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.

indent = alignAt;
tabStops = createTabStops(alignAt);
}
}

LineSpacing lineSpacing = new LineSpacing();
lineSpacing.Mode = LineSpacingMode.MINIMUM;
lineSpacing.Height = (short) (240
* Optional.ofNullable(generatedBibliography.getLineSpacing()).orElse(1)
* MM_PER_100_TWIP);

styleProperties.setPropertyValue("ParaFirstLineIndent", (int) (firstLineIndent * MM_PER_100_TWIP));
styleProperties.setPropertyValue("ParaLeftMargin", (int) (indent * MM_PER_100_TWIP));
styleProperties.setPropertyValue("ParaLineSpacing", lineSpacing);
styleProperties.setPropertyValue("ParaTopMargin", 0);
styleProperties.setPropertyValue("ParaBottomMargin", 0);
styleProperties.setPropertyValue("ParaTabStops", tabStops);

XPropertySet cursorProperties = UnoRuntime.queryInterface(XPropertySet.class, cursor);
cursorProperties.setPropertyValue("ParaStyleName", ZOTERO_BIBLIOGRAPHY_PARAGRAPH_STYLE);
OOTextIntoOO.write(document, cursor, createZoteroBibliographyText(generatedBibliography));
}

static OOText createZoteroBibliographyText(Bibliography bibliography) {
StringJoiner bibliographyText = new StringJoiner(
"<p oo:ParaStyleName=\"%s\">".formatted(ZOTERO_BIBLIOGRAPHY_PARAGRAPH_STYLE));
for (String bibliographyEntry : bibliography.getEntries()) {
String formattedBibliographyEntry = CSL_LEFT_RIGHT_MARGIN_PATTERN.matcher(bibliographyEntry)
.replaceAll("$1\t$2");
formattedBibliographyEntry = CSLFormatUtils.transformHTML(formattedBibliographyEntry);
formattedBibliographyEntry = EMPTY_PARAGRAPHS_AT_BIBLIOGRAPHY_ENTRY_EDGES_PATTERN.matcher(formattedBibliographyEntry)
.replaceAll("");
bibliographyText.add(formattedBibliographyEntry);
}
return OOFormat.setLocaleNone(OOText.fromString(bibliographyText.toString()));
}
for (String bibliographyEntry : bibliographyEntriesText) {
String formattedBibliographyEntry = CSLFormatUtils.transformHTML(bibliographyEntry);

private static TabStop[] createTabStops(int... positions) {
TabStop[] tabStops = new TabStop[positions.length];
for (int i = 0; i < positions.length; i++) {
tabStops[i] = new TabStop();
tabStops[i].Position = (int) (positions[i] * MM_PER_100_TWIP);
tabStops[i].Alignment = TabAlign.LEFT;
OOText ooText = OOFormat.setLocaleNone(OOText.fromString(formattedBibliographyEntry));
OOTextIntoOO.write(document, cursor, ooText);
}
return tabStops;
}

/// Inserts references and also adds a space before the citation if not already present ("smart space").
Expand Down
Loading
Loading