From a50cc794019378fbb75972959a6e74047157dcb8 Mon Sep 17 00:00:00 2001 From: Antal K Date: Thu, 27 May 2021 23:15:27 +0200 Subject: [PATCH 01/51] step0 : start model/openoffice, logic/openoffice/style --- .../org/jabref/gui/openoffice/CitationEntryViewModel.java | 2 +- .../jabref/gui/openoffice/ManageCitationsDialogViewModel.java | 2 +- src/main/java/org/jabref/gui/openoffice/OOBibBase.java | 4 ++-- src/main/java/org/jabref/gui/openoffice/OpenOfficePanel.java | 4 ++-- .../java/org/jabref/gui/openoffice/StyleSelectDialogView.java | 4 ++-- .../org/jabref/gui/openoffice/StyleSelectDialogViewModel.java | 4 ++-- .../org/jabref/gui/openoffice/StyleSelectItemViewModel.java | 2 +- src/main/java/org/jabref/logic/layout/LayoutEntry.java | 2 +- .../org/jabref/logic/openoffice/UndefinedBibtexEntry.java | 1 + .../org/jabref/logic/openoffice/{ => style}/OOBibStyle.java | 2 +- .../jabref/logic/openoffice/{ => style}/OOPreFormatter.java | 2 +- .../org/jabref/logic/openoffice/{ => style}/StyleLoader.java | 3 ++- .../org/jabref/{logic => model}/openoffice/CitationEntry.java | 2 +- src/main/java/org/jabref/preferences/JabRefPreferences.java | 2 +- .../jabref/logic/openoffice/{ => style}/OOBibStyleTest.java | 2 +- .../logic/openoffice/{ => style}/OOPreFormatterTest.java | 2 +- .../jabref/logic/openoffice/{ => style}/StyleLoaderTest.java | 3 ++- .../jabref/{logic => model}/openoffice/CitationEntryTest.java | 2 +- .../org/jabref/logic/openoffice/{ => style}/test.jstyle | 0 .../openoffice/{ => style}/testWithDefaultAtFirstLIne.jstyle | 0 20 files changed, 24 insertions(+), 21 deletions(-) rename src/main/java/org/jabref/logic/openoffice/{ => style}/OOBibStyle.java (99%) rename src/main/java/org/jabref/logic/openoffice/{ => style}/OOPreFormatter.java (99%) rename src/main/java/org/jabref/logic/openoffice/{ => style}/StyleLoader.java (98%) rename src/main/java/org/jabref/{logic => model}/openoffice/CitationEntry.java (97%) rename src/test/java/org/jabref/logic/openoffice/{ => style}/OOBibStyleTest.java (99%) rename src/test/java/org/jabref/logic/openoffice/{ => style}/OOPreFormatterTest.java (98%) rename src/test/java/org/jabref/logic/openoffice/{ => style}/StyleLoaderTest.java (98%) rename src/test/java/org/jabref/{logic => model}/openoffice/CitationEntryTest.java (98%) rename src/test/resources/org/jabref/logic/openoffice/{ => style}/test.jstyle (100%) rename src/test/resources/org/jabref/logic/openoffice/{ => style}/testWithDefaultAtFirstLIne.jstyle (100%) diff --git a/src/main/java/org/jabref/gui/openoffice/CitationEntryViewModel.java b/src/main/java/org/jabref/gui/openoffice/CitationEntryViewModel.java index 028990a6bc8..c29f24c8424 100644 --- a/src/main/java/org/jabref/gui/openoffice/CitationEntryViewModel.java +++ b/src/main/java/org/jabref/gui/openoffice/CitationEntryViewModel.java @@ -3,7 +3,7 @@ import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; -import org.jabref.logic.openoffice.CitationEntry; +import org.jabref.model.openoffice.CitationEntry; public class CitationEntryViewModel { diff --git a/src/main/java/org/jabref/gui/openoffice/ManageCitationsDialogViewModel.java b/src/main/java/org/jabref/gui/openoffice/ManageCitationsDialogViewModel.java index 55d532caa62..40cee65036f 100644 --- a/src/main/java/org/jabref/gui/openoffice/ManageCitationsDialogViewModel.java +++ b/src/main/java/org/jabref/gui/openoffice/ManageCitationsDialogViewModel.java @@ -10,7 +10,7 @@ import org.jabref.gui.DialogService; import org.jabref.logic.l10n.Localization; -import org.jabref.logic.openoffice.CitationEntry; +import org.jabref.model.openoffice.CitationEntry; import com.sun.star.beans.IllegalTypeException; import com.sun.star.beans.NotRemoveableException; diff --git a/src/main/java/org/jabref/gui/openoffice/OOBibBase.java b/src/main/java/org/jabref/gui/openoffice/OOBibBase.java index 076c2ce2360..26bbde95eb3 100644 --- a/src/main/java/org/jabref/gui/openoffice/OOBibBase.java +++ b/src/main/java/org/jabref/gui/openoffice/OOBibBase.java @@ -28,8 +28,8 @@ import org.jabref.logic.bibtex.comparator.FieldComparatorStack; import org.jabref.logic.l10n.Localization; import org.jabref.logic.layout.Layout; -import org.jabref.logic.openoffice.OOBibStyle; -import org.jabref.logic.openoffice.OOPreFormatter; +import org.jabref.logic.openoffice.style.OOBibStyle; +import org.jabref.logic.openoffice.style.OOPreFormatter; import org.jabref.logic.openoffice.OOUtil; import org.jabref.logic.openoffice.UndefinedBibtexEntry; import org.jabref.logic.openoffice.UndefinedParagraphFormatException; diff --git a/src/main/java/org/jabref/gui/openoffice/OpenOfficePanel.java b/src/main/java/org/jabref/gui/openoffice/OpenOfficePanel.java index 635d9469a31..481eadf867c 100644 --- a/src/main/java/org/jabref/gui/openoffice/OpenOfficePanel.java +++ b/src/main/java/org/jabref/gui/openoffice/OpenOfficePanel.java @@ -44,11 +44,11 @@ import org.jabref.logic.citationkeypattern.CitationKeyPatternPreferences; import org.jabref.logic.help.HelpFile; import org.jabref.logic.l10n.Localization; -import org.jabref.logic.openoffice.OOBibStyle; import org.jabref.logic.openoffice.OpenOfficeFileSearch; import org.jabref.logic.openoffice.OpenOfficePreferences; -import org.jabref.logic.openoffice.StyleLoader; import org.jabref.logic.openoffice.UndefinedParagraphFormatException; +import org.jabref.logic.openoffice.style.OOBibStyle; +import org.jabref.logic.openoffice.style.StyleLoader; import org.jabref.model.database.BibDatabase; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; diff --git a/src/main/java/org/jabref/gui/openoffice/StyleSelectDialogView.java b/src/main/java/org/jabref/gui/openoffice/StyleSelectDialogView.java index 60a5bb84de5..0465dfd0a41 100644 --- a/src/main/java/org/jabref/gui/openoffice/StyleSelectDialogView.java +++ b/src/main/java/org/jabref/gui/openoffice/StyleSelectDialogView.java @@ -20,8 +20,8 @@ import org.jabref.gui.util.ViewModelTableRowFactory; import org.jabref.logic.l10n.Localization; import org.jabref.logic.layout.TextBasedPreviewLayout; -import org.jabref.logic.openoffice.OOBibStyle; -import org.jabref.logic.openoffice.StyleLoader; +import org.jabref.logic.openoffice.style.OOBibStyle; +import org.jabref.logic.openoffice.style.StyleLoader; import org.jabref.logic.util.TestEntry; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.types.StandardEntryType; diff --git a/src/main/java/org/jabref/gui/openoffice/StyleSelectDialogViewModel.java b/src/main/java/org/jabref/gui/openoffice/StyleSelectDialogViewModel.java index 80c7b97c8de..d2ae14549a6 100644 --- a/src/main/java/org/jabref/gui/openoffice/StyleSelectDialogViewModel.java +++ b/src/main/java/org/jabref/gui/openoffice/StyleSelectDialogViewModel.java @@ -22,9 +22,9 @@ import org.jabref.gui.externalfiletype.ExternalFileTypes; import org.jabref.gui.util.FileDialogConfiguration; import org.jabref.logic.l10n.Localization; -import org.jabref.logic.openoffice.OOBibStyle; +import org.jabref.logic.openoffice.style.OOBibStyle; import org.jabref.logic.openoffice.OpenOfficePreferences; -import org.jabref.logic.openoffice.StyleLoader; +import org.jabref.logic.openoffice.style.StyleLoader; import org.jabref.logic.util.StandardFileType; import org.jabref.model.database.BibDatabaseContext; import org.jabref.preferences.PreferencesService; diff --git a/src/main/java/org/jabref/gui/openoffice/StyleSelectItemViewModel.java b/src/main/java/org/jabref/gui/openoffice/StyleSelectItemViewModel.java index 72890122d4f..af7390aeb65 100644 --- a/src/main/java/org/jabref/gui/openoffice/StyleSelectItemViewModel.java +++ b/src/main/java/org/jabref/gui/openoffice/StyleSelectItemViewModel.java @@ -9,7 +9,7 @@ import javafx.scene.Node; import org.jabref.gui.icon.IconTheme; -import org.jabref.logic.openoffice.OOBibStyle; +import org.jabref.logic.openoffice.style.OOBibStyle; public class StyleSelectItemViewModel { diff --git a/src/main/java/org/jabref/logic/layout/LayoutEntry.java b/src/main/java/org/jabref/logic/layout/LayoutEntry.java index cef0e5dcb0a..8b3c98edb8d 100644 --- a/src/main/java/org/jabref/logic/layout/LayoutEntry.java +++ b/src/main/java/org/jabref/logic/layout/LayoutEntry.java @@ -80,7 +80,7 @@ import org.jabref.logic.layout.format.WrapContent; import org.jabref.logic.layout.format.WrapFileLinks; import org.jabref.logic.layout.format.XMLChars; -import org.jabref.logic.openoffice.OOPreFormatter; +import org.jabref.logic.openoffice.style.OOPreFormatter; import org.jabref.model.database.BibDatabase; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; diff --git a/src/main/java/org/jabref/logic/openoffice/UndefinedBibtexEntry.java b/src/main/java/org/jabref/logic/openoffice/UndefinedBibtexEntry.java index 6533d61326e..8a1a7cf5e8f 100644 --- a/src/main/java/org/jabref/logic/openoffice/UndefinedBibtexEntry.java +++ b/src/main/java/org/jabref/logic/openoffice/UndefinedBibtexEntry.java @@ -1,5 +1,6 @@ package org.jabref.logic.openoffice; +import org.jabref.logic.openoffice.style.OOBibStyle; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.field.StandardField; diff --git a/src/main/java/org/jabref/logic/openoffice/OOBibStyle.java b/src/main/java/org/jabref/logic/openoffice/style/OOBibStyle.java similarity index 99% rename from src/main/java/org/jabref/logic/openoffice/OOBibStyle.java rename to src/main/java/org/jabref/logic/openoffice/style/OOBibStyle.java index 63120d0776e..561099afe96 100644 --- a/src/main/java/org/jabref/logic/openoffice/OOBibStyle.java +++ b/src/main/java/org/jabref/logic/openoffice/style/OOBibStyle.java @@ -1,4 +1,4 @@ -package org.jabref.logic.openoffice; +package org.jabref.logic.openoffice.style; import java.io.File; import java.io.FileInputStream; diff --git a/src/main/java/org/jabref/logic/openoffice/OOPreFormatter.java b/src/main/java/org/jabref/logic/openoffice/style/OOPreFormatter.java similarity index 99% rename from src/main/java/org/jabref/logic/openoffice/OOPreFormatter.java rename to src/main/java/org/jabref/logic/openoffice/style/OOPreFormatter.java index 131b8ea374a..dc02daf6ee9 100644 --- a/src/main/java/org/jabref/logic/openoffice/OOPreFormatter.java +++ b/src/main/java/org/jabref/logic/openoffice/style/OOPreFormatter.java @@ -1,4 +1,4 @@ -package org.jabref.logic.openoffice; +package org.jabref.logic.openoffice.style; import java.util.Map; import java.util.Objects; diff --git a/src/main/java/org/jabref/logic/openoffice/StyleLoader.java b/src/main/java/org/jabref/logic/openoffice/style/StyleLoader.java similarity index 98% rename from src/main/java/org/jabref/logic/openoffice/StyleLoader.java rename to src/main/java/org/jabref/logic/openoffice/style/StyleLoader.java index 4d3b30537db..6d7651a24de 100644 --- a/src/main/java/org/jabref/logic/openoffice/StyleLoader.java +++ b/src/main/java/org/jabref/logic/openoffice/style/StyleLoader.java @@ -1,4 +1,4 @@ -package org.jabref.logic.openoffice; +package org.jabref.logic.openoffice.style; import java.io.File; import java.io.FileNotFoundException; @@ -10,6 +10,7 @@ import java.util.Objects; import org.jabref.logic.layout.LayoutFormatterPreferences; +import org.jabref.logic.openoffice.OpenOfficePreferences; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/src/main/java/org/jabref/logic/openoffice/CitationEntry.java b/src/main/java/org/jabref/model/openoffice/CitationEntry.java similarity index 97% rename from src/main/java/org/jabref/logic/openoffice/CitationEntry.java rename to src/main/java/org/jabref/model/openoffice/CitationEntry.java index 33f3c34662a..219e71eb0d1 100644 --- a/src/main/java/org/jabref/logic/openoffice/CitationEntry.java +++ b/src/main/java/org/jabref/model/openoffice/CitationEntry.java @@ -1,4 +1,4 @@ -package org.jabref.logic.openoffice; +package org.jabref.model.openoffice; import java.util.Objects; import java.util.Optional; diff --git a/src/main/java/org/jabref/preferences/JabRefPreferences.java b/src/main/java/org/jabref/preferences/JabRefPreferences.java index 11bf4fa31b3..ba5df0fa216 100644 --- a/src/main/java/org/jabref/preferences/JabRefPreferences.java +++ b/src/main/java/org/jabref/preferences/JabRefPreferences.java @@ -87,7 +87,7 @@ import org.jabref.logic.layout.format.NameFormatterPreferences; import org.jabref.logic.net.ProxyPreferences; import org.jabref.logic.openoffice.OpenOfficePreferences; -import org.jabref.logic.openoffice.StyleLoader; +import org.jabref.logic.openoffice.style.StyleLoader; import org.jabref.logic.preferences.DOIPreferences; import org.jabref.logic.preferences.OwnerPreferences; import org.jabref.logic.preferences.TimestampPreferences; diff --git a/src/test/java/org/jabref/logic/openoffice/OOBibStyleTest.java b/src/test/java/org/jabref/logic/openoffice/style/OOBibStyleTest.java similarity index 99% rename from src/test/java/org/jabref/logic/openoffice/OOBibStyleTest.java rename to src/test/java/org/jabref/logic/openoffice/style/OOBibStyleTest.java index 093a5eb6348..786e678324c 100644 --- a/src/test/java/org/jabref/logic/openoffice/OOBibStyleTest.java +++ b/src/test/java/org/jabref/logic/openoffice/style/OOBibStyleTest.java @@ -1,4 +1,4 @@ -package org.jabref.logic.openoffice; +package org.jabref.logic.openoffice.style; import java.io.File; import java.io.IOException; diff --git a/src/test/java/org/jabref/logic/openoffice/OOPreFormatterTest.java b/src/test/java/org/jabref/logic/openoffice/style/OOPreFormatterTest.java similarity index 98% rename from src/test/java/org/jabref/logic/openoffice/OOPreFormatterTest.java rename to src/test/java/org/jabref/logic/openoffice/style/OOPreFormatterTest.java index 8b76fbeacd0..0478089a8dc 100644 --- a/src/test/java/org/jabref/logic/openoffice/OOPreFormatterTest.java +++ b/src/test/java/org/jabref/logic/openoffice/style/OOPreFormatterTest.java @@ -1,4 +1,4 @@ -package org.jabref.logic.openoffice; +package org.jabref.logic.openoffice.style; import org.junit.jupiter.api.Test; diff --git a/src/test/java/org/jabref/logic/openoffice/StyleLoaderTest.java b/src/test/java/org/jabref/logic/openoffice/style/StyleLoaderTest.java similarity index 98% rename from src/test/java/org/jabref/logic/openoffice/StyleLoaderTest.java rename to src/test/java/org/jabref/logic/openoffice/style/StyleLoaderTest.java index be5e3768a01..9b5a58f4de5 100644 --- a/src/test/java/org/jabref/logic/openoffice/StyleLoaderTest.java +++ b/src/test/java/org/jabref/logic/openoffice/style/StyleLoaderTest.java @@ -1,4 +1,4 @@ -package org.jabref.logic.openoffice; +package org.jabref.logic.openoffice.style; import java.net.URISyntaxException; import java.nio.charset.Charset; @@ -9,6 +9,7 @@ import java.util.List; import org.jabref.logic.layout.LayoutFormatterPreferences; +import org.jabref.logic.openoffice.OpenOfficePreferences; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; diff --git a/src/test/java/org/jabref/logic/openoffice/CitationEntryTest.java b/src/test/java/org/jabref/model/openoffice/CitationEntryTest.java similarity index 98% rename from src/test/java/org/jabref/logic/openoffice/CitationEntryTest.java rename to src/test/java/org/jabref/model/openoffice/CitationEntryTest.java index b1cd1d100a7..fd7ec48b9b3 100644 --- a/src/test/java/org/jabref/logic/openoffice/CitationEntryTest.java +++ b/src/test/java/org/jabref/model/openoffice/CitationEntryTest.java @@ -1,4 +1,4 @@ -package org.jabref.logic.openoffice; +package org.jabref.model.openoffice; import java.util.Optional; diff --git a/src/test/resources/org/jabref/logic/openoffice/test.jstyle b/src/test/resources/org/jabref/logic/openoffice/style/test.jstyle similarity index 100% rename from src/test/resources/org/jabref/logic/openoffice/test.jstyle rename to src/test/resources/org/jabref/logic/openoffice/style/test.jstyle diff --git a/src/test/resources/org/jabref/logic/openoffice/testWithDefaultAtFirstLIne.jstyle b/src/test/resources/org/jabref/logic/openoffice/style/testWithDefaultAtFirstLIne.jstyle similarity index 100% rename from src/test/resources/org/jabref/logic/openoffice/testWithDefaultAtFirstLIne.jstyle rename to src/test/resources/org/jabref/logic/openoffice/style/testWithDefaultAtFirstLIne.jstyle From d93bd56ef99c19da6fac760baeeeb6229046e110 Mon Sep 17 00:00:00 2001 From: Antal K Date: Fri, 28 May 2021 00:12:17 +0200 Subject: [PATCH 02/51] correction: import order --- src/main/java/org/jabref/gui/openoffice/OOBibBase.java | 4 ++-- .../org/jabref/gui/openoffice/StyleSelectDialogViewModel.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/jabref/gui/openoffice/OOBibBase.java b/src/main/java/org/jabref/gui/openoffice/OOBibBase.java index 26bbde95eb3..6a464507c09 100644 --- a/src/main/java/org/jabref/gui/openoffice/OOBibBase.java +++ b/src/main/java/org/jabref/gui/openoffice/OOBibBase.java @@ -28,11 +28,11 @@ import org.jabref.logic.bibtex.comparator.FieldComparatorStack; import org.jabref.logic.l10n.Localization; import org.jabref.logic.layout.Layout; -import org.jabref.logic.openoffice.style.OOBibStyle; -import org.jabref.logic.openoffice.style.OOPreFormatter; import org.jabref.logic.openoffice.OOUtil; import org.jabref.logic.openoffice.UndefinedBibtexEntry; import org.jabref.logic.openoffice.UndefinedParagraphFormatException; +import org.jabref.logic.openoffice.style.OOBibStyle; +import org.jabref.logic.openoffice.style.OOPreFormatter; import org.jabref.model.database.BibDatabase; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.field.StandardField; diff --git a/src/main/java/org/jabref/gui/openoffice/StyleSelectDialogViewModel.java b/src/main/java/org/jabref/gui/openoffice/StyleSelectDialogViewModel.java index d2ae14549a6..abb37e87bd7 100644 --- a/src/main/java/org/jabref/gui/openoffice/StyleSelectDialogViewModel.java +++ b/src/main/java/org/jabref/gui/openoffice/StyleSelectDialogViewModel.java @@ -22,8 +22,8 @@ import org.jabref.gui.externalfiletype.ExternalFileTypes; import org.jabref.gui.util.FileDialogConfiguration; import org.jabref.logic.l10n.Localization; -import org.jabref.logic.openoffice.style.OOBibStyle; import org.jabref.logic.openoffice.OpenOfficePreferences; +import org.jabref.logic.openoffice.style.OOBibStyle; import org.jabref.logic.openoffice.style.StyleLoader; import org.jabref.logic.util.StandardFileType; import org.jabref.model.database.BibDatabaseContext; From c9b72e998a8edd19e10f1060fa28d10beee4e78c Mon Sep 17 00:00:00 2001 From: Antal K Date: Fri, 28 May 2021 00:20:40 +0200 Subject: [PATCH 03/51] add general utilities --- .../model/openoffice/util/OOListUtil.java | 31 +++++ .../jabref/model/openoffice/util/OOPair.java | 11 ++ .../model/openoffice/util/OOResult.java | 128 ++++++++++++++++++ .../model/openoffice/util/OOVoidResult.java | 52 +++++++ 4 files changed, 222 insertions(+) create mode 100644 src/main/java/org/jabref/model/openoffice/util/OOListUtil.java create mode 100644 src/main/java/org/jabref/model/openoffice/util/OOPair.java create mode 100644 src/main/java/org/jabref/model/openoffice/util/OOResult.java create mode 100644 src/main/java/org/jabref/model/openoffice/util/OOVoidResult.java diff --git a/src/main/java/org/jabref/model/openoffice/util/OOListUtil.java b/src/main/java/org/jabref/model/openoffice/util/OOListUtil.java new file mode 100644 index 00000000000..1fe617ed99a --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/util/OOListUtil.java @@ -0,0 +1,31 @@ +package org.jabref.model.openoffice.util; + +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class OOListUtil { + + public static List map(List list, Function fun) { + return list.stream().map(e -> fun.apply(e)).collect(Collectors.toList()); + } + + /** Integers 0..(n-1) */ + public static List makeIndices(int n) { + return Stream.iterate(0, i -> i + 1).limit(n).collect(Collectors.toList()); + } + + /** Return indices so that list.get(indices.get(i)) is sorted. */ + public static List order(List list, Comparator comparator) { + List ii = makeIndices(list.size()); + Collections.sort(ii, new Comparator() { + @Override public int compare(final Integer o1, final Integer o2) { + return comparator.compare((U) list.get(o1), (U) list.get(o2)); + } + }); + return ii; + } +} diff --git a/src/main/java/org/jabref/model/openoffice/util/OOPair.java b/src/main/java/org/jabref/model/openoffice/util/OOPair.java new file mode 100644 index 00000000000..9d1596d19ef --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/util/OOPair.java @@ -0,0 +1,11 @@ +package org.jabref.model.openoffice.util; + +public class OOPair { + public final A a; + public final B b; + public OOPair(A a, B b) { + this.a = a; + this.b = b; + } +} + diff --git a/src/main/java/org/jabref/model/openoffice/util/OOResult.java b/src/main/java/org/jabref/model/openoffice/util/OOResult.java new file mode 100644 index 00000000000..1bc8cddcc13 --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/util/OOResult.java @@ -0,0 +1,128 @@ +package org.jabref.model.openoffice.util; + +import java.util.Optional; +import java.util.function.Consumer; +import java.util.function.Function; + +/* + * error cannot be null + * result cannot be null + * + * Void is not allowed for R, use OOVoidResult instead. + * + * Out of `isPresent()` and `isError()` exactly one is true. + */ +public class OOResult { + private final Optional result; + private final Optional error; + + /** + * Exactly one of the arguments should be Optional.empty() + * + * @param result + * @param error + */ + private OOResult(Optional result, Optional error) { + this.result = result; + this.error = error; + } + + /** + * @param result Null is not allowed. + */ + public static OOResult ok(R result) { + return new OOResult(Optional.of(result), Optional.empty()); + } + + /** + * @param error Null is not allowed. + */ + public static OOResult error(E error) { + return new OOResult(Optional.empty(), Optional.of(error)); + } + + /* + * Test state + */ + + public boolean isPresent() { + return result.isPresent(); + } + + public boolean isEmpty() { + return !isPresent(); + } + + public boolean isError() { + return error.isPresent(); + } + + public boolean isOK() { + return !isError(); + } + + /* + * getters + */ + + public R get() { + if (isError()) { + throw new RuntimeException("Cannot get from error"); + } + return result.get(); + } + + public E getError() { + return error.get(); + } + + /* + * Conditionals + */ + + public OOResult ifPresent(Consumer fun) { + if (isPresent()) { + fun.accept(get()); + } + return this; + } + + public OOResult ifError(Consumer fun) { + if (isError()) { + fun.accept(getError()); + } + return this; + } + + public OOResult map(Function fun) { + if (isError()) { + return error(getError()); + } else { + return ok(fun.apply(get())); + } + } + + public OOResult mapError(Function fun) { + if (isError()) { + return error(fun.apply(getError())); + } else { + return ok(get()); + } + } + + /** Throw away the error part. */ + public Optional getOptional() { + return result; + } + + /** Throw away the result part. */ + public OOVoidResult asVoidResult() { + if (isError()) { + return OOVoidResult.error(getError()); + } else { + return OOVoidResult.ok(); + } + } + +} + diff --git a/src/main/java/org/jabref/model/openoffice/util/OOVoidResult.java b/src/main/java/org/jabref/model/openoffice/util/OOVoidResult.java new file mode 100644 index 00000000000..a0bb0ebebe2 --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/util/OOVoidResult.java @@ -0,0 +1,52 @@ +package org.jabref.model.openoffice.util; + +import java.util.Optional; +import java.util.function.Consumer; +import java.util.function.Function; + +/* + * error cannot be null + */ +public class OOVoidResult { + private final Optional error; + + private OOVoidResult(Optional error) { + this.error = error; + } + + public static OOVoidResult ok() { + return new OOVoidResult(Optional.empty()); + } + + public static OOVoidResult error(E error) { + return new OOVoidResult(Optional.of(error)); + } + + public boolean isError() { + return error.isPresent(); + } + + public boolean isOK() { + return !isError(); + } + + public E getError() { + return error.get(); + } + + public OOVoidResult ifError(Consumer fun) { + if (isError()) { + fun.accept(getError()); + } + return this; + } + + public OOVoidResult mapError(Function fun) { + if (isError()) { + return error(fun.apply(getError())); + } else { + return ok(); + } + } +} + From 381a5493c8ed730bdf021a50a63b37b52b530f72 Mon Sep 17 00:00:00 2001 From: Antal K Date: Fri, 28 May 2021 00:42:03 +0200 Subject: [PATCH 04/51] add UNO utilities, move CreationException, NoDocumentException --- .../gui/openoffice/CreationException.java | 13 -- .../gui/openoffice/NoDocumentException.java | 13 -- .../org/jabref/gui/openoffice/OOBibBase.java | 2 + .../gui/openoffice/OpenOfficePanel.java | 2 + .../openoffice/uno/CreationException.java | 15 ++ .../openoffice/uno/NoDocumentException.java | 12 ++ .../model/openoffice/uno/UnoBookmark.java | 98 ++++++++++ .../jabref/model/openoffice/uno/UnoCast.java | 24 +++ .../model/openoffice/uno/UnoCrossRef.java | 75 ++++++++ .../model/openoffice/uno/UnoCursor.java | 45 +++++ .../model/openoffice/uno/UnoNameAccess.java | 27 +++ .../jabref/model/openoffice/uno/UnoNamed.java | 68 +++++++ .../model/openoffice/uno/UnoProperties.java | 95 ++++++++++ .../model/openoffice/uno/UnoRedlines.java | 54 ++++++ .../openoffice/uno/UnoReferenceMark.java | 133 ++++++++++++++ .../openoffice/uno/UnoScreenRefresh.java | 33 ++++ .../model/openoffice/uno/UnoSelection.java | 115 ++++++++++++ .../jabref/model/openoffice/uno/UnoStyle.java | 79 ++++++++ .../model/openoffice/uno/UnoTextDocument.java | 92 ++++++++++ .../model/openoffice/uno/UnoTextRange.java | 76 ++++++++ .../model/openoffice/uno/UnoTextSection.java | 90 +++++++++ .../jabref/model/openoffice/uno/UnoUndo.java | 45 +++++ .../uno/UnoUserDefinedProperty.java | 173 ++++++++++++++++++ 23 files changed, 1353 insertions(+), 26 deletions(-) delete mode 100644 src/main/java/org/jabref/gui/openoffice/CreationException.java delete mode 100644 src/main/java/org/jabref/gui/openoffice/NoDocumentException.java create mode 100644 src/main/java/org/jabref/model/openoffice/uno/CreationException.java create mode 100644 src/main/java/org/jabref/model/openoffice/uno/NoDocumentException.java create mode 100644 src/main/java/org/jabref/model/openoffice/uno/UnoBookmark.java create mode 100644 src/main/java/org/jabref/model/openoffice/uno/UnoCast.java create mode 100644 src/main/java/org/jabref/model/openoffice/uno/UnoCrossRef.java create mode 100644 src/main/java/org/jabref/model/openoffice/uno/UnoCursor.java create mode 100644 src/main/java/org/jabref/model/openoffice/uno/UnoNameAccess.java create mode 100644 src/main/java/org/jabref/model/openoffice/uno/UnoNamed.java create mode 100644 src/main/java/org/jabref/model/openoffice/uno/UnoProperties.java create mode 100644 src/main/java/org/jabref/model/openoffice/uno/UnoRedlines.java create mode 100644 src/main/java/org/jabref/model/openoffice/uno/UnoReferenceMark.java create mode 100644 src/main/java/org/jabref/model/openoffice/uno/UnoScreenRefresh.java create mode 100644 src/main/java/org/jabref/model/openoffice/uno/UnoSelection.java create mode 100644 src/main/java/org/jabref/model/openoffice/uno/UnoStyle.java create mode 100644 src/main/java/org/jabref/model/openoffice/uno/UnoTextDocument.java create mode 100644 src/main/java/org/jabref/model/openoffice/uno/UnoTextRange.java create mode 100644 src/main/java/org/jabref/model/openoffice/uno/UnoTextSection.java create mode 100644 src/main/java/org/jabref/model/openoffice/uno/UnoUndo.java create mode 100644 src/main/java/org/jabref/model/openoffice/uno/UnoUserDefinedProperty.java diff --git a/src/main/java/org/jabref/gui/openoffice/CreationException.java b/src/main/java/org/jabref/gui/openoffice/CreationException.java deleted file mode 100644 index 62c1fb0dafa..00000000000 --- a/src/main/java/org/jabref/gui/openoffice/CreationException.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.jabref.gui.openoffice; - -/** - * Exception used to indicate that the plugin attempted to set a character format that is - * not defined in the current OpenOffice document. - */ -class CreationException extends Exception { - - public CreationException(String message) { - super(message); - } - -} diff --git a/src/main/java/org/jabref/gui/openoffice/NoDocumentException.java b/src/main/java/org/jabref/gui/openoffice/NoDocumentException.java deleted file mode 100644 index 05d5f6d06ac..00000000000 --- a/src/main/java/org/jabref/gui/openoffice/NoDocumentException.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.jabref.gui.openoffice; - -/** - * Exception used to indicate that the plugin attempted to set a character format that is - * not defined in the current OpenOffice document. - */ -class NoDocumentException extends Exception { - - public NoDocumentException(String message) { - super(message); - } - -} diff --git a/src/main/java/org/jabref/gui/openoffice/OOBibBase.java b/src/main/java/org/jabref/gui/openoffice/OOBibBase.java index 6a464507c09..540c5f9216b 100644 --- a/src/main/java/org/jabref/gui/openoffice/OOBibBase.java +++ b/src/main/java/org/jabref/gui/openoffice/OOBibBase.java @@ -36,6 +36,8 @@ import org.jabref.model.database.BibDatabase; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.field.StandardField; +import org.jabref.model.openoffice.uno.CreationException; +import org.jabref.model.openoffice.uno.NoDocumentException; import com.sun.star.awt.Point; import com.sun.star.beans.IllegalTypeException; diff --git a/src/main/java/org/jabref/gui/openoffice/OpenOfficePanel.java b/src/main/java/org/jabref/gui/openoffice/OpenOfficePanel.java index 481eadf867c..367855a4b37 100644 --- a/src/main/java/org/jabref/gui/openoffice/OpenOfficePanel.java +++ b/src/main/java/org/jabref/gui/openoffice/OpenOfficePanel.java @@ -52,6 +52,8 @@ import org.jabref.model.database.BibDatabase; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; +import org.jabref.model.openoffice.uno.CreationException; +import org.jabref.model.openoffice.uno.NoDocumentException; import org.jabref.preferences.PreferencesService; import com.sun.star.beans.IllegalTypeException; diff --git a/src/main/java/org/jabref/model/openoffice/uno/CreationException.java b/src/main/java/org/jabref/model/openoffice/uno/CreationException.java new file mode 100644 index 00000000000..091d5469e2d --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/uno/CreationException.java @@ -0,0 +1,15 @@ +package org.jabref.model.openoffice.uno; + +/** + * Exception used to indicate failure in either + * + * XMultiServiceFactory.createInstance() + * XMultiComponentFactory.createInstanceWithContext() + */ +public class CreationException extends Exception { + + public CreationException(String message) { + super(message); + } + +} diff --git a/src/main/java/org/jabref/model/openoffice/uno/NoDocumentException.java b/src/main/java/org/jabref/model/openoffice/uno/NoDocumentException.java new file mode 100644 index 00000000000..20783e3a580 --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/uno/NoDocumentException.java @@ -0,0 +1,12 @@ +package org.jabref.model.openoffice.uno; + +public class NoDocumentException extends Exception { + + public NoDocumentException(String message) { + super(message); + } + + public NoDocumentException() { + super("Not connected to a document"); + } +} diff --git a/src/main/java/org/jabref/model/openoffice/uno/UnoBookmark.java b/src/main/java/org/jabref/model/openoffice/uno/UnoBookmark.java new file mode 100644 index 00000000000..b97618a3231 --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/uno/UnoBookmark.java @@ -0,0 +1,98 @@ +package org.jabref.model.openoffice.uno; + +import java.util.Optional; + +import com.sun.star.container.NoSuchElementException; +import com.sun.star.container.XNameAccess; +import com.sun.star.container.XNamed; +import com.sun.star.lang.DisposedException; +import com.sun.star.lang.IllegalArgumentException; +import com.sun.star.lang.WrappedTargetException; +import com.sun.star.text.XBookmarksSupplier; +import com.sun.star.text.XTextContent; +import com.sun.star.text.XTextDocument; +import com.sun.star.text.XTextRange; + +public class UnoBookmark { + + private UnoBookmark() { } + + /** + * Provides access to bookmarks by name. + */ + public static XNameAccess getNameAccess(XTextDocument doc) + throws + NoDocumentException { + + XBookmarksSupplier supplier = UnoCast.unoQI(XBookmarksSupplier.class, doc); + try { + return supplier.getBookmarks(); + } catch (DisposedException ex) { + throw new NoDocumentException("UnoBookmark.getNameAccess failed with" + ex); + } + } + + /** + * Get the XTextRange corresponding to the named bookmark. + * + * @param name The name of the bookmark to find. + * @return The XTextRange for the bookmark, or Optional.empty(). + */ + public static Optional getAnchor(XTextDocument doc, String name) + throws + WrappedTargetException, + NoDocumentException { + + XNameAccess nameAccess = getNameAccess(doc); + return (UnoNameAccess.getTextContentByName(nameAccess, name) + .map(e -> e.getAnchor())); + } + + /** + * Insert a bookmark with the given name at the cursor provided, + * or with another name if the one we asked for is already in use. + * + * In LibreOffice the another name is in "{name}{number}" format. + * + * @param name For the bookmark. + * @param range Cursor marking the location or range for + * the bookmark. + * @param absorb Shall we incorporate range? + * + * @return The XNamed interface of the bookmark. + * + * result.getName() should be checked by the + * caller, because its name may differ from the one + * requested. + */ + public static XNamed create(XTextDocument doc, String name, XTextRange range, boolean absorb) + throws + IllegalArgumentException, + CreationException { + return UnoNamed.insertNamedTextContent(doc, + "com.sun.star.text.Bookmark", + name, + range, + absorb); + } + + /** + * Remove the named bookmark if it exists. + */ + public static void remove(XTextDocument doc, String name) + throws + NoDocumentException, + NoSuchElementException, + WrappedTargetException { + + XNameAccess marks = UnoBookmark.getNameAccess(doc); + + if (marks.hasByName(name)) { + Optional mark = UnoNameAccess.getTextContentByName(marks, name); + if (mark.isEmpty()) { + return; + } + doc.getText().removeTextContent(mark.get()); + } + } +} diff --git a/src/main/java/org/jabref/model/openoffice/uno/UnoCast.java b/src/main/java/org/jabref/model/openoffice/uno/UnoCast.java new file mode 100644 index 00000000000..df5be51db36 --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/uno/UnoCast.java @@ -0,0 +1,24 @@ +package org.jabref.model.openoffice.uno; + +import java.util.Optional; + +import com.sun.star.uno.UnoRuntime; + +public class UnoCast { + + private UnoCast() { } + + /** + * unoQI : short for UnoRuntime.queryInterface + * + * @return A reference to the requested UNO interface type if available, + * otherwise null + */ + public static T unoQI(Class zInterface, Object object) { + return UnoRuntime.queryInterface(zInterface, object); + } + + public static Optional optUnoQI(Class zInterface, Object object) { + return Optional.ofNullable(UnoRuntime.queryInterface(zInterface, object)); + } +} diff --git a/src/main/java/org/jabref/model/openoffice/uno/UnoCrossRef.java b/src/main/java/org/jabref/model/openoffice/uno/UnoCrossRef.java new file mode 100644 index 00000000000..0e8b84e8d22 --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/uno/UnoCrossRef.java @@ -0,0 +1,75 @@ +package org.jabref.model.openoffice.uno; + +import com.sun.star.beans.PropertyVetoException; +import com.sun.star.beans.UnknownPropertyException; +import com.sun.star.beans.XPropertySet; +import com.sun.star.lang.WrappedTargetException; +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.text.ReferenceFieldPart; +import com.sun.star.text.ReferenceFieldSource; +import com.sun.star.text.XTextContent; +import com.sun.star.text.XTextDocument; +import com.sun.star.text.XTextRange; +import com.sun.star.util.XRefreshable; + +public class UnoCrossRef { + + private UnoCrossRef() { } + + /** + * Update TextFields, etc. We use it to refresh cross-references in the document. + */ + public static void refresh(XTextDocument doc) { + // Refresh the document + XRefreshable xRefresh = UnoCast.unoQI(XRefreshable.class, doc); + xRefresh.refresh(); + } + + /** + * Insert a clickable cross-reference to a reference mark, + * with a label containing the target's page number. + * + * May need a documentConnection.refresh() after, to update + * the text shown. + */ + public static void insertReferenceToPageNumberOfReferenceMark(XTextDocument doc, + String referenceMarkName, + XTextRange cursor) + throws + CreationException, + UnknownPropertyException, + PropertyVetoException, + WrappedTargetException { + + // based on: https://wiki.openoffice.org/wiki/Documentation/DevGuide/Text/Reference_Marks + XMultiServiceFactory msf = UnoCast.unoQI(XMultiServiceFactory.class, doc); + // Create a 'GetReference' text field to refer to the reference mark we just inserted, + // and get it's XPropertySet interface + XPropertySet xFieldProps; + try { + String name = "com.sun.star.text.textfield.GetReference"; + xFieldProps = (XPropertySet) UnoCast.unoQI(XPropertySet.class, + msf.createInstance(name)); + } catch (Exception e) { + throw new CreationException(e.getMessage()); + } + + // Set the SourceName of the GetReference text field to the referenceMarkName + xFieldProps.setPropertyValue("SourceName", referenceMarkName); + + // specify that the source is a reference mark (could also be a footnote, + // bookmark or sequence field) + xFieldProps.setPropertyValue("ReferenceFieldSource", + new Short(ReferenceFieldSource.REFERENCE_MARK)); + + // We want the reference displayed as page number + xFieldProps.setPropertyValue("ReferenceFieldPart", + new Short(ReferenceFieldPart.PAGE)); + + // Get the XTextContent interface of the GetReference text field + XTextContent xRefContent = (XTextContent) UnoCast.unoQI(XTextContent.class, xFieldProps); + + // Insert the text field + cursor.getText().insertTextContent(cursor.getEnd(), xRefContent, false); + } +} diff --git a/src/main/java/org/jabref/model/openoffice/uno/UnoCursor.java b/src/main/java/org/jabref/model/openoffice/uno/UnoCursor.java new file mode 100644 index 00000000000..b7115feb8e1 --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/uno/UnoCursor.java @@ -0,0 +1,45 @@ +package org.jabref.model.openoffice.uno; + +import java.util.Optional; + +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.XTextViewCursor; +import com.sun.star.text.XTextViewCursorSupplier; + +public class UnoCursor { + + private UnoCursor() { } + + /** + * Get the cursor positioned by the user. + */ + public static Optional getViewCursor(XTextDocument doc) { + return (UnoTextDocument.getCurrentController(doc) + .flatMap(e -> UnoCast.optUnoQI(XTextViewCursorSupplier.class, e)) + .map(e -> e.getViewCursor())); + } + + /** + * Create a text cursor for a textContent. + * + * @return Optional.empty if mark is null, otherwise cursor. + * + */ + public static Optional getTextCursorOfTextContentAnchor(XTextContent mark) { + if (mark == null) { + return Optional.empty(); + } + XTextRange markAnchor = mark.getAnchor(); + if (markAnchor == null) { + return Optional.empty(); + } + return Optional.of(markAnchor.getText().createTextCursorByRange(markAnchor)); + } + + public static XTextCursor createTextCursorByRange(XTextRange r) { + return r.getText().createTextCursorByRange(r); + } +} diff --git a/src/main/java/org/jabref/model/openoffice/uno/UnoNameAccess.java b/src/main/java/org/jabref/model/openoffice/uno/UnoNameAccess.java new file mode 100644 index 00000000000..3d172d11f14 --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/uno/UnoNameAccess.java @@ -0,0 +1,27 @@ +package org.jabref.model.openoffice.uno; + +import java.util.Optional; + +import com.sun.star.container.NoSuchElementException; +import com.sun.star.container.XNameAccess; +import com.sun.star.lang.WrappedTargetException; +import com.sun.star.text.XTextContent; + +public class UnoNameAccess { + + private UnoNameAccess() { } + + /** + * @return null if name not found, or if the result does not + * support the XTextContent interface. + */ + public static Optional getTextContentByName(XNameAccess nameAccess, String name) + throws + WrappedTargetException { + try { + return UnoCast.optUnoQI(XTextContent.class, nameAccess.getByName(name)); + } catch (NoSuchElementException ex) { + return Optional.empty(); + } + } +} diff --git a/src/main/java/org/jabref/model/openoffice/uno/UnoNamed.java b/src/main/java/org/jabref/model/openoffice/uno/UnoNamed.java new file mode 100644 index 00000000000..d99b6702153 --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/uno/UnoNamed.java @@ -0,0 +1,68 @@ +package org.jabref.model.openoffice.uno; + +import com.sun.star.container.XNamed; +import com.sun.star.lang.XMultiServiceFactory; +import com.sun.star.text.XTextContent; +import com.sun.star.text.XTextDocument; +import com.sun.star.text.XTextRange; + +public class UnoNamed { + + private UnoNamed() { } + + /** + * Insert a new instance of a service at the provided cursor + * position. + * + * @param service For example + * "com.sun.star.text.ReferenceMark", + * "com.sun.star.text.Bookmark" or + * "com.sun.star.text.TextSection". + * + * Passed to this.asXMultiServiceFactory().createInstance(service) + * The result is expected to support the + * XNamed and XTextContent interfaces. + * + * @param name For the ReferenceMark, Bookmark, TextSection. + * If the name is already in use, LibreOffice + * may change the name. + * + * @param range Marks the location or range for + * the thing to be inserted. + * + * @param absorb ReferenceMark, Bookmark and TextSection can + * incorporate a text range. If absorb is true, + * the text in the range becomes part of the thing. + * If absorb is false, the thing is + * inserted at the end of the range. + * + * @return The XNamed interface, in case we need to check the actual name. + * + */ + static XNamed insertNamedTextContent(XTextDocument doc, + String service, + String name, + XTextRange range, + boolean absorb) + throws + CreationException { + + XMultiServiceFactory msf = UnoCast.unoQI(XMultiServiceFactory.class, doc); + + Object xObject; + try { + xObject = msf.createInstance(service); + } catch (Exception e) { + throw new CreationException(e.getMessage()); + } + + XNamed xNamed = UnoCast.unoQI(XNamed.class, xObject); + xNamed.setName(name); + + // get XTextContent interface + XTextContent xTextContent = UnoCast.unoQI(XTextContent.class, xObject); + range.getText().insertTextContent(range, xTextContent, absorb); + return xNamed; + } + +} diff --git a/src/main/java/org/jabref/model/openoffice/uno/UnoProperties.java b/src/main/java/org/jabref/model/openoffice/uno/UnoProperties.java new file mode 100644 index 00000000000..07a6de1374d --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/uno/UnoProperties.java @@ -0,0 +1,95 @@ +package org.jabref.model.openoffice.uno; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; + +import com.sun.star.beans.Property; +import com.sun.star.beans.UnknownPropertyException; +import com.sun.star.beans.XPropertyContainer; +import com.sun.star.beans.XPropertySet; +import com.sun.star.beans.XPropertySetInfo; +import com.sun.star.lang.WrappedTargetException; + +/** + * Utilities for properties. + */ +public class UnoProperties { + + private UnoProperties() { } + + /* + * asPropertySet + */ + + public static Optional asPropertySet(XPropertyContainer propertyContainer) { + return UnoCast.optUnoQI(XPropertySet.class, propertyContainer); + } + + /* + * getPropertySetInfo + */ + + public static Optional getPropertySetInfo(XPropertySet propertySet) { + return (Optional.ofNullable(propertySet) + .flatMap(e -> Optional.ofNullable(e.getPropertySetInfo()))); + } + + public static Optional getPropertySetInfo(XPropertyContainer propertyContainer) { + return Optional.ofNullable(propertyContainer).flatMap(UnoProperties::getPropertySetInfo); + } + + /* + * getPropertyNames + */ + + public static List getPropertyNames(Property[] properties) { + Objects.requireNonNull(properties); + return (Arrays.stream(properties) + .map(p -> p.Name) + .collect(Collectors.toList())); + } + + public static List getPropertyNames(XPropertySetInfo propertySetInfo) { + return getPropertyNames(propertySetInfo.getProperties()); + } + + public static List getPropertyNames(XPropertySet propertySet) { + return getPropertyNames(propertySet.getPropertySetInfo()); + } + + public static List getPropertyNames(XPropertyContainer propertyContainer) { + return (asPropertySet(propertyContainer) + .map(UnoProperties::getPropertyNames) + .orElse(new ArrayList<>())); + } + + /* + * getPropertyValue + */ + + public static Optional getValueAsObject(XPropertySet propertySet, String property) + throws + WrappedTargetException { + Objects.requireNonNull(propertySet); + Objects.requireNonNull(property); + try { + return Optional.ofNullable(propertySet.getPropertyValue(property)); + } catch (UnknownPropertyException e) { + return Optional.empty(); + } + } + + public static Optional getValueAsObject(XPropertyContainer propertyContainer, String property) + throws + WrappedTargetException { + Optional propertySet = asPropertySet(propertyContainer); + if (propertySet.isEmpty()) { + return Optional.empty(); + } + return UnoProperties.getValueAsObject(propertySet.get(), property); + } +} diff --git a/src/main/java/org/jabref/model/openoffice/uno/UnoRedlines.java b/src/main/java/org/jabref/model/openoffice/uno/UnoRedlines.java new file mode 100644 index 00000000000..a5f5ce7c6d1 --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/uno/UnoRedlines.java @@ -0,0 +1,54 @@ +package org.jabref.model.openoffice.uno; + +import com.sun.star.beans.UnknownPropertyException; +import com.sun.star.beans.XPropertySet; +import com.sun.star.container.NoSuchElementException; +import com.sun.star.container.XEnumeration; +import com.sun.star.container.XEnumerationAccess; +import com.sun.star.document.XRedlinesSupplier; +import com.sun.star.lang.WrappedTargetException; +import com.sun.star.text.XTextDocument; + +/** + * Change tracking and Redlines + */ +public class UnoRedlines { + + public static boolean getRecordChanges(XTextDocument doc) + throws + UnknownPropertyException, + WrappedTargetException { + + // https://wiki.openoffice.org/wiki/Documentation/DevGuide/Text/Settings + // "Properties of com.sun.star.text.TextDocument" + + XPropertySet propertySet = (UnoCast.optUnoQI(XPropertySet.class, doc) + .orElseThrow(RuntimeException::new)); + + return (boolean) propertySet.getPropertyValue("RecordChanges"); + } + + private static XRedlinesSupplier getRedlinesSupplier(XTextDocument doc) { + return UnoCast.unoQI(XRedlinesSupplier.class, doc); + } + + public static int countRedlines(XTextDocument doc) { + XRedlinesSupplier supplier = getRedlinesSupplier(doc); + XEnumerationAccess enumerationAccess = supplier.getRedlines(); + XEnumeration enumeration = enumerationAccess.createEnumeration(); + if (enumeration == null) { + return 0; + } else { + int count = 0; + while (enumeration.hasMoreElements()) { + try { + enumeration.nextElement(); + count++; + } catch (NoSuchElementException | WrappedTargetException ex) { + break; + } + } + return count; + } + } +} diff --git a/src/main/java/org/jabref/model/openoffice/uno/UnoReferenceMark.java b/src/main/java/org/jabref/model/openoffice/uno/UnoReferenceMark.java new file mode 100644 index 00000000000..8185c1ffbe3 --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/uno/UnoReferenceMark.java @@ -0,0 +1,133 @@ +package org.jabref.model.openoffice.uno; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +import com.sun.star.container.NoSuchElementException; +import com.sun.star.container.XNameAccess; +import com.sun.star.container.XNamed; +import com.sun.star.lang.DisposedException; +import com.sun.star.lang.WrappedTargetException; +import com.sun.star.text.XReferenceMarksSupplier; +import com.sun.star.text.XTextContent; +import com.sun.star.text.XTextDocument; +import com.sun.star.text.XTextRange; + +public class UnoReferenceMark { + + private UnoReferenceMark() { } + + /** + * @throws NoDocumentException If cannot get reference marks + * + * Note: also used by `isDocumentConnectionMissing` to test if + * we have a working connection. + * + */ + public static XNameAccess getNameAccess(XTextDocument doc) + throws + NoDocumentException { + + XReferenceMarksSupplier supplier = UnoCast.unoQI(XReferenceMarksSupplier.class, doc); + + try { + return supplier.getReferenceMarks(); + } catch (DisposedException ex) { + throw new NoDocumentException("UnoReferenceMarks.getNameAccess failed with" + ex); + } + } + + /** + * Names of all reference marks. + * + * Empty list for nothing. + */ + public static List getListOfNames(XTextDocument doc) + throws NoDocumentException { + + XNameAccess nameAccess = UnoReferenceMark.getNameAccess(doc); + String[] names = nameAccess.getElementNames(); + if (names == null) { + return new ArrayList<>(); + } + return Arrays.asList(names); + } + + /** + * Remove the named reference mark. + * + * Removes both the text and the mark itself. + */ + public static void remove(XTextDocument doc, String name) + throws + WrappedTargetException, + NoDocumentException, + NoSuchElementException { + + XNameAccess xReferenceMarks = UnoReferenceMark.getNameAccess(doc); + + if (xReferenceMarks.hasByName(name)) { + Optional mark = UnoNameAccess.getTextContentByName(xReferenceMarks, name); + if (mark.isEmpty()) { + return; + } + doc.getText().removeTextContent(mark.get()); + } + } + + /** + * @return reference mark as XTextContent, Optional.empty if not found. + */ + public static Optional getAsTextContent(XTextDocument doc, String name) + throws + NoDocumentException, + WrappedTargetException { + + XNameAccess nameAccess = UnoReferenceMark.getNameAccess(doc); + return UnoNameAccess.getTextContentByName(nameAccess, name); + } + + /** + * XTextRange for the named reference mark, Optional.empty if not found. + */ + public static Optional getAnchor(XTextDocument doc, String name) + throws + NoDocumentException, + WrappedTargetException { + return (UnoReferenceMark.getAsTextContent(doc, name) + .map(e -> e.getAnchor())); + } + + /** + * Insert a new reference mark at the provided cursor + * position. + * + * If {@code absorb} is true, the text in the cursor range will become + * the text with gray background. + * + * Note: LibreOffice 6.4.6.2 will create multiple reference marks + * with the same name without error or renaming. + * Its GUI does not allow this, + * but we can create them programmatically. + * In the GUI, clicking on any of those identical names + * will move the cursor to the same mark. + * + * @param name For the reference mark. + * @param range Cursor marking the location or range for + * the reference mark. + */ + public static XNamed create(XTextDocument doc, + String name, + XTextRange range, + boolean absorb) + throws + CreationException { + return UnoNamed.insertNamedTextContent(doc, + "com.sun.star.text.ReferenceMark", + name, + range, + absorb); + } +} diff --git a/src/main/java/org/jabref/model/openoffice/uno/UnoScreenRefresh.java b/src/main/java/org/jabref/model/openoffice/uno/UnoScreenRefresh.java new file mode 100644 index 00000000000..e5c27fcbb5c --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/uno/UnoScreenRefresh.java @@ -0,0 +1,33 @@ +package org.jabref.model.openoffice.uno; + +import com.sun.star.text.XTextDocument; + +/** + * Disable/enable screen refresh. + */ +public class UnoScreenRefresh { + + private UnoScreenRefresh() { } + + /** + * Disable screen refresh. + * + * Must be paired with unlockControllers() + * + * https://www.openoffice.org/api/docs/common/ref/com/sun/star/frame/XModel.html + * + * While there is at least one lock remaining, some + * notifications for display updates are not broadcasted. + */ + public static void lockControllers(XTextDocument doc) { + doc.lockControllers(); + } + + public static void unlockControllers(XTextDocument doc) { + doc.unlockControllers(); + } + + public static boolean hasControllersLocked(XTextDocument doc) { + return doc.hasControllersLocked(); + } +} diff --git a/src/main/java/org/jabref/model/openoffice/uno/UnoSelection.java b/src/main/java/org/jabref/model/openoffice/uno/UnoSelection.java new file mode 100644 index 00000000000..0ef750b9983 --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/uno/UnoSelection.java @@ -0,0 +1,115 @@ +package org.jabref.model.openoffice.uno; + +import java.util.Objects; +import java.util.Optional; + +import com.sun.star.frame.XController; +import com.sun.star.lang.XServiceInfo; +import com.sun.star.text.XTextDocument; +import com.sun.star.view.XSelectionSupplier; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Selection in the document. + */ +public class UnoSelection { + + private static final Logger LOGGER = LoggerFactory.getLogger(UnoSelection.class); + + private UnoSelection() { } + + private static Optional getSelectionSupplier(XTextDocument doc) { + if (doc == null) { + LOGGER.warn("UnoSelection.getSelectionSupplier: doc is null"); + return Optional.empty(); + } + Optional controller = UnoTextDocument.getCurrentController(doc); + if (controller.isEmpty()) { + LOGGER.warn("UnoSelection.getSelectionSupplier: getCurrentController(doc) returned empty"); + return Optional.empty(); + } + XSelectionSupplier supplier = UnoCast.unoQI(XSelectionSupplier.class, controller.get()); + if (supplier == null) { + LOGGER.warn("UnoSelection.getSelectionSupplier: unoQI(XSelectionSupplier) returned null"); + return Optional.empty(); + } + return Optional.of(supplier); + } + + /** + * @return may be Optional.empty(), or some type supporting XServiceInfo + * + * + * So far it seems the first thing we have to do + * with a selection is to decide what do we have. + * + * One way to do that is accessing its XServiceInfo interface. + * + * Experiments using printServiceInfo with cursor in various + * positions in the document: + * + * With cursor within the frame, in text: + * *** xserviceinfo.getImplementationName: "SwXTextRanges" + * "com.sun.star.text.TextRanges" + * + * With cursor somewhere else in text: + * *** xserviceinfo.getImplementationName: "SwXTextRanges" + * "com.sun.star.text.TextRanges" + * + * With cursor in comment (also known as "annotation"): + * *** XSelectionSupplier is OK + * *** Object initialSelection is null + * *** xserviceinfo is null + * + * With frame selected: + * *** xserviceinfo.getImplementationName: "SwXTextFrame" + * "com.sun.star.text.BaseFrame" + * "com.sun.star.text.TextContent" + * "com.sun.star.document.LinkTarget" + * "com.sun.star.text.TextFrame" + * "com.sun.star.text.Text" + * + * With cursor selecting an inserted image: + * *** XSelectionSupplier is OK + * *** Object initialSelection is OK + * *** xserviceinfo is OK + * *** xserviceinfo.getImplementationName: "SwXTextGraphicObject" + * "com.sun.star.text.BaseFrame" + * "com.sun.star.text.TextContent" + * "com.sun.star.document.LinkTarget" + * "com.sun.star.text.TextGraphicObject" + */ + public static Optional getSelectionAsXServiceInfo(XTextDocument doc) { + Objects.requireNonNull(doc); + Optional supplier = getSelectionSupplier(doc); + if (supplier.isEmpty()) { + LOGGER.warn("getSelectionSupplier returned empty"); + return Optional.empty(); + } + Object selection = supplier.get().getSelection(); + if (selection == null) { + return Optional.empty(); + } + XServiceInfo result = UnoCast.unoQI(XServiceInfo.class, selection); + if (result == null) { + LOGGER.warn("unoQI(XServiceInfo) returned null"); + return Optional.empty(); + } + return Optional.of(result); + } + + /** + * Select the object represented by {@code newSelection} if it is + * known and selectable in this {@code XSelectionSupplier} object. + * + * Presumably result from {@code XSelectionSupplier.getSelection()} is + * usually OK. It also accepted + * {@code XTextRange newSelection = doc.getText().getStart();} + * + */ + public static void select(XTextDocument doc, Object newSelection) { + Objects.requireNonNull(doc); + getSelectionSupplier(doc).ifPresent(e -> e.select(newSelection)); + } +} diff --git a/src/main/java/org/jabref/model/openoffice/uno/UnoStyle.java b/src/main/java/org/jabref/model/openoffice/uno/UnoStyle.java new file mode 100644 index 00000000000..86222872597 --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/uno/UnoStyle.java @@ -0,0 +1,79 @@ +package org.jabref.model.openoffice.uno; + +import java.util.Optional; + +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.style.XStyle; +import com.sun.star.style.XStyleFamiliesSupplier; +import com.sun.star.text.XTextDocument; + +/** + * Styles in the document. + */ +public class UnoStyle { + + public static final String CHARACTER_STYLES = "CharacterStyles"; + public static final String PARAGRAPH_STYLES = "ParagraphStyles"; + + private UnoStyle() { } + + private static Optional getStyleFromFamily(XTextDocument doc, + String familyName, + String styleName) + throws + WrappedTargetException { + + XStyleFamiliesSupplier fss = UnoCast.unoQI(XStyleFamiliesSupplier.class, doc); + XNameAccess fs = UnoCast.unoQI(XNameAccess.class, fss.getStyleFamilies()); + XNameContainer xFamily; + try { + xFamily = UnoCast.unoQI(XNameContainer.class, fs.getByName(familyName)); + } catch (NoSuchElementException ex) { + String msg = String.format("Style family name '%s' is not recognized", familyName); + throw new RuntimeException(msg, ex); + } + + try { + Object s = xFamily.getByName(styleName); + XStyle xs = (XStyle) UnoCast.unoQI(XStyle.class, s); + return Optional.ofNullable(xs); + } catch (NoSuchElementException ex) { + return Optional.empty(); + } + } + + public static Optional getParagraphStyle(XTextDocument doc, String styleName) + throws + WrappedTargetException { + return getStyleFromFamily(doc, PARAGRAPH_STYLES, styleName); + } + + public static Optional getCharacterStyle(XTextDocument doc, String styleName) + throws + WrappedTargetException { + return getStyleFromFamily(doc, CHARACTER_STYLES, styleName); + } + + public static Optional getInternalNameOfStyle(XTextDocument doc, String familyName, + String name) + throws + WrappedTargetException { + return (getStyleFromFamily(doc, familyName, name) + .map(e -> e.getName())); + } + + public static Optional getInternalNameOfParagraphStyle(XTextDocument doc, String name) + throws + WrappedTargetException { + return getInternalNameOfStyle(doc, PARAGRAPH_STYLES, name); + } + + public static Optional getInternalNameOfCharacterStyle(XTextDocument doc, String name) + throws + WrappedTargetException { + return getInternalNameOfStyle(doc, CHARACTER_STYLES, name); + } +} diff --git a/src/main/java/org/jabref/model/openoffice/uno/UnoTextDocument.java b/src/main/java/org/jabref/model/openoffice/uno/UnoTextDocument.java new file mode 100644 index 00000000000..188a092465a --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/uno/UnoTextDocument.java @@ -0,0 +1,92 @@ +package org.jabref.model.openoffice.uno; + +import java.util.Optional; + +import com.sun.star.beans.XPropertySet; +import com.sun.star.document.XDocumentProperties; +import com.sun.star.document.XDocumentPropertiesSupplier; +import com.sun.star.frame.XController; +import com.sun.star.frame.XFrame; +import com.sun.star.lang.WrappedTargetException; +import com.sun.star.text.XTextDocument; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class UnoTextDocument { + + private static final Logger LOGGER = LoggerFactory.getLogger(UnoTextDocument.class); + + private UnoTextDocument() { } + + /** + * @return True if we cannot reach the current document. + */ + public static boolean isDocumentConnectionMissing(XTextDocument doc) { + + boolean missing = false; + if (doc == null) { + missing = true; + } + + // Attempt to check document is really available + if (!missing) { + try { + UnoReferenceMark.getNameAccess(doc); + } catch (NoDocumentException ex) { + missing = true; + } catch (com.sun.star.lang.DisposedException ex) { + missing = true; + } + } + return missing; + } + + public static Optional getCurrentController(XTextDocument doc) { + if (doc == null) { + return Optional.empty(); + } + XController controller = doc.getCurrentController(); + if (controller == null) { + LOGGER.warn("doc.getCurrentController() returned null"); + return Optional.empty(); + } + return Optional.of(controller); + } + + /** + * @param doc The XTextDocument we want the frame title for. Null allowed. + * @return The title or Optional.empty() + */ + public static Optional getFrameTitle(XTextDocument doc) { + + Optional frame = getCurrentController(doc).map(e -> e.getFrame()); + if (frame.isEmpty()) { + return Optional.empty(); + } + + Optional propertySet = UnoCast.optUnoQI(XPropertySet.class, frame.get()); + if (propertySet.isEmpty()) { + return Optional.empty(); + } + + try { + Optional frameTitleObj = + UnoProperties.getValueAsObject(propertySet.get(), "Title"); + if (frameTitleObj.isEmpty()) { + return Optional.empty(); + } + String frameTitleString = String.valueOf(frameTitleObj.get()); + return Optional.ofNullable(frameTitleString); + } catch (WrappedTargetException e) { + LOGGER.warn("Could not get document title", e); + return Optional.empty(); + } + } + + static Optional getDocumentProperties(XTextDocument doc) { + return (Optional.ofNullable(doc) + .map(e -> UnoCast.unoQI(XDocumentPropertiesSupplier.class, e)) + .map(e -> e.getDocumentProperties())); + } +} + diff --git a/src/main/java/org/jabref/model/openoffice/uno/UnoTextRange.java b/src/main/java/org/jabref/model/openoffice/uno/UnoTextRange.java new file mode 100644 index 00000000000..1559c55d490 --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/uno/UnoTextRange.java @@ -0,0 +1,76 @@ +package org.jabref.model.openoffice.uno; + +import java.util.Optional; + +import com.sun.star.text.XFootnote; +import com.sun.star.text.XTextRange; +import com.sun.star.text.XTextRangeCompare; + +public class UnoTextRange { + + private UnoTextRange() { } + + /** + * If original is in a footnote, return a range containing + * the corresponding footnote marker. + * + * Returns Optional.empty if not in a footnote. + */ + public static Optional getFootnoteMarkRange(XTextRange original) { + XFootnote footer = UnoCast.unoQI(XFootnote.class, original.getText()); + if (footer != null) { + // If we are inside a footnote, + // find the linking footnote marker: + // The footnote's anchor gives the correct position in the text: + return Optional.ofNullable(footer.getAnchor()); + } + return Optional.empty(); + } + + /** + * Test if two XTextRange values are comparable (i.e. they share + * the same getText()). + */ + public static boolean comparables(XTextRange a, XTextRange b) { + return a.getText() == b.getText(); + } + + /** + * @return follows java conventions + * + * 1 if (a > b); (-1) if (a < b) + */ + public static int compareStarts(XTextRange a, XTextRange b) { + if (!comparables(a, b)) { + throw new RuntimeException("compareStarts: got incomparable regions"); + } + final XTextRangeCompare compare = UnoCast.unoQI(XTextRangeCompare.class, a.getText()); + return (-1) * compare.compareRegionStarts(a, b); + } + + /** + * @return follows java conventions + * + * 1 if (a > b); (-1) if (a < b) + */ + public static int compareEnds(XTextRange a, XTextRange b) { + if (!comparables(a, b)) { + throw new RuntimeException("compareEnds: got incomparable regions"); + } + final XTextRangeCompare compare = UnoCast.unoQI(XTextRangeCompare.class, a.getText()); + return (-1) * compare.compareRegionEnds(a, b); + } + + public static int compareStartsThenEnds(XTextRange a, XTextRange b) { + if (!comparables(a, b)) { + throw new RuntimeException("compareStartsThenEnds: got incomparable regions"); + } + final XTextRangeCompare compare = UnoCast.unoQI(XTextRangeCompare.class, a.getText()); + int res = (-1) * compare.compareRegionStarts(a, b); + if (res != 0) { + return res; + } + return (-1) * compare.compareRegionEnds(a, b); + } + +} diff --git a/src/main/java/org/jabref/model/openoffice/uno/UnoTextSection.java b/src/main/java/org/jabref/model/openoffice/uno/UnoTextSection.java new file mode 100644 index 00000000000..107e7a0e018 --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/uno/UnoTextSection.java @@ -0,0 +1,90 @@ +package org.jabref.model.openoffice.uno; + +import java.util.Optional; + +import com.sun.star.container.NoSuchElementException; +import com.sun.star.container.XNameAccess; +import com.sun.star.container.XNamed; +import com.sun.star.lang.DisposedException; +import com.sun.star.lang.IllegalArgumentException; +import com.sun.star.lang.WrappedTargetException; +import com.sun.star.text.XTextDocument; +import com.sun.star.text.XTextRange; +import com.sun.star.text.XTextSection; +import com.sun.star.text.XTextSectionsSupplier; +import com.sun.star.uno.Any; + +public class UnoTextSection { + + /** + * @return An XNameAccess to find sections by name. + */ + public static XNameAccess getNameAccess(XTextDocument doc) + throws + NoDocumentException { + + XTextSectionsSupplier supplier = UnoCast.unoQI(XTextSectionsSupplier.class, doc); + try { + return supplier.getTextSections(); + } catch (DisposedException ex) { + throw new NoDocumentException("UnoTextSection.getNameAccess failed with" + ex); + } + } + + /** + * Get an XTextSection by name. + */ + public static Optional getByName(XTextDocument doc, String name) + throws + WrappedTargetException, + NoDocumentException { + XNameAccess nameAccess = getNameAccess(doc); + try { + return Optional.ofNullable((XTextSection) + ((Any) nameAccess.getByName(name)) + .getObject()); + } catch (NoSuchElementException ex) { + return Optional.empty(); + } + } + + /** + * Get the XTextRange covering to the named section. + * + * @param name The name of the section to find. + * @return The XTextRange for the section, or Optional.empty(). + */ + public static Optional getAnchor(XTextDocument doc, String name) + throws + WrappedTargetException, + NoDocumentException { + + XNameAccess nameAccess = getNameAccess(doc); + return (UnoNameAccess.getTextContentByName(nameAccess, name) + .map(e -> e.getAnchor())); + } + + /** + * Create a text section with the provided name and insert it at + * the provided cursor. + * + * @param name The desired name for the section. + * @param range The location to insert at. + * + * If an XTextSection by that name already exists, + * LibreOffice (6.4.6.2) creates a section with a name different from + * what we requested, in "Section {number}" format. + */ + public static XNamed create(XTextDocument doc, String name, XTextRange range, boolean absorb) + throws + IllegalArgumentException, + CreationException { + + return UnoNamed.insertNamedTextContent(doc, + "com.sun.star.text.TextSection", + name, + range, + absorb); + } +} + diff --git a/src/main/java/org/jabref/model/openoffice/uno/UnoUndo.java b/src/main/java/org/jabref/model/openoffice/uno/UnoUndo.java new file mode 100644 index 00000000000..7f41515dd1c --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/uno/UnoUndo.java @@ -0,0 +1,45 @@ +package org.jabref.model.openoffice.uno; + +import java.util.Optional; + +import com.sun.star.document.XUndoManager; +import com.sun.star.document.XUndoManagerSupplier; +import com.sun.star.text.XTextDocument; +import com.sun.star.util.InvalidStateException; + +/** + * Undo : group document changes into larger Undo actions. + */ +public class UnoUndo { + + private UnoUndo() { } + + public static Optional getXUndoManager(XTextDocument doc) { + // https://www.openoffice.org/api/docs/common/ref/com/sun/star/document/XUndoManager.html + return (UnoCast.optUnoQI(XUndoManagerSupplier.class, doc) + .map(e -> e.getUndoManager())); + } + + /** + * Each call to enterUndoContext must be paired by a call to + * leaveUndoContext, otherwise, the document's undo stack is + * left in an inconsistent state. + */ + public static void enterUndoContext(XTextDocument doc, String title) { + Optional um = getXUndoManager(doc); + if (um.isPresent()) { + um.get().enterUndoContext(title); + } + } + + public static void leaveUndoContext(XTextDocument doc) { + Optional um = getXUndoManager(doc); + if (um.isPresent()) { + try { + um.get().leaveUndoContext(); + } catch (InvalidStateException ex) { + throw new RuntimeException("leaveUndoContext reported InvalidStateException"); + } + } + } +} diff --git a/src/main/java/org/jabref/model/openoffice/uno/UnoUserDefinedProperty.java b/src/main/java/org/jabref/model/openoffice/uno/UnoUserDefinedProperty.java new file mode 100644 index 00000000000..dd09ae4745a --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/uno/UnoUserDefinedProperty.java @@ -0,0 +1,173 @@ +package org.jabref.model.openoffice.uno; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +import com.sun.star.beans.IllegalTypeException; +import com.sun.star.beans.NotRemoveableException; +import com.sun.star.beans.PropertyExistException; +import com.sun.star.beans.PropertyVetoException; +import com.sun.star.beans.UnknownPropertyException; +import com.sun.star.beans.XPropertyContainer; +import com.sun.star.beans.XPropertySet; +import com.sun.star.beans.XPropertySetInfo; +import com.sun.star.lang.IllegalArgumentException; +import com.sun.star.lang.WrappedTargetException; +import com.sun.star.text.XTextDocument; +import com.sun.star.uno.Any; +import com.sun.star.uno.Type; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Document level user-defined properties. + * + * LibreOffice GUI: [File]/[Properties]/[Custom Properties] + */ +public class UnoUserDefinedProperty { + + private static final Logger LOGGER = LoggerFactory.getLogger(UnoUserDefinedProperty.class); + + private UnoUserDefinedProperty() { } + + public static Optional getPropertyContainer(XTextDocument doc) { + return UnoTextDocument.getDocumentProperties(doc).map(e -> e.getUserDefinedProperties()); + } + + public static List getListOfNames(XTextDocument doc) { + return (UnoUserDefinedProperty.getPropertyContainer(doc) + .map(UnoProperties::getPropertyNames) + .orElse(new ArrayList<>())); + } + + /** + * @param property Name of a custom document property in the + * current document. + * + * @return The value of the property or Optional.empty() + * + * These properties are used to store extra data about + * individual citation. In particular, the `pageInfo` part. + * + */ + public static Optional getStringValue(XTextDocument doc, String property) + throws + WrappedTargetException { + Optional propertySet = (UnoUserDefinedProperty.getPropertyContainer(doc) + .flatMap(UnoProperties::asPropertySet)); + if (propertySet.isEmpty()) { + throw new RuntimeException("getting UserDefinedProperties as XPropertySet failed"); + } + try { + String v = propertySet.get().getPropertyValue(property).toString(); + return Optional.ofNullable(v); + } catch (UnknownPropertyException ex) { + return Optional.empty(); + } + } + + /** + * @param property Name of a custom document property in the + * current document. Created if does not exist yet. + * + * @param value The value to be stored. + */ + public static void createStringProperty(XTextDocument doc, String property, String value) + throws + NotRemoveableException, + PropertyExistException, + IllegalTypeException, + IllegalArgumentException, + PropertyVetoException, + WrappedTargetException { + + Objects.requireNonNull(property); + Objects.requireNonNull(value); + + Optional container = UnoUserDefinedProperty.getPropertyContainer(doc); + + if (container.isEmpty()) { + throw new RuntimeException("UnoUserDefinedProperty.getPropertyContainer failed"); + } + + Optional propertySet = container.flatMap(UnoProperties::asPropertySet); + if (propertySet.isEmpty()) { + throw new RuntimeException("asPropertySet failed"); + } + + XPropertySetInfo propertySetInfo = propertySet.get().getPropertySetInfo(); + + if (propertySetInfo.hasPropertyByName(property)) { + try { + propertySet.get().setPropertyValue(property, value); + return; + } catch (UnknownPropertyException ex) { + // fall through to addProperty + } + } + + container.get().addProperty(property, + com.sun.star.beans.PropertyAttribute.REMOVEABLE, + new Any(Type.STRING, value)); + } + + /** + * @param property Name of a custom document property in the + * current document. + * + * Logs warning if does not exist. + */ + public static void remove(XTextDocument doc, String property) + throws + NotRemoveableException, + PropertyExistException, + IllegalTypeException, + IllegalArgumentException { + + Objects.requireNonNull(property); + + Optional container = UnoUserDefinedProperty.getPropertyContainer(doc); + + if (container.isEmpty()) { + throw new RuntimeException("getUserDefinedPropertiesAsXPropertyContainer failed"); + } + + try { + container.get().removeProperty(property); + } catch (UnknownPropertyException ex) { + LOGGER.warn(String.format("UnoUserDefinedProperty.remove(%s)" + + " This property was not there to remove", + property)); + } + } + + /** + * @param property Name of a custom document property in the + * current document. + * + * Keep silent if property did not exist. + */ + public static void removeIfExists(XTextDocument doc, String property) + throws + NotRemoveableException, + PropertyExistException, + IllegalTypeException, + IllegalArgumentException { + + Objects.requireNonNull(property); + + Optional container = UnoUserDefinedProperty.getPropertyContainer(doc); + + if (container.isEmpty()) { + throw new RuntimeException("getUserDefinedPropertiesAsXPropertyContainer failed"); + } + + try { + container.get().removeProperty(property); + } catch (UnknownPropertyException ex) { + // did not exist + } + } +} From f94c1f6b2c8b9970ade3f2d75d064c6459fcb7a4 Mon Sep 17 00:00:00 2001 From: Antal K Date: Fri, 28 May 2021 11:15:34 +0200 Subject: [PATCH 05/51] Xlint:unchecked model/openoffice/util --- src/main/java/org/jabref/model/openoffice/util/OOResult.java | 4 ++-- .../java/org/jabref/model/openoffice/util/OOVoidResult.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/jabref/model/openoffice/util/OOResult.java b/src/main/java/org/jabref/model/openoffice/util/OOResult.java index 1bc8cddcc13..2be478f6d8a 100644 --- a/src/main/java/org/jabref/model/openoffice/util/OOResult.java +++ b/src/main/java/org/jabref/model/openoffice/util/OOResult.java @@ -31,14 +31,14 @@ private OOResult(Optional result, Optional error) { * @param result Null is not allowed. */ public static OOResult ok(R result) { - return new OOResult(Optional.of(result), Optional.empty()); + return new OOResult<>(Optional.of(result), Optional.empty()); } /** * @param error Null is not allowed. */ public static OOResult error(E error) { - return new OOResult(Optional.empty(), Optional.of(error)); + return new OOResult<>(Optional.empty(), Optional.of(error)); } /* diff --git a/src/main/java/org/jabref/model/openoffice/util/OOVoidResult.java b/src/main/java/org/jabref/model/openoffice/util/OOVoidResult.java index a0bb0ebebe2..5de68f8697c 100644 --- a/src/main/java/org/jabref/model/openoffice/util/OOVoidResult.java +++ b/src/main/java/org/jabref/model/openoffice/util/OOVoidResult.java @@ -15,11 +15,11 @@ private OOVoidResult(Optional error) { } public static OOVoidResult ok() { - return new OOVoidResult(Optional.empty()); + return new OOVoidResult<>(Optional.empty()); } public static OOVoidResult error(E error) { - return new OOVoidResult(Optional.of(error)); + return new OOVoidResult<>(Optional.of(error)); } public boolean isError() { From f9a817603c7ac24eb0ad4f3b8ab624bd9f018d77 Mon Sep 17 00:00:00 2001 From: Antal K Date: Fri, 28 May 2021 09:38:30 +0200 Subject: [PATCH 06/51] add ootext --- .../model/openoffice/ootext/OOFormat.java | 73 ++ .../model/openoffice/ootext/OOText.java | 62 ++ .../model/openoffice/ootext/OOTextIntoOO.java | 817 ++++++++++++++++++ 3 files changed, 952 insertions(+) create mode 100644 src/main/java/org/jabref/model/openoffice/ootext/OOFormat.java create mode 100644 src/main/java/org/jabref/model/openoffice/ootext/OOText.java create mode 100644 src/main/java/org/jabref/model/openoffice/ootext/OOTextIntoOO.java diff --git a/src/main/java/org/jabref/model/openoffice/ootext/OOFormat.java b/src/main/java/org/jabref/model/openoffice/ootext/OOFormat.java new file mode 100644 index 00000000000..472ca519161 --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/ootext/OOFormat.java @@ -0,0 +1,73 @@ +package org.jabref.model.openoffice.ootext; + +public class OOFormat { + + /** + * Mark {@code s} as using a character locale known to OO. + * + * @param locale language[-country[-territory]] + * + * https://www.openoffice.org/api/docs/common/ref/com/sun/star/lang/Locale.html + * + * The country part is optional. + * + * The territory part is not only optional, the allowed "codes are + * vendor and browser-specific", so probably best to avoid them if possible. + * + */ + public static OOText setLocale(OOText s, String locale) { + return OOText.fromString(String.format("", locale) + s.asString() + ""); + } + + /** + * Mark {@code s} as using the character locale "zxx", which means + * "no language", "no linguistic content". + * + * Used around citation marks, probably to turn off spellchecking. + * + */ + public static OOText setLocaleNone(OOText s) { + return OOFormat.setLocale(s, "zxx"); + } + + /** + * Mark {@code s} using a character style {@code charStyle} + * + * @param charStyle Name of a character style known to OO. May be + * empty for "Standard", which in turn means do not override any properties. + * + */ + public static OOText setCharStyle(OOText s, String charStyle) { + return OOText.fromString(String.format("", charStyle) + + s.asString() + + ""); + } + + /** + * Mark {@code s} as part of a paragraph with style {@code paraStyle} + */ + public static OOText paragraph(OOText s, String paraStyle) { + if (paraStyle == null || "".equals(paraStyle)) { + return paragraph(s); + } + String startTag = String.format("

", paraStyle); + return OOText.fromString(startTag + s.asString() + "

"); + } + + /** + * Mark {@code s} as part of a paragraph. + */ + public static OOText paragraph(OOText s) { + return OOText.fromString("

" + s.asString() + "

"); + } + + /** + * Format an OO cross-reference showing the target's page number + * as label to a reference mark. + */ + public static OOText formatReferenceToPageNumberOfReferenceMark(String referencMarkName) { + String s = String.format("", + referencMarkName); + return OOText.fromString(s); + } + } diff --git a/src/main/java/org/jabref/model/openoffice/ootext/OOText.java b/src/main/java/org/jabref/model/openoffice/ootext/OOText.java new file mode 100644 index 00000000000..a568a7c6e8a --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/ootext/OOText.java @@ -0,0 +1,62 @@ +package org.jabref.model.openoffice.ootext; + +import java.util.Objects; + +/** + * Text with HTML-like markup as understood by OOTextIntoOO.write + * + * Some of the tags can be added using OOFormat methods. Others come + * from the layout engine, either by interpreting LaTeX markup or from + * settings in the jstyle file. + */ +public class OOText { + + private final String data; + + private OOText(String data) { + Objects.requireNonNull(data); + this.data = data; + } + + /* null input is passed through */ + public static OOText fromString(String s) { + if (s == null) { + return null; + } + return new OOText(s); + } + + /* null input is passed through */ + public static String toString(OOText s) { + if (s == null) { + return null; + } + return s.data; + } + + public String asString() { + return data; + } + + /* Object.equals */ + @Override + public boolean equals(Object o) { + + if (o == this) { + return true; + } + + if (!(o instanceof OOText)) { + return false; + } + + OOText c = (OOText) o; + + return data.equals(c.data); + } + + @Override + public int hashCode() { + return data.hashCode(); + } +} diff --git a/src/main/java/org/jabref/model/openoffice/ootext/OOTextIntoOO.java b/src/main/java/org/jabref/model/openoffice/ootext/OOTextIntoOO.java new file mode 100644 index 00000000000..f062bed7c40 --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/ootext/OOTextIntoOO.java @@ -0,0 +1,817 @@ +package org.jabref.model.openoffice.ootext; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.Stack; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.jabref.architecture.AllowedToUseAwt; +import org.jabref.model.openoffice.uno.CreationException; +import org.jabref.model.openoffice.uno.UnoCast; +import org.jabref.model.openoffice.uno.UnoCrossRef; +import org.jabref.model.openoffice.util.OOPair; + +import com.sun.star.awt.FontSlant; +import com.sun.star.awt.FontStrikeout; +import com.sun.star.awt.FontUnderline; +import com.sun.star.awt.FontWeight; +import com.sun.star.beans.Property; +import com.sun.star.beans.PropertyAttribute; +import com.sun.star.beans.PropertyState; +import com.sun.star.beans.PropertyVetoException; +import com.sun.star.beans.UnknownPropertyException; +import com.sun.star.beans.XMultiPropertySet; +import com.sun.star.beans.XMultiPropertyStates; +import com.sun.star.beans.XPropertySet; +import com.sun.star.beans.XPropertySetInfo; +import com.sun.star.beans.XPropertyState; +import com.sun.star.container.NoSuchElementException; +import com.sun.star.lang.Locale; +import com.sun.star.lang.WrappedTargetException; +import com.sun.star.style.CaseMap; +import com.sun.star.text.ControlCharacter; +import com.sun.star.text.XParagraphCursor; +import com.sun.star.text.XText; +import com.sun.star.text.XTextCursor; +import com.sun.star.text.XTextDocument; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Interpret OOText into an OpenOffice or LibreOffice writer + * document. + */ +@AllowedToUseAwt("Requires AWT for changing document properties") +public class OOTextIntoOO { + + private static final Logger LOGGER = LoggerFactory.getLogger(OOTextIntoOO.class); + + /** + * "ParaStyleName" is an OpenOffice Property name. + */ + private static final String PARA_STYLE_NAME = "ParaStyleName"; + + /* + * Character property names used in multiple locations below. + */ + private static final String CHAR_ESCAPEMENT_HEIGHT = "CharEscapementHeight"; + private static final String CHAR_ESCAPEMENT = "CharEscapement"; + private static final String CHAR_STYLE_NAME = "CharStyleName"; + private static final String CHAR_UNDERLINE = "CharUnderline"; + private static final String CHAR_STRIKEOUT = "CharStrikeout"; + + /* + * SUPERSCRIPT_VALUE and SUPERSCRIPT_HEIGHT are percents of the normal character height + */ + private static final short CHAR_ESCAPEMENT_VALUE_DEFAULT = (short) 0; + private static final short SUPERSCRIPT_VALUE = (short) 33; + private static final short SUBSCRIPT_VALUE = (short) -10; + private static final byte CHAR_ESCAPEMENT_HEIGHT_DEFAULT = (byte) 100; + private static final byte SUPERSCRIPT_HEIGHT = (byte) 58; + private static final byte SUBSCRIPT_HEIGHT = (byte) 58; + + private static final String TAG_NAME_REGEXP = + "(?:b|i|em|tt|smallcaps|sup|sub|u|s|p|span|oo:referenceToPageNumberOfReferenceMark)"; + + private static final String ATTRIBUTE_NAME_REGEXP = + "(?:oo:ParaStyleName|oo:CharStyleName|lang|style|target)"; + + private static final String ATTRIBUTE_VALUE_REGEXP = "\"([^\"]*)\""; + + private static final Pattern HTML_TAG = + Pattern.compile("<(/" + TAG_NAME_REGEXP + ")>" + + "|" + + "<(" + TAG_NAME_REGEXP + ")" + + "((?:\\s+(" + ATTRIBUTE_NAME_REGEXP + ")=" + ATTRIBUTE_VALUE_REGEXP + ")*)" + + ">"); + + private static final Pattern ATTRIBUTE_PATTERN = + Pattern.compile("\\s+(" + ATTRIBUTE_NAME_REGEXP + ")=" + ATTRIBUTE_VALUE_REGEXP); + + private OOTextIntoOO() { + // Hide the public constructor + } + + /** + * Insert a text with formatting indicated by HTML-like tags, into + * a text at the position given by a cursor. + * + * Limitation: understands no entities. It does not receive any either, unless + * the user provides it. + * + * To limit the damage {@code TAG_NAME_REGEXP} and {@code ATTRIBUTE_NAME_REGEXP} + * explicitly lists the names we care about. + * + * Notable changes w.r.t insertOOFormattedTextAtCurrentLocation: + * + * - new tags: + * + * - <span lang="zxx"> + * - earlier was applied from code + * + * - <span oo:CharStyleName="CharStylename"> + * - earlier was applied from code, for "CitationCharacterFormat" + * + * - <p> start new paragraph + * - earlier was applied from code + * + * - <p oo:ParaStyleName="ParStyleName"> : start new paragraph and apply ParStyleName + * - earlier was applied from code + * + * - <tt> + * - earlier: known, but ignored + * - now: equivalent to <span oo:CharStyleName="Example"> + * - <oo:referenceToPageNumberOfReferenceMark> (self-closing) + * + * - closing tags try to properly restore state (in particular, the "not directly set" state) + * instead of dictating an "off" state. This makes a difference when the value inherited from + * another level (for example the paragraph) is not the "off" state. + * + * An example: a style with + * ReferenceParagraphFormat="JR_bibentry" + * Assume JR_bibentry in LibreOffice is a paragraph style that prescribes "bold" font. + * LAYOUT only prescribes bold around year. + * Which parts of the bibliography entries should come out as bold? + * + * - The user can format citation marks (it is enough to format their start) and the + * properties not (everywhere) dictated by the style are preserved (where they are not). + * + * @param position The cursor giving the insert location. Not modified. + * @param ootext The marked-up text to insert. + */ + public static void write(XTextDocument doc, XTextCursor position, OOText ootext) + throws + UnknownPropertyException, + PropertyVetoException, + WrappedTargetException, + IllegalArgumentException, + NoSuchElementException, + CreationException { + + final boolean debugThisFun = false; + + Objects.requireNonNull(doc); + Objects.requireNonNull(ootext); + Objects.requireNonNull(position); + + String lText = OOText.toString(ootext); + + if (debugThisFun) { + System.out.println(lText); + } + + XText text = position.getText(); + XTextCursor cursor = text.createTextCursorByRange(position); + cursor.collapseToEnd(); + + MyPropertyStack formatStack = new MyPropertyStack(cursor); + Stack expectEnd = new Stack<>(); + + // We need to extract formatting. Use a simple regexp search iteration: + int piv = 0; + Matcher m = HTML_TAG.matcher(lText); + while (m.find()) { + + String currentSubstring = lText.substring(piv, m.start()); + if (!currentSubstring.isEmpty()) { + cursor.setString(currentSubstring); + } + formatStack.apply(cursor); + cursor.collapseToEnd(); + + String fullTag = m.group(); + String endTagName = m.group(1); + String startTagName = m.group(2); + String attributeListPart = m.group(3); + boolean isStartTag = (endTagName == null) || "".equals(endTagName); + String tagName = isStartTag ? startTagName : endTagName; + Objects.requireNonNull(tagName); + + // Attibutes parsed into (name,value) pairs. + List> attributes = parseAttributes(attributeListPart); + + // Handle tags: + switch (tagName) { + case "b": + formatStack.pushLayer(setCharWeight(FontWeight.BOLD)); + expectEnd.push("/" + tagName); + break; + case "i": + case "em": + formatStack.pushLayer(setCharPosture(FontSlant.ITALIC)); + expectEnd.push("/" + tagName); + break; + case "smallcaps": + formatStack.pushLayer(setCharCaseMap(CaseMap.SMALLCAPS)); + expectEnd.push("/" + tagName); + break; + case "sup": + formatStack.pushLayer(setSuperScript(formatStack)); + expectEnd.push("/" + tagName); + break; + case "sub": + formatStack.pushLayer(setSubScript(formatStack)); + expectEnd.push("/" + tagName); + break; + case "u": + formatStack.pushLayer(setCharUnderline(FontUnderline.SINGLE)); + expectEnd.push("/" + tagName); + break; + case "s": + formatStack.pushLayer(setCharStrikeout(FontStrikeout.SINGLE)); + expectEnd.push("/" + tagName); + break; + case "/p": + // nop + break; + case "p": + insertParagraphBreak(text, cursor); + cursor.collapseToEnd(); + for (OOPair pair : attributes) { + String key = pair.a; + String value = pair.b; + switch (key) { + case "oo:ParaStyleName": + //

+ if (value != null && !value.equals("")) { + if (setParagraphStyle(cursor, value)) { + if (debugThisFun) { + // Presumably tested already: + LOGGER.warn(String.format("oo:ParaStyleName=\"%s\" failed", value)); + } + } + } else { + if (debugThisFun) { + LOGGER.warn(String.format("oo:ParaStyleName inherited")); + } + } + break; + default: + LOGGER.warn(String.format("Unexpected attribute '%s' for <%s>", key, tagName)); + break; + } + } + break; + case "oo:referenceToPageNumberOfReferenceMark": + for (OOPair pair : attributes) { + String key = pair.a; + String value = pair.b; + switch (key) { + case "target": + UnoCrossRef.insertReferenceToPageNumberOfReferenceMark(doc, value, cursor); + break; + default: + LOGGER.warn(String.format("Unexpected attribute '%s' for <%s>", key, tagName)); + break; + } + } + break; + case "tt": + // Note: "Example" names a character style in LibreOffice. + formatStack.pushLayer(setCharStyleName("Example")); + expectEnd.push("/" + tagName); + break; + case "span": + List> settings = new ArrayList<>(); + for (OOPair pair : attributes) { + String key = pair.a; + String value = pair.b; + switch (key) { + case "oo:CharStyleName": + // + settings.addAll(setCharStyleName(value)); + break; + case "lang": + // + // + settings.addAll(setCharLocale(value)); + break; + case "style": + // HTML-style small-caps + if (value.equals("font-variant: small-caps")) { + settings.addAll(setCharCaseMap(CaseMap.SMALLCAPS)); + break; + } + LOGGER.warn(String.format("Unexpected value %s for attribute '%s' for <%s>", + value, key, tagName)); + break; + default: + LOGGER.warn(String.format("Unexpected attribute '%s' for <%s>", key, tagName)); + break; + } + } + formatStack.pushLayer(settings); + expectEnd.push("/" + tagName); + break; + case "/b": + case "/i": + case "/em": + case "/tt": + case "/smallcaps": + case "/sup": + case "/sub": + case "/u": + case "/s": + case "/span": + formatStack.popLayer(); + String expected = expectEnd.pop(); + if (!tagName.equals(expected)) { + LOGGER.warn(String.format("expected '<%s>', found '<%s>' after '%s'", + expected, + tagName, + currentSubstring)); + } + break; + } + + piv = m.end(); + } + + if (piv < lText.length()) { + cursor.setString(lText.substring(piv)); + } + formatStack.apply(cursor); + cursor.collapseToEnd(); + + if (!expectEnd.empty()) { + String rest = ""; + for (String s : expectEnd) { + rest = String.format("<%s>", s) + rest; + } + LOGGER.warn(String.format("OOTextIntoOO.write:" + + " expectEnd stack is not empty at the end: %s%n", + rest)); + } + } + + /** + * Purpose: in some cases we do not want to inherit direct + * formatting from the context. + * + * In particular, when filling the bibliography title and body. + */ + public static void removeDirectFormatting(XTextCursor cursor) { + + XMultiPropertyStates mpss = UnoCast.unoQI(XMultiPropertyStates.class, cursor); + + XPropertySet propertySet = UnoCast.unoQI(XPropertySet.class, cursor); + XPropertyState xPropertyState = UnoCast.unoQI(XPropertyState.class, cursor); + + try { + // Special handling + propertySet.setPropertyValue(CHAR_STYLE_NAME, "Standard"); + xPropertyState.setPropertyToDefault("CharCaseMap"); + } catch (UnknownPropertyException | + PropertyVetoException | + IllegalArgumentException | + WrappedTargetException ex) { + LOGGER.warn("exception caught", ex); + } + + mpss.setAllPropertiesToDefault(); + + /* + * Now that we have called setAllPropertiesToDefault, check which properties + * are not set to default and try to correct what we can and seem necessary. + * + * Note: tested with LibreOffice : 6.4.6.2 + */ + + // Only report those we do not yet know about + final Set knownToFail = Set.of("ListAutoFormat", + "ListId", + "NumberingIsNumber", + "NumberingLevel", + "NumberingRules", + "NumberingStartValue", + "ParaChapterNumberingLevel", + "ParaIsNumberingRestart", + "ParaStyleName"); + + // query again, just in case it matters + propertySet = UnoCast.unoQI(XPropertySet.class, cursor); + XPropertySetInfo propertySetInfo = propertySet.getPropertySetInfo(); + + // check the result + for (Property p : propertySetInfo.getProperties()) { + if ((p.Attributes & PropertyAttribute.READONLY) != 0) { + continue; + } + try { + if (isPropertyDefault(cursor, p.Name)) { + continue; + } + } catch (UnknownPropertyException ex) { + throw new RuntimeException("Unexpected UnknownPropertyException"); + } + if (knownToFail.contains(p.Name)) { + continue; + } + LOGGER.warn(String.format("OOTextIntoOO.removeDirectFormatting failed on '%s'", p.Name)); + } + } + + static class MyPropertyStack { + + /* + * We only try to control these. Should include all character + * properties we set, and maybe their interdependencies. + * + * For a list of properties see: + * https://www.openoffice.org/api/docs/common/ref/com/sun/star/style/CharacterProperties.html + * + * For interdependencies between properties: + * https://wiki.openoffice.org/wiki/Documentation/DevGuide/Text/Formatting + * (at the end, under "Interdependencies between Properties") + * + */ + static final Set CONTROLLED_PROPERTIES = Set.of( + + /* Used for SuperScript, SubScript. + * + * These three are interdependent: changing one may change others. + */ + "CharEscapement", "CharEscapementHeight", "CharAutoEscapement", + + /* used for Bold */ + "CharWeight", + + /* Used for Italic */ + "CharPosture", + + /* Used for strikeout. These two are interdependent. */ + "CharStrikeout", "CharCrossedOut", + + /* Used for underline. These three are interdependent, but apparently + * we can leave out the last two. + */ + "CharUnderline", // "CharUnderlineColor", "CharUnderlineHasColor", + + /* Used for lang="zxx", to silence spellchecker. */ + "CharLocale", + + /* Used for CitationCharacterFormat. */ + "CharStyleName", + + /* Used for and */ + "CharCaseMap"); + + /** + * The number of properties actually controlled. + */ + final int goodSize; + + /** + * From property name to index in goodNames. + */ + final Map goodNameToIndex; + + /** + * From index to property name. + */ + final String[] goodNames; + + /** + * Maintain a stack of layers, each containing a description + * of the desired state of properties. Each description is an + * ArrayList of property values, Optional.empty() encoding + * "not directly set". + */ + final Stack>> layers; + + MyPropertyStack(XTextCursor cursor) + throws UnknownPropertyException { + + XPropertySet propertySet = UnoCast.unoQI(XPropertySet.class, cursor); + XPropertySetInfo propertySetInfo = propertySet.getPropertySetInfo(); + + /* + * On creation, initialize the property name -- index mapping. + */ + this.goodNameToIndex = new HashMap<>(); + int nextIndex = 0; + for (Property p : propertySetInfo.getProperties()) { + if ((p.Attributes & PropertyAttribute.READONLY) != 0) { + continue; + } + if (!CONTROLLED_PROPERTIES.contains(p.Name)) { + continue; + } + this.goodNameToIndex.put(p.Name, nextIndex); + nextIndex++; + } + + this.goodSize = nextIndex; + + this.goodNames = new String[goodSize]; + for (Map.Entry entry : goodNameToIndex.entrySet()) { + goodNames[ entry.getValue() ] = entry.getKey(); + } + + // XMultiPropertySet.setPropertyValues() + // requires alphabetically sorted property names. + // We adjust here: + Arrays.sort(goodNames); + for (int i = 0; i < goodSize; i++) { + this.goodNameToIndex.put(goodNames[i], i); + } + + /* + * Get the initial state of the properties and add add the first layer. + */ + XMultiPropertyStates mpss = UnoCast.unoQI(XMultiPropertyStates.class, cursor); + PropertyState[] propertyStates = mpss.getPropertyStates(goodNames); + + XMultiPropertySet mps = UnoCast.unoQI(XMultiPropertySet.class, cursor); + Object[] initialValues = mps.getPropertyValues(goodNames); + + ArrayList> initialValuesOpt = new ArrayList<>(goodSize); + + for (int i = 0; i < goodSize; i++) { + if (propertyStates[i] == PropertyState.DIRECT_VALUE) { + initialValuesOpt.add(Optional.of(initialValues[i])); + } else { + initialValuesOpt.add(Optional.empty()); + } + } + + this.layers = new Stack<>(); + this.layers.push(initialValuesOpt); + } + + /** + * Given a list of property name, property value pairs, + * construct and push a new layer describing the intended + * state after these have been applied. + * + * Opening tags usually call this. + */ + void pushLayer(List> settings) { + ArrayList> oldLayer = layers.peek(); + ArrayList> newLayer = new ArrayList<>(oldLayer); + for (OOPair pair : settings) { + String name = pair.a; + Integer i = goodNameToIndex.get(name); + if (i == null) { + LOGGER.warn(String.format("pushLayer: '%s' is not in goodNameToIndex", name)); + continue; + } + Object newValue = pair.b; + newLayer.set(i, Optional.ofNullable(newValue)); + } + layers.push(newLayer); + } + + /** + * Closing tags just pop a layer. + */ + void popLayer() { + if (layers.size() <= 1) { + LOGGER.warn("popLayer: underflow"); + return; + } + layers.pop(); + } + + /** + * Apply the current desired formatting state to a cursor. + * The idea is to minimize the number of calls to OpenOffice. + */ + void apply(XTextCursor cursor) { + XMultiPropertySet mps = UnoCast.unoQI(XMultiPropertySet.class, cursor); + XMultiPropertyStates mpss = UnoCast.unoQI(XMultiPropertyStates.class, cursor); + ArrayList> topLayer = layers.peek(); + try { + // select values to be set + ArrayList names = new ArrayList<>(goodSize); + ArrayList values = new ArrayList<>(goodSize); + // and those to be cleared + ArrayList delNames = new ArrayList<>(goodSize); + for (int i = 0; i < goodSize; i++) { + if (topLayer.get(i).isPresent()) { + names.add(goodNames[i]); + values.add(topLayer.get(i).get()); + } else { + delNames.add(goodNames[i]); + } + } + // namesArray must be alphabetically sorted. + String[] namesArray = names.toArray(new String[names.size()]); + String[] delNamesArray = delNames.toArray(new String[names.size()]); + mpss.setPropertiesToDefault(delNamesArray); + mps.setPropertyValues(namesArray, values.toArray()); + } catch (UnknownPropertyException ex) { + LOGGER.warn("UnknownPropertyException in MyPropertyStack.apply"); + } catch (PropertyVetoException ex) { + LOGGER.warn("PropertyVetoException in MyPropertyStack.apply"); + } catch (IllegalArgumentException ex) { + LOGGER.warn("IllegalArgumentException in MyPropertyStack.apply"); + } catch (WrappedTargetException ex) { + LOGGER.warn("WrappedTargetException in MyPropertyStack.apply"); + } + } + + // Relative CharEscapement needs to know current values. + Optional getPropertyValue(String name) { + if (goodNameToIndex.containsKey(name)) { + int i = goodNameToIndex.get(name); + ArrayList> topLayer = layers.peek(); + Optional value = topLayer.get(i); + return value; + } + return Optional.empty(); + } + } + + /** + * Parse HTML-like attributes to a list of (name,value) pairs. + */ + private static List> parseAttributes(String s) { + List> res = new ArrayList<>(); + if (s == null) { + return res; + } + Matcher m = ATTRIBUTE_PATTERN.matcher(s); + while (m.find()) { + String key = m.group(1); + String value = m.group(2); + res.add(new OOPair(key, value)); + } + return res; + } + + /* + * We rely on property values being either DIRECT_VALUE or + * DEFAULT_VALUE (not AMBIGUOUS_VALUE). If the cursor covers a homogeneous region, + * or is collapsed, then this is true. + */ + private static boolean isPropertyDefault(XTextCursor cursor, String propertyName) + throws + UnknownPropertyException { + XPropertyState xPropertyState = UnoCast.unoQI(XPropertyState.class, cursor); + PropertyState state = xPropertyState.getPropertyState(propertyName); + if (state == PropertyState.AMBIGUOUS_VALUE) { + throw new RuntimeException("PropertyState.AMBIGUOUS_VALUE" + + " (expected properties for a homogeneous cursor)"); + } + return state == PropertyState.DEFAULT_VALUE; + } + + /* + * Various property change requests. Their results are passed to MyPropertyStack.pushLayer() + */ + + private static List> setCharWeight(float value) { + List> settings = new ArrayList<>(); + settings.add(new OOPair<>("CharWeight", (Float) value)); + return settings; + } + + private static List> setCharPosture(FontSlant value) { + List> settings = new ArrayList<>(); + settings.add(new OOPair<>("CharPosture", (Object) value)); + return settings; + } + + private static List> setCharCaseMap(short value) { + List> settings = new ArrayList<>(); + settings.add(new OOPair<>("CharCaseMap", (Short) value)); + return settings; + } + + // com.sun.star.awt.FontUnderline + private static List> setCharUnderline(short value) { + List> settings = new ArrayList<>(); + settings.add(new OOPair<>(CHAR_UNDERLINE, (Short) value)); + return settings; + } + + // com.sun.star.awt.FontStrikeout + private static List> setCharStrikeout(short value) { + List> settings = new ArrayList<>(); + settings.add(new OOPair<>(CHAR_STRIKEOUT, (Short) value)); + return settings; + } + + // CharStyleName + private static List> setCharStyleName(String value) { + List> settings = new ArrayList<>(); + if (value != null && value != "") { + settings.add(new OOPair<>(CHAR_STYLE_NAME, value)); + } else { + LOGGER.warn("setCharStyleName: received null or empty value"); + } + return settings; + } + + // Locale + private static List> setCharLocale(Locale value) { + List> settings = new ArrayList<>(); + settings.add(new OOPair<>("CharLocale", (Object) value)); + return settings; + } + + /** + * Locale from string encoding: language, language-country or language-country-variant + */ + private static List> setCharLocale(String value) { + if (value == null || "".equals(value)) { + throw new RuntimeException("setCharLocale \"\" or null"); + } + String[] parts = value.split("-"); + String language = (parts.length > 0) ? parts[0] : ""; + String country = (parts.length > 1) ? parts[1] : ""; + String variant = (parts.length > 2) ? parts[2] : ""; + return setCharLocale(new Locale(language, country, variant)); + } + + /* + * SuperScript and SubScript. + * + * @param relative If true, calculate the new values relative to + * the current values. This allows subscript-in-superscript. + */ + private static List> setCharEscapement(Optional value, + Optional height, + boolean relative, + MyPropertyStack formatStack) { + List> settings = new ArrayList<>(); + Optional oldValue = (formatStack + .getPropertyValue(CHAR_ESCAPEMENT) + .map(e -> (short) e)); + + Optional oldHeight = (formatStack + .getPropertyValue(CHAR_ESCAPEMENT_HEIGHT) + .map(e -> (byte) e)); + + if (relative && (value.isPresent() || height.isPresent())) { + double oldHeightFloat = oldHeight.orElse(CHAR_ESCAPEMENT_HEIGHT_DEFAULT) * 0.01; + double oldValueFloat = oldValue.orElse(CHAR_ESCAPEMENT_VALUE_DEFAULT); + double heightFloat = height.orElse(CHAR_ESCAPEMENT_HEIGHT_DEFAULT); + double valueFloat = value.orElse(CHAR_ESCAPEMENT_VALUE_DEFAULT); + byte newHeight = (byte) Math.round(heightFloat * oldHeightFloat); + short newValue = (short) Math.round(valueFloat * oldHeightFloat + oldValueFloat); + if (value.isPresent()) { + settings.add(new OOPair<>(CHAR_ESCAPEMENT, (Short) newValue)); + } + if (height.isPresent()) { + settings.add(new OOPair<>(CHAR_ESCAPEMENT_HEIGHT, (Byte) newHeight)); + } + } else { + if (value.isPresent()) { + settings.add(new OOPair<>(CHAR_ESCAPEMENT, (Short) value.get())); + } + if (height.isPresent()) { + settings.add(new OOPair<>(CHAR_ESCAPEMENT_HEIGHT, (Byte) height.get())); + } + } + return settings; + } + + private static List> setSubScript(MyPropertyStack formatStack) { + return setCharEscapement(Optional.of(SUBSCRIPT_VALUE), + Optional.of(SUBSCRIPT_HEIGHT), + true, + formatStack); + } + + private static List> setSuperScript(MyPropertyStack formatStack) { + return setCharEscapement(Optional.of(SUPERSCRIPT_VALUE), + Optional.of(SUPERSCRIPT_HEIGHT), + true, + formatStack); + } + + /* + * @return true on failure + */ + public static boolean setParagraphStyle(XTextCursor cursor, String paragraphStyle) { + final boolean FAIL = true; + final boolean PASS = false; + + XParagraphCursor paragraphCursor = UnoCast.unoQI(XParagraphCursor.class, cursor); + XPropertySet propertySet = UnoCast.unoQI(XPropertySet.class, paragraphCursor); + try { + propertySet.setPropertyValue(PARA_STYLE_NAME, paragraphStyle); + return PASS; + } catch (UnknownPropertyException + | PropertyVetoException + | IllegalArgumentException + | WrappedTargetException ex) { + return FAIL; + } + } + + private static void insertParagraphBreak(XText text, XTextCursor cursor) + throws IllegalArgumentException { + text.insertControlCharacter(cursor, ControlCharacter.PARAGRAPH_BREAK, true); + } + +} From 6d013f12ccd1dd6b22ac833c98368d714cea551e Mon Sep 17 00:00:00 2001 From: Antal K Date: Fri, 28 May 2021 11:11:26 +0200 Subject: [PATCH 07/51] add rangesort --- .../rangesort/FunctionalTextViewCursor.java | 159 +++++++++++++++ .../openoffice/rangesort/RangeKeyedMap.java | 90 +++++++++ .../rangesort/RangeKeyedMapList.java | 49 +++++ .../openoffice/rangesort/RangeOverlap.java | 16 ++ .../rangesort/RangeOverlapFinder.java | 61 ++++++ .../rangesort/RangeOverlapKind.java | 14 ++ .../openoffice/rangesort/RangeSortEntry.java | 42 ++++ .../openoffice/rangesort/RangeSortVisual.java | 182 ++++++++++++++++++ .../openoffice/rangesort/RangeSortable.java | 24 +++ 9 files changed, 637 insertions(+) create mode 100644 src/main/java/org/jabref/model/openoffice/rangesort/FunctionalTextViewCursor.java create mode 100644 src/main/java/org/jabref/model/openoffice/rangesort/RangeKeyedMap.java create mode 100644 src/main/java/org/jabref/model/openoffice/rangesort/RangeKeyedMapList.java create mode 100644 src/main/java/org/jabref/model/openoffice/rangesort/RangeOverlap.java create mode 100644 src/main/java/org/jabref/model/openoffice/rangesort/RangeOverlapFinder.java create mode 100644 src/main/java/org/jabref/model/openoffice/rangesort/RangeOverlapKind.java create mode 100644 src/main/java/org/jabref/model/openoffice/rangesort/RangeSortEntry.java create mode 100644 src/main/java/org/jabref/model/openoffice/rangesort/RangeSortVisual.java create mode 100644 src/main/java/org/jabref/model/openoffice/rangesort/RangeSortable.java diff --git a/src/main/java/org/jabref/model/openoffice/rangesort/FunctionalTextViewCursor.java b/src/main/java/org/jabref/model/openoffice/rangesort/FunctionalTextViewCursor.java new file mode 100644 index 00000000000..3f780274b0f --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/rangesort/FunctionalTextViewCursor.java @@ -0,0 +1,159 @@ +package org.jabref.model.openoffice.rangesort; + +import java.util.Arrays; +import java.util.Objects; + +import org.jabref.model.openoffice.uno.UnoCursor; +import org.jabref.model.openoffice.uno.UnoSelection; +import org.jabref.model.openoffice.util.OOResult; + +import com.sun.star.lang.XServiceInfo; +import com.sun.star.text.XTextDocument; +import com.sun.star.text.XTextRange; +import com.sun.star.text.XTextViewCursor; + +/* + * A problem with XTextViewCursor: if it is not in text, then we get a + * crippled version that does not support viewCursor.getStart() or + * viewCursor.gotoRange(range,false), and will throw an exception + * instead. + * + * Here we manipulate the cursor via XSelectionSupplier.getSelection and + * XSelectionSupplier.select to move it to the text. + * + * Seems to work when the user selected a frame or image. + * In these cases restoring the selection works, too. + * + * When the cursor is in a comment (referred to as "annotation" in OO + * API) then initialSelection is null, and select() fails to + * get a functional viewCursor. + * + * If FunctionalTextViewCursor.get() reports error, we have to ask the + * user to move the cursor into the text part of the document. + * + * Usage: + * + * OOResult fcursor = FunctionalTextViewCursor.get(doc, msg); + * if (fcursor.isError()) { + * ... + * } else { + * XTextViewCursor viewCursor = fcursor.get().getViewCursor(); + * ... + * fc.restore(); + * } + * + */ +public class FunctionalTextViewCursor { + + /* + * The initial position of the cursor or null. + */ + private XTextRange initialPosition; + + /* + * The initial selection in the document or null. + */ + private XServiceInfo initialSelection; + + /* + * The view cursor, potentially moved from its original location. + */ + private XTextViewCursor viewCursor; + + private FunctionalTextViewCursor(XTextRange initialPosition, + XServiceInfo initialSelection, + XTextViewCursor viewCursor) { + this.initialPosition = initialPosition; + this.initialSelection = initialSelection; + this.viewCursor = viewCursor; + } + + /* + * Get a functional XTextViewCursor or an error message. + * + * The cursor position may differ from the location + * provided by the user. + * + * On failure the constructor restores the selection. On success, + * the caller may want to call instance.restore() after finished + * using the cursor. + */ + public static OOResult get(XTextDocument doc) { + + Objects.requireNonNull(doc); + + XTextRange initialPosition = null; + XServiceInfo initialSelection = UnoSelection.getSelectionAsXServiceInfo(doc).orElse(null); + XTextViewCursor viewCursor = UnoCursor.getViewCursor(doc).orElse(null); + if (viewCursor != null) { + try { + initialPosition = UnoCursor.createTextCursorByRange(viewCursor); + viewCursor.getStart(); + return OOResult.ok(new FunctionalTextViewCursor(initialPosition, + initialSelection, + viewCursor)); + } catch (com.sun.star.uno.RuntimeException ex) { + // bad cursor + viewCursor = null; + initialPosition = null; + } + } + + if (initialSelection == null) { + String errorMessage = ("Selection is not available:" + + " cannot provide a functional view cursor"); + return OOResult.error(errorMessage); + } else if (!Arrays.stream(initialSelection.getSupportedServiceNames()) + .anyMatch("com.sun.star.text.TextRanges"::equals)) { + // initialSelection does not support TextRanges. + // We need to change it (and the viewCursor with it). + XTextRange newSelection = doc.getText().getStart(); + UnoSelection.select(doc, newSelection); + viewCursor = UnoCursor.getViewCursor(doc).orElse(null); + } + + if (viewCursor == null) { + restore(doc, initialPosition, initialSelection); + String errorMessage = "Could not get the view cursor"; + return OOResult.error(errorMessage); + } + + try { + viewCursor.getStart(); + } catch (com.sun.star.uno.RuntimeException ex) { + restore(doc, initialPosition, initialSelection); + String errorMessage = "The view cursor failed the functionality test"; + return OOResult.error(errorMessage); + } + + return OOResult.ok(new FunctionalTextViewCursor(initialPosition, initialSelection, viewCursor)); + } + + public XTextViewCursor getViewCursor() { + return viewCursor; + } + + private static void restore(XTextDocument doc, + XTextRange initialPosition, + XServiceInfo initialSelection) { + + if (initialPosition != null) { + XTextViewCursor viewCursor = UnoCursor.getViewCursor(doc).orElse(null); + if (viewCursor != null) { + viewCursor.gotoRange(initialPosition, false); + return; + } + } + if (initialSelection != null) { + UnoSelection.select(doc, initialSelection); + } + } + + /* + * Restore initial state of viewCursor (possibly by restoring + * selection) if possible. + */ + public void restore(XTextDocument doc) { + FunctionalTextViewCursor.restore(doc, initialPosition, initialSelection); + } +} diff --git a/src/main/java/org/jabref/model/openoffice/rangesort/RangeKeyedMap.java b/src/main/java/org/jabref/model/openoffice/rangesort/RangeKeyedMap.java new file mode 100644 index 00000000000..094705f5fa8 --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/rangesort/RangeKeyedMap.java @@ -0,0 +1,90 @@ +package org.jabref.model.openoffice.rangesort; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.TreeMap; + +import com.sun.star.text.XText; +import com.sun.star.text.XTextRange; +import com.sun.star.text.XTextRangeCompare; +import com.sun.star.uno.UnoRuntime; + +/** + * Purpose: in order to check overlaps of XTextRange values, sort + * them, and allow recovering some corresponding information + * (of type V) + * + * Since XTextRange values are only comparable if they share the same + * range.getText(), we group them by these. + * + * Within such groups (partitions) we may define comparison, here + * based on (range.getStart(),range.getEnd()), where equality means identical + * ranges. + * + * For finding overlapping ranges this class proved insufficient, + * beacause it does not allow multiple values to be associated with a + * single XTextRange. The class RangeKeyedMapList solves this. + * + */ +public class RangeKeyedMap { + + private final Map> partitions; + + public RangeKeyedMap() { + this.partitions = new HashMap<>(); + } + + public boolean containsKey(XTextRange range) { + Objects.requireNonNull(range); + XText partitionKey = range.getText(); + if (!partitions.containsKey(partitionKey)) { + return false; + } + return partitions.get(partitionKey).containsKey(range); + } + + public V get(XTextRange range) { + TreeMap partition = partitions.get(range.getText()); + if (partition == null) { + return null; + } + return partition.get(range); + } + + /* + * Same as UnoTextRange.compareStartsThenEnds in logic. + */ + private static int comparator(XTextRange a, XTextRange b) { + if (a.getText() != b.getText()) { + throw new RuntimeException("comparator: got incomparable regions"); + } + + final XTextRangeCompare compare = + UnoRuntime.queryInterface(XTextRangeCompare.class, a.getText()); + + int cmpStart = (-1) * compare.compareRegionStarts(a, b); + if (cmpStart != 0) { + return cmpStart; + } + return (-1) * compare.compareRegionEnds(a, b); + } + + public V put(XTextRange range, V value) { + TreeMap partition = partitions.get(range.getText()); + if (partition == null) { + partition = new TreeMap<>(RangeKeyedMap::comparator); + partitions.put(range.getText(), partition); + } + return partition.put(range, value); + } + + /** + * @return A list of the partitions. + */ + public List> partitionValues() { + return new ArrayList<>(partitions.values()); + } +} diff --git a/src/main/java/org/jabref/model/openoffice/rangesort/RangeKeyedMapList.java b/src/main/java/org/jabref/model/openoffice/rangesort/RangeKeyedMapList.java new file mode 100644 index 00000000000..9e6edd69738 --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/rangesort/RangeKeyedMapList.java @@ -0,0 +1,49 @@ +package org.jabref.model.openoffice.rangesort; + +import java.util.ArrayList; +import java.util.List; +import java.util.TreeMap; + +import com.sun.star.text.XTextRange; + +/* + * Partition by XTextRange.getText() and sort within the partitions a + * set of XTextRange values, while keeping their associated data + * recoverable. Allows identical XTextRange values, their data is + * collected in a list. + */ +public class RangeKeyedMapList { + + private RangeKeyedMap> partitions; + + public RangeKeyedMapList() { + this.partitions = new RangeKeyedMap<>(); + } + + public boolean containsKey(XTextRange range) { + return partitions.containsKey(range); + } + + public List get(XTextRange range) { + return partitions.get(range); + } + + public void add(XTextRange range, V value) { + List values = partitions.get(range); + if (values == null) { + values = new ArrayList<>(); + values.add(value); + partitions.put(range, values); + } else { + values.add(value); + } + } + + /** + * @return A list of the partitions. + */ + public List>> partitionValues() { + return this.partitions.partitionValues(); + } + +} diff --git a/src/main/java/org/jabref/model/openoffice/rangesort/RangeOverlap.java b/src/main/java/org/jabref/model/openoffice/rangesort/RangeOverlap.java new file mode 100644 index 00000000000..3edd059d7ff --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/rangesort/RangeOverlap.java @@ -0,0 +1,16 @@ +package org.jabref.model.openoffice.rangesort; + +import java.util.List; + +/** + * Used in reporting range overlaps. + */ +public class RangeOverlap { + public final RangeOverlapKind kind; + public final List valuesForOverlappingRanges; + + public RangeOverlap(RangeOverlapKind kind, List valuesForOverlappingRanges) { + this.kind = kind; + this.valuesForOverlappingRanges = valuesForOverlappingRanges; + } +} diff --git a/src/main/java/org/jabref/model/openoffice/rangesort/RangeOverlapFinder.java b/src/main/java/org/jabref/model/openoffice/rangesort/RangeOverlapFinder.java new file mode 100644 index 00000000000..c5b74a0b42d --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/rangesort/RangeOverlapFinder.java @@ -0,0 +1,61 @@ +package org.jabref.model.openoffice.rangesort; + +import java.util.ArrayList; +import java.util.List; +import java.util.TreeMap; + +import org.jabref.model.openoffice.uno.UnoTextRange; + +import com.sun.star.text.XTextRange; + +public class RangeOverlapFinder { + /** + * Report identical, overlapping or touching ranges. + * + * For overlapping and touching, only report consecutive ranges + * and only with a single sample of otherwise identical ranges. + * + * @param atMost Limit the number of records returned to atMost. + * Zero or negative {@code atMost} means no limit. + * + * @param includeTouching Should the result contain ranges + * sharing only a boundary? + */ + public static List> findOverlappingRanges(RangeKeyedMapList input, + int atMost, + boolean includeTouching) { + List> result = new ArrayList<>(); + for (TreeMap> partition : input.partitionValues()) { + List orderedRanges = new ArrayList<>(partition.keySet()); + for (int i = 0; i < orderedRanges.size(); i++) { + XTextRange aRange = orderedRanges.get(i); + List aValues = partition.get(aRange); + if (aValues.size() > 1) { + result.add(new RangeOverlap(RangeOverlapKind.EQUAL_RANGE, aValues)); + if (atMost > 0 && result.size() >= atMost) { + return result; + } + } + if ((i + 1) < orderedRanges.size()) { + XTextRange bRange = orderedRanges.get(i + 1); + int cmp = UnoTextRange.compareStarts(aRange.getEnd(), bRange.getStart()); + if (cmp > 0 || (includeTouching && (cmp == 0))) { + // found overlap or touch + List bValues = partition.get(bRange); + List valuesForOverlappingRanges = new ArrayList<>(); + valuesForOverlappingRanges.add(aValues.get(0)); + valuesForOverlappingRanges.add(bValues.get(0)); + result.add(new RangeOverlap((cmp == 0) + ? RangeOverlapKind.TOUCH + : RangeOverlapKind.OVERLAP, + valuesForOverlappingRanges)); + } + if (atMost > 0 && result.size() >= atMost) { + return result; + } + } + } + } + return result; + } +} diff --git a/src/main/java/org/jabref/model/openoffice/rangesort/RangeOverlapKind.java b/src/main/java/org/jabref/model/openoffice/rangesort/RangeOverlapKind.java new file mode 100644 index 00000000000..2bb7f8f4af7 --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/rangesort/RangeOverlapKind.java @@ -0,0 +1,14 @@ +package org.jabref.model.openoffice.rangesort; + +public enum RangeOverlapKind { + + /** The ranges share a boundary */ + TOUCH, + + /** They share some characters */ + OVERLAP, + + /** They cover the same XTextRange */ + EQUAL_RANGE +} + diff --git a/src/main/java/org/jabref/model/openoffice/rangesort/RangeSortEntry.java b/src/main/java/org/jabref/model/openoffice/rangesort/RangeSortEntry.java new file mode 100644 index 00000000000..4ed651e5396 --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/rangesort/RangeSortEntry.java @@ -0,0 +1,42 @@ +package org.jabref.model.openoffice.rangesort; + +import com.sun.star.text.XTextRange; + +/** + * A simple implementation of {@code RangeSortable} + */ +public class RangeSortEntry implements RangeSortable { + + private XTextRange range; + private int indexInPosition; + private T content; + + public RangeSortEntry(XTextRange range, int indexInPosition, T content) { + this.range = range; + this.indexInPosition = indexInPosition; + this.content = content; + } + + @Override + public XTextRange getRange() { + return range; + } + + @Override + public int getIndexInPosition() { + return indexInPosition; + } + + @Override + public T getContent() { + return content; + } + + public void setRange(XTextRange r) { + range = r; + } + + public void setIndexInPosition(int i) { + indexInPosition = i; + } +} diff --git a/src/main/java/org/jabref/model/openoffice/rangesort/RangeSortVisual.java b/src/main/java/org/jabref/model/openoffice/rangesort/RangeSortVisual.java new file mode 100644 index 00000000000..67b1fa0471d --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/rangesort/RangeSortVisual.java @@ -0,0 +1,182 @@ +package org.jabref.model.openoffice.rangesort; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.TreeSet; + +import org.jabref.model.openoffice.uno.NoDocumentException; +import org.jabref.model.openoffice.uno.UnoScreenRefresh; + +import com.sun.star.awt.Point; +import com.sun.star.lang.WrappedTargetException; +import com.sun.star.text.XTextDocument; +import com.sun.star.text.XTextRange; +import com.sun.star.text.XTextViewCursor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Sort XTextRange values visually (top-down,left-to-right). + * + * Requires functional XTextViewCursor. + * + * Problem: for multicolumn layout and when viewing pages side-by-side + * in LO, the (top-down,left-to-right) order interpreted + * as-on-the-screen: an XTextRange at the top of the second + * column or second page is sorted before one at the bottom + * of the first column of the first page. + */ +public class RangeSortVisual { + + private static final Logger LOGGER = LoggerFactory.getLogger(RangeSortVisual.class); + + /** + * Sort the input {@code inputs} visually. + * + * Requires a functional {@code XTextViewCursor}. + * + * @return The input, sorted by the elements XTextRange and + * getIndexInPosition. + */ + public static List> visualSort(List> inputs, + XTextDocument doc, + FunctionalTextViewCursor fcursor) + throws + WrappedTargetException, + NoDocumentException { + + final int inputSize = inputs.size(); + + if (UnoScreenRefresh.hasControllersLocked(doc)) { + LOGGER.warn("visualSort:" + + " with ControllersLocked, viewCursor.gotoRange" + + " is probably useless"); + } + + XTextViewCursor viewCursor = fcursor.getViewCursor(); + + // find coordinates + List positions = new ArrayList<>(inputSize); + + for (RangeSortable v : inputs) { + positions.add(findPositionOfTextRange(v.getRange(), + viewCursor)); + } + + fcursor.restore(doc); + + if (positions.size() != inputSize) { + throw new RuntimeException("visualSort: positions.size() != inputSize"); + } + + // order by position + Set>> set = new TreeSet<>(); + for (int i = 0; i < inputSize; i++) { + set.add(new ComparableMark<>(positions.get(i), + inputs.get(i).getIndexInPosition(), + inputs.get(i))); + } + + if (set.size() != inputSize) { + throw new RuntimeException("visualSort: set.size() != inputSize"); + } + + // collect ordered result + List> result = new ArrayList<>(set.size()); + for (ComparableMark> mark : set) { + result.add(mark.getContent()); + } + + if (result.size() != inputSize) { + throw new RuntimeException("visualSort: result.size() != inputSize"); + } + + return result; + } + + /** + * Given a location, return its position: coordinates relative to + * the top left position of the first page of the document. + * + * Note: for text layouts with two or more columns, this gives the + * wrong order: top-down/left-to-right does not match + * reading order. + * + * Note: The "relative to the top left position of the first page" + * is meant "as it appears on the screen". + * + * In particular: when viewing pages side-by-side, the top + * half of the right page is higher than the lower half of + * the left page. Again, top-down/left-to-right does not + * match reading order. + * + * @param range Location. + * @param cursor To get the position, we need az XTextViewCursor. + * It will be moved to the range. + */ + private static Point findPositionOfTextRange(XTextRange range, XTextViewCursor cursor) { + cursor.gotoRange(range, false); + return cursor.getPosition(); + } + + /** + * A reference mark name paired with its visual position. + * + * Comparison is based on (Y,X,indexInPosition): vertical compared + * first, horizontal second, indexInPosition third. + * + * Used for sorting reference marks by their visual positions. + */ + private static class ComparableMark implements Comparable> { + + private final Point position; + private final int indexInPosition; + private final T content; + + public ComparableMark(Point position, int indexInPosition, T content) { + this.position = position; + this.indexInPosition = indexInPosition; + this.content = content; + } + + @Override + public int compareTo(ComparableMark other) { + + if (position.Y != other.position.Y) { + return position.Y - other.position.Y; + } + if (position.X != other.position.X) { + return position.X - other.position.X; + } + return indexInPosition - other.indexInPosition; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (o instanceof ComparableMark) { + ComparableMark other = (ComparableMark) o; + return ((this.position.X == other.position.X) + && (this.position.Y == other.position.Y) + && (this.indexInPosition == other.indexInPosition) + && Objects.equals(this.content, other.content)); + } + return false; + } + + public T getContent() { + return content; + } + + @Override + public int hashCode() { + return Objects.hash(position, indexInPosition, content); + } + } + +} diff --git a/src/main/java/org/jabref/model/openoffice/rangesort/RangeSortable.java b/src/main/java/org/jabref/model/openoffice/rangesort/RangeSortable.java new file mode 100644 index 00000000000..39a16b4ecb2 --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/rangesort/RangeSortable.java @@ -0,0 +1,24 @@ +package org.jabref.model.openoffice.rangesort; + +import com.sun.star.text.XTextRange; + +/** + * This is what {@code visualSort} needs in its input. + */ +public interface RangeSortable { + + /** The XTextRange + * + * For citation marks in footnotes this may be the range of the + * footnote mark. + */ + public XTextRange getRange(); + + /** + * For citation marks in footnotes this may provide order within + * the footnote. + */ + public int getIndexInPosition(); + + public T getContent(); +} From 04345791b7193eb6f1437b1fe11cb399cffccbb0 Mon Sep 17 00:00:00 2001 From: Antal K Date: Thu, 3 Jun 2021 10:26:08 +0200 Subject: [PATCH 08/51] add compareStartsUnsafe, compareStartsThenEndsUnsafe --- .../model/openoffice/uno/UnoTextRange.java | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/jabref/model/openoffice/uno/UnoTextRange.java b/src/main/java/org/jabref/model/openoffice/uno/UnoTextRange.java index 1559c55d490..b44ed6ce38b 100644 --- a/src/main/java/org/jabref/model/openoffice/uno/UnoTextRange.java +++ b/src/main/java/org/jabref/model/openoffice/uno/UnoTextRange.java @@ -40,12 +40,16 @@ public static boolean comparables(XTextRange a, XTextRange b) { * * 1 if (a > b); (-1) if (a < b) */ + public static int compareStartsUnsafe(XTextRangeCompare compare, XTextRange a, XTextRange b) { + return (-1) * compare.compareRegionStarts(a, b); + } + public static int compareStarts(XTextRange a, XTextRange b) { if (!comparables(a, b)) { throw new RuntimeException("compareStarts: got incomparable regions"); } final XTextRangeCompare compare = UnoCast.unoQI(XTextRangeCompare.class, a.getText()); - return (-1) * compare.compareRegionStarts(a, b); + return compareStartsUnsafe(compare, a, b); } /** @@ -61,16 +65,22 @@ public static int compareEnds(XTextRange a, XTextRange b) { return (-1) * compare.compareRegionEnds(a, b); } + /* + * Assumes a and b belong to the same XText as compare. + */ + public static int compareStartsThenEndsUnsafe(XTextRangeCompare compare, XTextRange a, XTextRange b) { + int res = compare.compareRegionStarts(a, b); + if (res != 0) { + return (-1) * res; + } + return (-1) * compare.compareRegionEnds(a, b); + } + public static int compareStartsThenEnds(XTextRange a, XTextRange b) { if (!comparables(a, b)) { throw new RuntimeException("compareStartsThenEnds: got incomparable regions"); } final XTextRangeCompare compare = UnoCast.unoQI(XTextRangeCompare.class, a.getText()); - int res = (-1) * compare.compareRegionStarts(a, b); - if (res != 0) { - return res; - } - return (-1) * compare.compareRegionEnds(a, b); + return compareStartsThenEndsUnsafe(compare, a, b); } - } From 3c45a8a162de5e30707e14c89b86520f5d46e43e Mon Sep 17 00:00:00 2001 From: Antal K Date: Thu, 3 Jun 2021 10:26:37 +0200 Subject: [PATCH 09/51] add Tuple3 --- .../org/jabref/model/openoffice/util/OOTuple3.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 src/main/java/org/jabref/model/openoffice/util/OOTuple3.java diff --git a/src/main/java/org/jabref/model/openoffice/util/OOTuple3.java b/src/main/java/org/jabref/model/openoffice/util/OOTuple3.java new file mode 100644 index 00000000000..ca324ebc4ad --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/util/OOTuple3.java @@ -0,0 +1,13 @@ +package org.jabref.model.openoffice.util; + +public class OOTuple3 { + public final A a; + public final B b; + public final C c; + public OOTuple3(A a, B b, C c) { + this.a = a; + this.b = b; + this.c = c; + } +} + From 32f7a43e43a0565c26ea719cabee20ebac6b838a Mon Sep 17 00:00:00 2001 From: Antal K Date: Fri, 28 May 2021 09:38:30 +0200 Subject: [PATCH 10/51] add ootext --- .../model/openoffice/ootext/OOFormat.java | 73 ++ .../model/openoffice/ootext/OOText.java | 62 ++ .../model/openoffice/ootext/OOTextIntoOO.java | 817 ++++++++++++++++++ 3 files changed, 952 insertions(+) create mode 100644 src/main/java/org/jabref/model/openoffice/ootext/OOFormat.java create mode 100644 src/main/java/org/jabref/model/openoffice/ootext/OOText.java create mode 100644 src/main/java/org/jabref/model/openoffice/ootext/OOTextIntoOO.java diff --git a/src/main/java/org/jabref/model/openoffice/ootext/OOFormat.java b/src/main/java/org/jabref/model/openoffice/ootext/OOFormat.java new file mode 100644 index 00000000000..472ca519161 --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/ootext/OOFormat.java @@ -0,0 +1,73 @@ +package org.jabref.model.openoffice.ootext; + +public class OOFormat { + + /** + * Mark {@code s} as using a character locale known to OO. + * + * @param locale language[-country[-territory]] + * + * https://www.openoffice.org/api/docs/common/ref/com/sun/star/lang/Locale.html + * + * The country part is optional. + * + * The territory part is not only optional, the allowed "codes are + * vendor and browser-specific", so probably best to avoid them if possible. + * + */ + public static OOText setLocale(OOText s, String locale) { + return OOText.fromString(String.format("", locale) + s.asString() + ""); + } + + /** + * Mark {@code s} as using the character locale "zxx", which means + * "no language", "no linguistic content". + * + * Used around citation marks, probably to turn off spellchecking. + * + */ + public static OOText setLocaleNone(OOText s) { + return OOFormat.setLocale(s, "zxx"); + } + + /** + * Mark {@code s} using a character style {@code charStyle} + * + * @param charStyle Name of a character style known to OO. May be + * empty for "Standard", which in turn means do not override any properties. + * + */ + public static OOText setCharStyle(OOText s, String charStyle) { + return OOText.fromString(String.format("", charStyle) + + s.asString() + + ""); + } + + /** + * Mark {@code s} as part of a paragraph with style {@code paraStyle} + */ + public static OOText paragraph(OOText s, String paraStyle) { + if (paraStyle == null || "".equals(paraStyle)) { + return paragraph(s); + } + String startTag = String.format("

", paraStyle); + return OOText.fromString(startTag + s.asString() + "

"); + } + + /** + * Mark {@code s} as part of a paragraph. + */ + public static OOText paragraph(OOText s) { + return OOText.fromString("

" + s.asString() + "

"); + } + + /** + * Format an OO cross-reference showing the target's page number + * as label to a reference mark. + */ + public static OOText formatReferenceToPageNumberOfReferenceMark(String referencMarkName) { + String s = String.format("", + referencMarkName); + return OOText.fromString(s); + } + } diff --git a/src/main/java/org/jabref/model/openoffice/ootext/OOText.java b/src/main/java/org/jabref/model/openoffice/ootext/OOText.java new file mode 100644 index 00000000000..a568a7c6e8a --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/ootext/OOText.java @@ -0,0 +1,62 @@ +package org.jabref.model.openoffice.ootext; + +import java.util.Objects; + +/** + * Text with HTML-like markup as understood by OOTextIntoOO.write + * + * Some of the tags can be added using OOFormat methods. Others come + * from the layout engine, either by interpreting LaTeX markup or from + * settings in the jstyle file. + */ +public class OOText { + + private final String data; + + private OOText(String data) { + Objects.requireNonNull(data); + this.data = data; + } + + /* null input is passed through */ + public static OOText fromString(String s) { + if (s == null) { + return null; + } + return new OOText(s); + } + + /* null input is passed through */ + public static String toString(OOText s) { + if (s == null) { + return null; + } + return s.data; + } + + public String asString() { + return data; + } + + /* Object.equals */ + @Override + public boolean equals(Object o) { + + if (o == this) { + return true; + } + + if (!(o instanceof OOText)) { + return false; + } + + OOText c = (OOText) o; + + return data.equals(c.data); + } + + @Override + public int hashCode() { + return data.hashCode(); + } +} diff --git a/src/main/java/org/jabref/model/openoffice/ootext/OOTextIntoOO.java b/src/main/java/org/jabref/model/openoffice/ootext/OOTextIntoOO.java new file mode 100644 index 00000000000..f062bed7c40 --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/ootext/OOTextIntoOO.java @@ -0,0 +1,817 @@ +package org.jabref.model.openoffice.ootext; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.Stack; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.jabref.architecture.AllowedToUseAwt; +import org.jabref.model.openoffice.uno.CreationException; +import org.jabref.model.openoffice.uno.UnoCast; +import org.jabref.model.openoffice.uno.UnoCrossRef; +import org.jabref.model.openoffice.util.OOPair; + +import com.sun.star.awt.FontSlant; +import com.sun.star.awt.FontStrikeout; +import com.sun.star.awt.FontUnderline; +import com.sun.star.awt.FontWeight; +import com.sun.star.beans.Property; +import com.sun.star.beans.PropertyAttribute; +import com.sun.star.beans.PropertyState; +import com.sun.star.beans.PropertyVetoException; +import com.sun.star.beans.UnknownPropertyException; +import com.sun.star.beans.XMultiPropertySet; +import com.sun.star.beans.XMultiPropertyStates; +import com.sun.star.beans.XPropertySet; +import com.sun.star.beans.XPropertySetInfo; +import com.sun.star.beans.XPropertyState; +import com.sun.star.container.NoSuchElementException; +import com.sun.star.lang.Locale; +import com.sun.star.lang.WrappedTargetException; +import com.sun.star.style.CaseMap; +import com.sun.star.text.ControlCharacter; +import com.sun.star.text.XParagraphCursor; +import com.sun.star.text.XText; +import com.sun.star.text.XTextCursor; +import com.sun.star.text.XTextDocument; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Interpret OOText into an OpenOffice or LibreOffice writer + * document. + */ +@AllowedToUseAwt("Requires AWT for changing document properties") +public class OOTextIntoOO { + + private static final Logger LOGGER = LoggerFactory.getLogger(OOTextIntoOO.class); + + /** + * "ParaStyleName" is an OpenOffice Property name. + */ + private static final String PARA_STYLE_NAME = "ParaStyleName"; + + /* + * Character property names used in multiple locations below. + */ + private static final String CHAR_ESCAPEMENT_HEIGHT = "CharEscapementHeight"; + private static final String CHAR_ESCAPEMENT = "CharEscapement"; + private static final String CHAR_STYLE_NAME = "CharStyleName"; + private static final String CHAR_UNDERLINE = "CharUnderline"; + private static final String CHAR_STRIKEOUT = "CharStrikeout"; + + /* + * SUPERSCRIPT_VALUE and SUPERSCRIPT_HEIGHT are percents of the normal character height + */ + private static final short CHAR_ESCAPEMENT_VALUE_DEFAULT = (short) 0; + private static final short SUPERSCRIPT_VALUE = (short) 33; + private static final short SUBSCRIPT_VALUE = (short) -10; + private static final byte CHAR_ESCAPEMENT_HEIGHT_DEFAULT = (byte) 100; + private static final byte SUPERSCRIPT_HEIGHT = (byte) 58; + private static final byte SUBSCRIPT_HEIGHT = (byte) 58; + + private static final String TAG_NAME_REGEXP = + "(?:b|i|em|tt|smallcaps|sup|sub|u|s|p|span|oo:referenceToPageNumberOfReferenceMark)"; + + private static final String ATTRIBUTE_NAME_REGEXP = + "(?:oo:ParaStyleName|oo:CharStyleName|lang|style|target)"; + + private static final String ATTRIBUTE_VALUE_REGEXP = "\"([^\"]*)\""; + + private static final Pattern HTML_TAG = + Pattern.compile("<(/" + TAG_NAME_REGEXP + ")>" + + "|" + + "<(" + TAG_NAME_REGEXP + ")" + + "((?:\\s+(" + ATTRIBUTE_NAME_REGEXP + ")=" + ATTRIBUTE_VALUE_REGEXP + ")*)" + + ">"); + + private static final Pattern ATTRIBUTE_PATTERN = + Pattern.compile("\\s+(" + ATTRIBUTE_NAME_REGEXP + ")=" + ATTRIBUTE_VALUE_REGEXP); + + private OOTextIntoOO() { + // Hide the public constructor + } + + /** + * Insert a text with formatting indicated by HTML-like tags, into + * a text at the position given by a cursor. + * + * Limitation: understands no entities. It does not receive any either, unless + * the user provides it. + * + * To limit the damage {@code TAG_NAME_REGEXP} and {@code ATTRIBUTE_NAME_REGEXP} + * explicitly lists the names we care about. + * + * Notable changes w.r.t insertOOFormattedTextAtCurrentLocation: + * + * - new tags: + * + * - <span lang="zxx"> + * - earlier was applied from code + * + * - <span oo:CharStyleName="CharStylename"> + * - earlier was applied from code, for "CitationCharacterFormat" + * + * - <p> start new paragraph + * - earlier was applied from code + * + * - <p oo:ParaStyleName="ParStyleName"> : start new paragraph and apply ParStyleName + * - earlier was applied from code + * + * - <tt> + * - earlier: known, but ignored + * - now: equivalent to <span oo:CharStyleName="Example"> + * - <oo:referenceToPageNumberOfReferenceMark> (self-closing) + * + * - closing tags try to properly restore state (in particular, the "not directly set" state) + * instead of dictating an "off" state. This makes a difference when the value inherited from + * another level (for example the paragraph) is not the "off" state. + * + * An example: a style with + * ReferenceParagraphFormat="JR_bibentry" + * Assume JR_bibentry in LibreOffice is a paragraph style that prescribes "bold" font. + * LAYOUT only prescribes bold around year. + * Which parts of the bibliography entries should come out as bold? + * + * - The user can format citation marks (it is enough to format their start) and the + * properties not (everywhere) dictated by the style are preserved (where they are not). + * + * @param position The cursor giving the insert location. Not modified. + * @param ootext The marked-up text to insert. + */ + public static void write(XTextDocument doc, XTextCursor position, OOText ootext) + throws + UnknownPropertyException, + PropertyVetoException, + WrappedTargetException, + IllegalArgumentException, + NoSuchElementException, + CreationException { + + final boolean debugThisFun = false; + + Objects.requireNonNull(doc); + Objects.requireNonNull(ootext); + Objects.requireNonNull(position); + + String lText = OOText.toString(ootext); + + if (debugThisFun) { + System.out.println(lText); + } + + XText text = position.getText(); + XTextCursor cursor = text.createTextCursorByRange(position); + cursor.collapseToEnd(); + + MyPropertyStack formatStack = new MyPropertyStack(cursor); + Stack expectEnd = new Stack<>(); + + // We need to extract formatting. Use a simple regexp search iteration: + int piv = 0; + Matcher m = HTML_TAG.matcher(lText); + while (m.find()) { + + String currentSubstring = lText.substring(piv, m.start()); + if (!currentSubstring.isEmpty()) { + cursor.setString(currentSubstring); + } + formatStack.apply(cursor); + cursor.collapseToEnd(); + + String fullTag = m.group(); + String endTagName = m.group(1); + String startTagName = m.group(2); + String attributeListPart = m.group(3); + boolean isStartTag = (endTagName == null) || "".equals(endTagName); + String tagName = isStartTag ? startTagName : endTagName; + Objects.requireNonNull(tagName); + + // Attibutes parsed into (name,value) pairs. + List> attributes = parseAttributes(attributeListPart); + + // Handle tags: + switch (tagName) { + case "b": + formatStack.pushLayer(setCharWeight(FontWeight.BOLD)); + expectEnd.push("/" + tagName); + break; + case "i": + case "em": + formatStack.pushLayer(setCharPosture(FontSlant.ITALIC)); + expectEnd.push("/" + tagName); + break; + case "smallcaps": + formatStack.pushLayer(setCharCaseMap(CaseMap.SMALLCAPS)); + expectEnd.push("/" + tagName); + break; + case "sup": + formatStack.pushLayer(setSuperScript(formatStack)); + expectEnd.push("/" + tagName); + break; + case "sub": + formatStack.pushLayer(setSubScript(formatStack)); + expectEnd.push("/" + tagName); + break; + case "u": + formatStack.pushLayer(setCharUnderline(FontUnderline.SINGLE)); + expectEnd.push("/" + tagName); + break; + case "s": + formatStack.pushLayer(setCharStrikeout(FontStrikeout.SINGLE)); + expectEnd.push("/" + tagName); + break; + case "/p": + // nop + break; + case "p": + insertParagraphBreak(text, cursor); + cursor.collapseToEnd(); + for (OOPair pair : attributes) { + String key = pair.a; + String value = pair.b; + switch (key) { + case "oo:ParaStyleName": + //

+ if (value != null && !value.equals("")) { + if (setParagraphStyle(cursor, value)) { + if (debugThisFun) { + // Presumably tested already: + LOGGER.warn(String.format("oo:ParaStyleName=\"%s\" failed", value)); + } + } + } else { + if (debugThisFun) { + LOGGER.warn(String.format("oo:ParaStyleName inherited")); + } + } + break; + default: + LOGGER.warn(String.format("Unexpected attribute '%s' for <%s>", key, tagName)); + break; + } + } + break; + case "oo:referenceToPageNumberOfReferenceMark": + for (OOPair pair : attributes) { + String key = pair.a; + String value = pair.b; + switch (key) { + case "target": + UnoCrossRef.insertReferenceToPageNumberOfReferenceMark(doc, value, cursor); + break; + default: + LOGGER.warn(String.format("Unexpected attribute '%s' for <%s>", key, tagName)); + break; + } + } + break; + case "tt": + // Note: "Example" names a character style in LibreOffice. + formatStack.pushLayer(setCharStyleName("Example")); + expectEnd.push("/" + tagName); + break; + case "span": + List> settings = new ArrayList<>(); + for (OOPair pair : attributes) { + String key = pair.a; + String value = pair.b; + switch (key) { + case "oo:CharStyleName": + // + settings.addAll(setCharStyleName(value)); + break; + case "lang": + // + // + settings.addAll(setCharLocale(value)); + break; + case "style": + // HTML-style small-caps + if (value.equals("font-variant: small-caps")) { + settings.addAll(setCharCaseMap(CaseMap.SMALLCAPS)); + break; + } + LOGGER.warn(String.format("Unexpected value %s for attribute '%s' for <%s>", + value, key, tagName)); + break; + default: + LOGGER.warn(String.format("Unexpected attribute '%s' for <%s>", key, tagName)); + break; + } + } + formatStack.pushLayer(settings); + expectEnd.push("/" + tagName); + break; + case "/b": + case "/i": + case "/em": + case "/tt": + case "/smallcaps": + case "/sup": + case "/sub": + case "/u": + case "/s": + case "/span": + formatStack.popLayer(); + String expected = expectEnd.pop(); + if (!tagName.equals(expected)) { + LOGGER.warn(String.format("expected '<%s>', found '<%s>' after '%s'", + expected, + tagName, + currentSubstring)); + } + break; + } + + piv = m.end(); + } + + if (piv < lText.length()) { + cursor.setString(lText.substring(piv)); + } + formatStack.apply(cursor); + cursor.collapseToEnd(); + + if (!expectEnd.empty()) { + String rest = ""; + for (String s : expectEnd) { + rest = String.format("<%s>", s) + rest; + } + LOGGER.warn(String.format("OOTextIntoOO.write:" + + " expectEnd stack is not empty at the end: %s%n", + rest)); + } + } + + /** + * Purpose: in some cases we do not want to inherit direct + * formatting from the context. + * + * In particular, when filling the bibliography title and body. + */ + public static void removeDirectFormatting(XTextCursor cursor) { + + XMultiPropertyStates mpss = UnoCast.unoQI(XMultiPropertyStates.class, cursor); + + XPropertySet propertySet = UnoCast.unoQI(XPropertySet.class, cursor); + XPropertyState xPropertyState = UnoCast.unoQI(XPropertyState.class, cursor); + + try { + // Special handling + propertySet.setPropertyValue(CHAR_STYLE_NAME, "Standard"); + xPropertyState.setPropertyToDefault("CharCaseMap"); + } catch (UnknownPropertyException | + PropertyVetoException | + IllegalArgumentException | + WrappedTargetException ex) { + LOGGER.warn("exception caught", ex); + } + + mpss.setAllPropertiesToDefault(); + + /* + * Now that we have called setAllPropertiesToDefault, check which properties + * are not set to default and try to correct what we can and seem necessary. + * + * Note: tested with LibreOffice : 6.4.6.2 + */ + + // Only report those we do not yet know about + final Set knownToFail = Set.of("ListAutoFormat", + "ListId", + "NumberingIsNumber", + "NumberingLevel", + "NumberingRules", + "NumberingStartValue", + "ParaChapterNumberingLevel", + "ParaIsNumberingRestart", + "ParaStyleName"); + + // query again, just in case it matters + propertySet = UnoCast.unoQI(XPropertySet.class, cursor); + XPropertySetInfo propertySetInfo = propertySet.getPropertySetInfo(); + + // check the result + for (Property p : propertySetInfo.getProperties()) { + if ((p.Attributes & PropertyAttribute.READONLY) != 0) { + continue; + } + try { + if (isPropertyDefault(cursor, p.Name)) { + continue; + } + } catch (UnknownPropertyException ex) { + throw new RuntimeException("Unexpected UnknownPropertyException"); + } + if (knownToFail.contains(p.Name)) { + continue; + } + LOGGER.warn(String.format("OOTextIntoOO.removeDirectFormatting failed on '%s'", p.Name)); + } + } + + static class MyPropertyStack { + + /* + * We only try to control these. Should include all character + * properties we set, and maybe their interdependencies. + * + * For a list of properties see: + * https://www.openoffice.org/api/docs/common/ref/com/sun/star/style/CharacterProperties.html + * + * For interdependencies between properties: + * https://wiki.openoffice.org/wiki/Documentation/DevGuide/Text/Formatting + * (at the end, under "Interdependencies between Properties") + * + */ + static final Set CONTROLLED_PROPERTIES = Set.of( + + /* Used for SuperScript, SubScript. + * + * These three are interdependent: changing one may change others. + */ + "CharEscapement", "CharEscapementHeight", "CharAutoEscapement", + + /* used for Bold */ + "CharWeight", + + /* Used for Italic */ + "CharPosture", + + /* Used for strikeout. These two are interdependent. */ + "CharStrikeout", "CharCrossedOut", + + /* Used for underline. These three are interdependent, but apparently + * we can leave out the last two. + */ + "CharUnderline", // "CharUnderlineColor", "CharUnderlineHasColor", + + /* Used for lang="zxx", to silence spellchecker. */ + "CharLocale", + + /* Used for CitationCharacterFormat. */ + "CharStyleName", + + /* Used for and */ + "CharCaseMap"); + + /** + * The number of properties actually controlled. + */ + final int goodSize; + + /** + * From property name to index in goodNames. + */ + final Map goodNameToIndex; + + /** + * From index to property name. + */ + final String[] goodNames; + + /** + * Maintain a stack of layers, each containing a description + * of the desired state of properties. Each description is an + * ArrayList of property values, Optional.empty() encoding + * "not directly set". + */ + final Stack>> layers; + + MyPropertyStack(XTextCursor cursor) + throws UnknownPropertyException { + + XPropertySet propertySet = UnoCast.unoQI(XPropertySet.class, cursor); + XPropertySetInfo propertySetInfo = propertySet.getPropertySetInfo(); + + /* + * On creation, initialize the property name -- index mapping. + */ + this.goodNameToIndex = new HashMap<>(); + int nextIndex = 0; + for (Property p : propertySetInfo.getProperties()) { + if ((p.Attributes & PropertyAttribute.READONLY) != 0) { + continue; + } + if (!CONTROLLED_PROPERTIES.contains(p.Name)) { + continue; + } + this.goodNameToIndex.put(p.Name, nextIndex); + nextIndex++; + } + + this.goodSize = nextIndex; + + this.goodNames = new String[goodSize]; + for (Map.Entry entry : goodNameToIndex.entrySet()) { + goodNames[ entry.getValue() ] = entry.getKey(); + } + + // XMultiPropertySet.setPropertyValues() + // requires alphabetically sorted property names. + // We adjust here: + Arrays.sort(goodNames); + for (int i = 0; i < goodSize; i++) { + this.goodNameToIndex.put(goodNames[i], i); + } + + /* + * Get the initial state of the properties and add add the first layer. + */ + XMultiPropertyStates mpss = UnoCast.unoQI(XMultiPropertyStates.class, cursor); + PropertyState[] propertyStates = mpss.getPropertyStates(goodNames); + + XMultiPropertySet mps = UnoCast.unoQI(XMultiPropertySet.class, cursor); + Object[] initialValues = mps.getPropertyValues(goodNames); + + ArrayList> initialValuesOpt = new ArrayList<>(goodSize); + + for (int i = 0; i < goodSize; i++) { + if (propertyStates[i] == PropertyState.DIRECT_VALUE) { + initialValuesOpt.add(Optional.of(initialValues[i])); + } else { + initialValuesOpt.add(Optional.empty()); + } + } + + this.layers = new Stack<>(); + this.layers.push(initialValuesOpt); + } + + /** + * Given a list of property name, property value pairs, + * construct and push a new layer describing the intended + * state after these have been applied. + * + * Opening tags usually call this. + */ + void pushLayer(List> settings) { + ArrayList> oldLayer = layers.peek(); + ArrayList> newLayer = new ArrayList<>(oldLayer); + for (OOPair pair : settings) { + String name = pair.a; + Integer i = goodNameToIndex.get(name); + if (i == null) { + LOGGER.warn(String.format("pushLayer: '%s' is not in goodNameToIndex", name)); + continue; + } + Object newValue = pair.b; + newLayer.set(i, Optional.ofNullable(newValue)); + } + layers.push(newLayer); + } + + /** + * Closing tags just pop a layer. + */ + void popLayer() { + if (layers.size() <= 1) { + LOGGER.warn("popLayer: underflow"); + return; + } + layers.pop(); + } + + /** + * Apply the current desired formatting state to a cursor. + * The idea is to minimize the number of calls to OpenOffice. + */ + void apply(XTextCursor cursor) { + XMultiPropertySet mps = UnoCast.unoQI(XMultiPropertySet.class, cursor); + XMultiPropertyStates mpss = UnoCast.unoQI(XMultiPropertyStates.class, cursor); + ArrayList> topLayer = layers.peek(); + try { + // select values to be set + ArrayList names = new ArrayList<>(goodSize); + ArrayList values = new ArrayList<>(goodSize); + // and those to be cleared + ArrayList delNames = new ArrayList<>(goodSize); + for (int i = 0; i < goodSize; i++) { + if (topLayer.get(i).isPresent()) { + names.add(goodNames[i]); + values.add(topLayer.get(i).get()); + } else { + delNames.add(goodNames[i]); + } + } + // namesArray must be alphabetically sorted. + String[] namesArray = names.toArray(new String[names.size()]); + String[] delNamesArray = delNames.toArray(new String[names.size()]); + mpss.setPropertiesToDefault(delNamesArray); + mps.setPropertyValues(namesArray, values.toArray()); + } catch (UnknownPropertyException ex) { + LOGGER.warn("UnknownPropertyException in MyPropertyStack.apply"); + } catch (PropertyVetoException ex) { + LOGGER.warn("PropertyVetoException in MyPropertyStack.apply"); + } catch (IllegalArgumentException ex) { + LOGGER.warn("IllegalArgumentException in MyPropertyStack.apply"); + } catch (WrappedTargetException ex) { + LOGGER.warn("WrappedTargetException in MyPropertyStack.apply"); + } + } + + // Relative CharEscapement needs to know current values. + Optional getPropertyValue(String name) { + if (goodNameToIndex.containsKey(name)) { + int i = goodNameToIndex.get(name); + ArrayList> topLayer = layers.peek(); + Optional value = topLayer.get(i); + return value; + } + return Optional.empty(); + } + } + + /** + * Parse HTML-like attributes to a list of (name,value) pairs. + */ + private static List> parseAttributes(String s) { + List> res = new ArrayList<>(); + if (s == null) { + return res; + } + Matcher m = ATTRIBUTE_PATTERN.matcher(s); + while (m.find()) { + String key = m.group(1); + String value = m.group(2); + res.add(new OOPair(key, value)); + } + return res; + } + + /* + * We rely on property values being either DIRECT_VALUE or + * DEFAULT_VALUE (not AMBIGUOUS_VALUE). If the cursor covers a homogeneous region, + * or is collapsed, then this is true. + */ + private static boolean isPropertyDefault(XTextCursor cursor, String propertyName) + throws + UnknownPropertyException { + XPropertyState xPropertyState = UnoCast.unoQI(XPropertyState.class, cursor); + PropertyState state = xPropertyState.getPropertyState(propertyName); + if (state == PropertyState.AMBIGUOUS_VALUE) { + throw new RuntimeException("PropertyState.AMBIGUOUS_VALUE" + + " (expected properties for a homogeneous cursor)"); + } + return state == PropertyState.DEFAULT_VALUE; + } + + /* + * Various property change requests. Their results are passed to MyPropertyStack.pushLayer() + */ + + private static List> setCharWeight(float value) { + List> settings = new ArrayList<>(); + settings.add(new OOPair<>("CharWeight", (Float) value)); + return settings; + } + + private static List> setCharPosture(FontSlant value) { + List> settings = new ArrayList<>(); + settings.add(new OOPair<>("CharPosture", (Object) value)); + return settings; + } + + private static List> setCharCaseMap(short value) { + List> settings = new ArrayList<>(); + settings.add(new OOPair<>("CharCaseMap", (Short) value)); + return settings; + } + + // com.sun.star.awt.FontUnderline + private static List> setCharUnderline(short value) { + List> settings = new ArrayList<>(); + settings.add(new OOPair<>(CHAR_UNDERLINE, (Short) value)); + return settings; + } + + // com.sun.star.awt.FontStrikeout + private static List> setCharStrikeout(short value) { + List> settings = new ArrayList<>(); + settings.add(new OOPair<>(CHAR_STRIKEOUT, (Short) value)); + return settings; + } + + // CharStyleName + private static List> setCharStyleName(String value) { + List> settings = new ArrayList<>(); + if (value != null && value != "") { + settings.add(new OOPair<>(CHAR_STYLE_NAME, value)); + } else { + LOGGER.warn("setCharStyleName: received null or empty value"); + } + return settings; + } + + // Locale + private static List> setCharLocale(Locale value) { + List> settings = new ArrayList<>(); + settings.add(new OOPair<>("CharLocale", (Object) value)); + return settings; + } + + /** + * Locale from string encoding: language, language-country or language-country-variant + */ + private static List> setCharLocale(String value) { + if (value == null || "".equals(value)) { + throw new RuntimeException("setCharLocale \"\" or null"); + } + String[] parts = value.split("-"); + String language = (parts.length > 0) ? parts[0] : ""; + String country = (parts.length > 1) ? parts[1] : ""; + String variant = (parts.length > 2) ? parts[2] : ""; + return setCharLocale(new Locale(language, country, variant)); + } + + /* + * SuperScript and SubScript. + * + * @param relative If true, calculate the new values relative to + * the current values. This allows subscript-in-superscript. + */ + private static List> setCharEscapement(Optional value, + Optional height, + boolean relative, + MyPropertyStack formatStack) { + List> settings = new ArrayList<>(); + Optional oldValue = (formatStack + .getPropertyValue(CHAR_ESCAPEMENT) + .map(e -> (short) e)); + + Optional oldHeight = (formatStack + .getPropertyValue(CHAR_ESCAPEMENT_HEIGHT) + .map(e -> (byte) e)); + + if (relative && (value.isPresent() || height.isPresent())) { + double oldHeightFloat = oldHeight.orElse(CHAR_ESCAPEMENT_HEIGHT_DEFAULT) * 0.01; + double oldValueFloat = oldValue.orElse(CHAR_ESCAPEMENT_VALUE_DEFAULT); + double heightFloat = height.orElse(CHAR_ESCAPEMENT_HEIGHT_DEFAULT); + double valueFloat = value.orElse(CHAR_ESCAPEMENT_VALUE_DEFAULT); + byte newHeight = (byte) Math.round(heightFloat * oldHeightFloat); + short newValue = (short) Math.round(valueFloat * oldHeightFloat + oldValueFloat); + if (value.isPresent()) { + settings.add(new OOPair<>(CHAR_ESCAPEMENT, (Short) newValue)); + } + if (height.isPresent()) { + settings.add(new OOPair<>(CHAR_ESCAPEMENT_HEIGHT, (Byte) newHeight)); + } + } else { + if (value.isPresent()) { + settings.add(new OOPair<>(CHAR_ESCAPEMENT, (Short) value.get())); + } + if (height.isPresent()) { + settings.add(new OOPair<>(CHAR_ESCAPEMENT_HEIGHT, (Byte) height.get())); + } + } + return settings; + } + + private static List> setSubScript(MyPropertyStack formatStack) { + return setCharEscapement(Optional.of(SUBSCRIPT_VALUE), + Optional.of(SUBSCRIPT_HEIGHT), + true, + formatStack); + } + + private static List> setSuperScript(MyPropertyStack formatStack) { + return setCharEscapement(Optional.of(SUPERSCRIPT_VALUE), + Optional.of(SUPERSCRIPT_HEIGHT), + true, + formatStack); + } + + /* + * @return true on failure + */ + public static boolean setParagraphStyle(XTextCursor cursor, String paragraphStyle) { + final boolean FAIL = true; + final boolean PASS = false; + + XParagraphCursor paragraphCursor = UnoCast.unoQI(XParagraphCursor.class, cursor); + XPropertySet propertySet = UnoCast.unoQI(XPropertySet.class, paragraphCursor); + try { + propertySet.setPropertyValue(PARA_STYLE_NAME, paragraphStyle); + return PASS; + } catch (UnknownPropertyException + | PropertyVetoException + | IllegalArgumentException + | WrappedTargetException ex) { + return FAIL; + } + } + + private static void insertParagraphBreak(XText text, XTextCursor cursor) + throws IllegalArgumentException { + text.insertControlCharacter(cursor, ControlCharacter.PARAGRAPH_BREAK, true); + } + +} From 6bae6b469f2d8d8b3ebe3d49eecb06ea19f15729 Mon Sep 17 00:00:00 2001 From: Antal K Date: Fri, 28 May 2021 11:11:26 +0200 Subject: [PATCH 11/51] add rangesort --- .../rangesort/FunctionalTextViewCursor.java | 159 +++++++++++++++ .../openoffice/rangesort/RangeKeyedMap.java | 90 +++++++++ .../rangesort/RangeKeyedMapList.java | 49 +++++ .../openoffice/rangesort/RangeOverlap.java | 16 ++ .../rangesort/RangeOverlapFinder.java | 61 ++++++ .../rangesort/RangeOverlapKind.java | 14 ++ .../openoffice/rangesort/RangeSortEntry.java | 42 ++++ .../openoffice/rangesort/RangeSortVisual.java | 182 ++++++++++++++++++ .../openoffice/rangesort/RangeSortable.java | 24 +++ 9 files changed, 637 insertions(+) create mode 100644 src/main/java/org/jabref/model/openoffice/rangesort/FunctionalTextViewCursor.java create mode 100644 src/main/java/org/jabref/model/openoffice/rangesort/RangeKeyedMap.java create mode 100644 src/main/java/org/jabref/model/openoffice/rangesort/RangeKeyedMapList.java create mode 100644 src/main/java/org/jabref/model/openoffice/rangesort/RangeOverlap.java create mode 100644 src/main/java/org/jabref/model/openoffice/rangesort/RangeOverlapFinder.java create mode 100644 src/main/java/org/jabref/model/openoffice/rangesort/RangeOverlapKind.java create mode 100644 src/main/java/org/jabref/model/openoffice/rangesort/RangeSortEntry.java create mode 100644 src/main/java/org/jabref/model/openoffice/rangesort/RangeSortVisual.java create mode 100644 src/main/java/org/jabref/model/openoffice/rangesort/RangeSortable.java diff --git a/src/main/java/org/jabref/model/openoffice/rangesort/FunctionalTextViewCursor.java b/src/main/java/org/jabref/model/openoffice/rangesort/FunctionalTextViewCursor.java new file mode 100644 index 00000000000..3f780274b0f --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/rangesort/FunctionalTextViewCursor.java @@ -0,0 +1,159 @@ +package org.jabref.model.openoffice.rangesort; + +import java.util.Arrays; +import java.util.Objects; + +import org.jabref.model.openoffice.uno.UnoCursor; +import org.jabref.model.openoffice.uno.UnoSelection; +import org.jabref.model.openoffice.util.OOResult; + +import com.sun.star.lang.XServiceInfo; +import com.sun.star.text.XTextDocument; +import com.sun.star.text.XTextRange; +import com.sun.star.text.XTextViewCursor; + +/* + * A problem with XTextViewCursor: if it is not in text, then we get a + * crippled version that does not support viewCursor.getStart() or + * viewCursor.gotoRange(range,false), and will throw an exception + * instead. + * + * Here we manipulate the cursor via XSelectionSupplier.getSelection and + * XSelectionSupplier.select to move it to the text. + * + * Seems to work when the user selected a frame or image. + * In these cases restoring the selection works, too. + * + * When the cursor is in a comment (referred to as "annotation" in OO + * API) then initialSelection is null, and select() fails to + * get a functional viewCursor. + * + * If FunctionalTextViewCursor.get() reports error, we have to ask the + * user to move the cursor into the text part of the document. + * + * Usage: + * + * OOResult fcursor = FunctionalTextViewCursor.get(doc, msg); + * if (fcursor.isError()) { + * ... + * } else { + * XTextViewCursor viewCursor = fcursor.get().getViewCursor(); + * ... + * fc.restore(); + * } + * + */ +public class FunctionalTextViewCursor { + + /* + * The initial position of the cursor or null. + */ + private XTextRange initialPosition; + + /* + * The initial selection in the document or null. + */ + private XServiceInfo initialSelection; + + /* + * The view cursor, potentially moved from its original location. + */ + private XTextViewCursor viewCursor; + + private FunctionalTextViewCursor(XTextRange initialPosition, + XServiceInfo initialSelection, + XTextViewCursor viewCursor) { + this.initialPosition = initialPosition; + this.initialSelection = initialSelection; + this.viewCursor = viewCursor; + } + + /* + * Get a functional XTextViewCursor or an error message. + * + * The cursor position may differ from the location + * provided by the user. + * + * On failure the constructor restores the selection. On success, + * the caller may want to call instance.restore() after finished + * using the cursor. + */ + public static OOResult get(XTextDocument doc) { + + Objects.requireNonNull(doc); + + XTextRange initialPosition = null; + XServiceInfo initialSelection = UnoSelection.getSelectionAsXServiceInfo(doc).orElse(null); + XTextViewCursor viewCursor = UnoCursor.getViewCursor(doc).orElse(null); + if (viewCursor != null) { + try { + initialPosition = UnoCursor.createTextCursorByRange(viewCursor); + viewCursor.getStart(); + return OOResult.ok(new FunctionalTextViewCursor(initialPosition, + initialSelection, + viewCursor)); + } catch (com.sun.star.uno.RuntimeException ex) { + // bad cursor + viewCursor = null; + initialPosition = null; + } + } + + if (initialSelection == null) { + String errorMessage = ("Selection is not available:" + + " cannot provide a functional view cursor"); + return OOResult.error(errorMessage); + } else if (!Arrays.stream(initialSelection.getSupportedServiceNames()) + .anyMatch("com.sun.star.text.TextRanges"::equals)) { + // initialSelection does not support TextRanges. + // We need to change it (and the viewCursor with it). + XTextRange newSelection = doc.getText().getStart(); + UnoSelection.select(doc, newSelection); + viewCursor = UnoCursor.getViewCursor(doc).orElse(null); + } + + if (viewCursor == null) { + restore(doc, initialPosition, initialSelection); + String errorMessage = "Could not get the view cursor"; + return OOResult.error(errorMessage); + } + + try { + viewCursor.getStart(); + } catch (com.sun.star.uno.RuntimeException ex) { + restore(doc, initialPosition, initialSelection); + String errorMessage = "The view cursor failed the functionality test"; + return OOResult.error(errorMessage); + } + + return OOResult.ok(new FunctionalTextViewCursor(initialPosition, initialSelection, viewCursor)); + } + + public XTextViewCursor getViewCursor() { + return viewCursor; + } + + private static void restore(XTextDocument doc, + XTextRange initialPosition, + XServiceInfo initialSelection) { + + if (initialPosition != null) { + XTextViewCursor viewCursor = UnoCursor.getViewCursor(doc).orElse(null); + if (viewCursor != null) { + viewCursor.gotoRange(initialPosition, false); + return; + } + } + if (initialSelection != null) { + UnoSelection.select(doc, initialSelection); + } + } + + /* + * Restore initial state of viewCursor (possibly by restoring + * selection) if possible. + */ + public void restore(XTextDocument doc) { + FunctionalTextViewCursor.restore(doc, initialPosition, initialSelection); + } +} diff --git a/src/main/java/org/jabref/model/openoffice/rangesort/RangeKeyedMap.java b/src/main/java/org/jabref/model/openoffice/rangesort/RangeKeyedMap.java new file mode 100644 index 00000000000..094705f5fa8 --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/rangesort/RangeKeyedMap.java @@ -0,0 +1,90 @@ +package org.jabref.model.openoffice.rangesort; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.TreeMap; + +import com.sun.star.text.XText; +import com.sun.star.text.XTextRange; +import com.sun.star.text.XTextRangeCompare; +import com.sun.star.uno.UnoRuntime; + +/** + * Purpose: in order to check overlaps of XTextRange values, sort + * them, and allow recovering some corresponding information + * (of type V) + * + * Since XTextRange values are only comparable if they share the same + * range.getText(), we group them by these. + * + * Within such groups (partitions) we may define comparison, here + * based on (range.getStart(),range.getEnd()), where equality means identical + * ranges. + * + * For finding overlapping ranges this class proved insufficient, + * beacause it does not allow multiple values to be associated with a + * single XTextRange. The class RangeKeyedMapList solves this. + * + */ +public class RangeKeyedMap { + + private final Map> partitions; + + public RangeKeyedMap() { + this.partitions = new HashMap<>(); + } + + public boolean containsKey(XTextRange range) { + Objects.requireNonNull(range); + XText partitionKey = range.getText(); + if (!partitions.containsKey(partitionKey)) { + return false; + } + return partitions.get(partitionKey).containsKey(range); + } + + public V get(XTextRange range) { + TreeMap partition = partitions.get(range.getText()); + if (partition == null) { + return null; + } + return partition.get(range); + } + + /* + * Same as UnoTextRange.compareStartsThenEnds in logic. + */ + private static int comparator(XTextRange a, XTextRange b) { + if (a.getText() != b.getText()) { + throw new RuntimeException("comparator: got incomparable regions"); + } + + final XTextRangeCompare compare = + UnoRuntime.queryInterface(XTextRangeCompare.class, a.getText()); + + int cmpStart = (-1) * compare.compareRegionStarts(a, b); + if (cmpStart != 0) { + return cmpStart; + } + return (-1) * compare.compareRegionEnds(a, b); + } + + public V put(XTextRange range, V value) { + TreeMap partition = partitions.get(range.getText()); + if (partition == null) { + partition = new TreeMap<>(RangeKeyedMap::comparator); + partitions.put(range.getText(), partition); + } + return partition.put(range, value); + } + + /** + * @return A list of the partitions. + */ + public List> partitionValues() { + return new ArrayList<>(partitions.values()); + } +} diff --git a/src/main/java/org/jabref/model/openoffice/rangesort/RangeKeyedMapList.java b/src/main/java/org/jabref/model/openoffice/rangesort/RangeKeyedMapList.java new file mode 100644 index 00000000000..9e6edd69738 --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/rangesort/RangeKeyedMapList.java @@ -0,0 +1,49 @@ +package org.jabref.model.openoffice.rangesort; + +import java.util.ArrayList; +import java.util.List; +import java.util.TreeMap; + +import com.sun.star.text.XTextRange; + +/* + * Partition by XTextRange.getText() and sort within the partitions a + * set of XTextRange values, while keeping their associated data + * recoverable. Allows identical XTextRange values, their data is + * collected in a list. + */ +public class RangeKeyedMapList { + + private RangeKeyedMap> partitions; + + public RangeKeyedMapList() { + this.partitions = new RangeKeyedMap<>(); + } + + public boolean containsKey(XTextRange range) { + return partitions.containsKey(range); + } + + public List get(XTextRange range) { + return partitions.get(range); + } + + public void add(XTextRange range, V value) { + List values = partitions.get(range); + if (values == null) { + values = new ArrayList<>(); + values.add(value); + partitions.put(range, values); + } else { + values.add(value); + } + } + + /** + * @return A list of the partitions. + */ + public List>> partitionValues() { + return this.partitions.partitionValues(); + } + +} diff --git a/src/main/java/org/jabref/model/openoffice/rangesort/RangeOverlap.java b/src/main/java/org/jabref/model/openoffice/rangesort/RangeOverlap.java new file mode 100644 index 00000000000..3edd059d7ff --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/rangesort/RangeOverlap.java @@ -0,0 +1,16 @@ +package org.jabref.model.openoffice.rangesort; + +import java.util.List; + +/** + * Used in reporting range overlaps. + */ +public class RangeOverlap { + public final RangeOverlapKind kind; + public final List valuesForOverlappingRanges; + + public RangeOverlap(RangeOverlapKind kind, List valuesForOverlappingRanges) { + this.kind = kind; + this.valuesForOverlappingRanges = valuesForOverlappingRanges; + } +} diff --git a/src/main/java/org/jabref/model/openoffice/rangesort/RangeOverlapFinder.java b/src/main/java/org/jabref/model/openoffice/rangesort/RangeOverlapFinder.java new file mode 100644 index 00000000000..c5b74a0b42d --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/rangesort/RangeOverlapFinder.java @@ -0,0 +1,61 @@ +package org.jabref.model.openoffice.rangesort; + +import java.util.ArrayList; +import java.util.List; +import java.util.TreeMap; + +import org.jabref.model.openoffice.uno.UnoTextRange; + +import com.sun.star.text.XTextRange; + +public class RangeOverlapFinder { + /** + * Report identical, overlapping or touching ranges. + * + * For overlapping and touching, only report consecutive ranges + * and only with a single sample of otherwise identical ranges. + * + * @param atMost Limit the number of records returned to atMost. + * Zero or negative {@code atMost} means no limit. + * + * @param includeTouching Should the result contain ranges + * sharing only a boundary? + */ + public static List> findOverlappingRanges(RangeKeyedMapList input, + int atMost, + boolean includeTouching) { + List> result = new ArrayList<>(); + for (TreeMap> partition : input.partitionValues()) { + List orderedRanges = new ArrayList<>(partition.keySet()); + for (int i = 0; i < orderedRanges.size(); i++) { + XTextRange aRange = orderedRanges.get(i); + List aValues = partition.get(aRange); + if (aValues.size() > 1) { + result.add(new RangeOverlap(RangeOverlapKind.EQUAL_RANGE, aValues)); + if (atMost > 0 && result.size() >= atMost) { + return result; + } + } + if ((i + 1) < orderedRanges.size()) { + XTextRange bRange = orderedRanges.get(i + 1); + int cmp = UnoTextRange.compareStarts(aRange.getEnd(), bRange.getStart()); + if (cmp > 0 || (includeTouching && (cmp == 0))) { + // found overlap or touch + List bValues = partition.get(bRange); + List valuesForOverlappingRanges = new ArrayList<>(); + valuesForOverlappingRanges.add(aValues.get(0)); + valuesForOverlappingRanges.add(bValues.get(0)); + result.add(new RangeOverlap((cmp == 0) + ? RangeOverlapKind.TOUCH + : RangeOverlapKind.OVERLAP, + valuesForOverlappingRanges)); + } + if (atMost > 0 && result.size() >= atMost) { + return result; + } + } + } + } + return result; + } +} diff --git a/src/main/java/org/jabref/model/openoffice/rangesort/RangeOverlapKind.java b/src/main/java/org/jabref/model/openoffice/rangesort/RangeOverlapKind.java new file mode 100644 index 00000000000..2bb7f8f4af7 --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/rangesort/RangeOverlapKind.java @@ -0,0 +1,14 @@ +package org.jabref.model.openoffice.rangesort; + +public enum RangeOverlapKind { + + /** The ranges share a boundary */ + TOUCH, + + /** They share some characters */ + OVERLAP, + + /** They cover the same XTextRange */ + EQUAL_RANGE +} + diff --git a/src/main/java/org/jabref/model/openoffice/rangesort/RangeSortEntry.java b/src/main/java/org/jabref/model/openoffice/rangesort/RangeSortEntry.java new file mode 100644 index 00000000000..4ed651e5396 --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/rangesort/RangeSortEntry.java @@ -0,0 +1,42 @@ +package org.jabref.model.openoffice.rangesort; + +import com.sun.star.text.XTextRange; + +/** + * A simple implementation of {@code RangeSortable} + */ +public class RangeSortEntry implements RangeSortable { + + private XTextRange range; + private int indexInPosition; + private T content; + + public RangeSortEntry(XTextRange range, int indexInPosition, T content) { + this.range = range; + this.indexInPosition = indexInPosition; + this.content = content; + } + + @Override + public XTextRange getRange() { + return range; + } + + @Override + public int getIndexInPosition() { + return indexInPosition; + } + + @Override + public T getContent() { + return content; + } + + public void setRange(XTextRange r) { + range = r; + } + + public void setIndexInPosition(int i) { + indexInPosition = i; + } +} diff --git a/src/main/java/org/jabref/model/openoffice/rangesort/RangeSortVisual.java b/src/main/java/org/jabref/model/openoffice/rangesort/RangeSortVisual.java new file mode 100644 index 00000000000..67b1fa0471d --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/rangesort/RangeSortVisual.java @@ -0,0 +1,182 @@ +package org.jabref.model.openoffice.rangesort; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.TreeSet; + +import org.jabref.model.openoffice.uno.NoDocumentException; +import org.jabref.model.openoffice.uno.UnoScreenRefresh; + +import com.sun.star.awt.Point; +import com.sun.star.lang.WrappedTargetException; +import com.sun.star.text.XTextDocument; +import com.sun.star.text.XTextRange; +import com.sun.star.text.XTextViewCursor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Sort XTextRange values visually (top-down,left-to-right). + * + * Requires functional XTextViewCursor. + * + * Problem: for multicolumn layout and when viewing pages side-by-side + * in LO, the (top-down,left-to-right) order interpreted + * as-on-the-screen: an XTextRange at the top of the second + * column or second page is sorted before one at the bottom + * of the first column of the first page. + */ +public class RangeSortVisual { + + private static final Logger LOGGER = LoggerFactory.getLogger(RangeSortVisual.class); + + /** + * Sort the input {@code inputs} visually. + * + * Requires a functional {@code XTextViewCursor}. + * + * @return The input, sorted by the elements XTextRange and + * getIndexInPosition. + */ + public static List> visualSort(List> inputs, + XTextDocument doc, + FunctionalTextViewCursor fcursor) + throws + WrappedTargetException, + NoDocumentException { + + final int inputSize = inputs.size(); + + if (UnoScreenRefresh.hasControllersLocked(doc)) { + LOGGER.warn("visualSort:" + + " with ControllersLocked, viewCursor.gotoRange" + + " is probably useless"); + } + + XTextViewCursor viewCursor = fcursor.getViewCursor(); + + // find coordinates + List positions = new ArrayList<>(inputSize); + + for (RangeSortable v : inputs) { + positions.add(findPositionOfTextRange(v.getRange(), + viewCursor)); + } + + fcursor.restore(doc); + + if (positions.size() != inputSize) { + throw new RuntimeException("visualSort: positions.size() != inputSize"); + } + + // order by position + Set>> set = new TreeSet<>(); + for (int i = 0; i < inputSize; i++) { + set.add(new ComparableMark<>(positions.get(i), + inputs.get(i).getIndexInPosition(), + inputs.get(i))); + } + + if (set.size() != inputSize) { + throw new RuntimeException("visualSort: set.size() != inputSize"); + } + + // collect ordered result + List> result = new ArrayList<>(set.size()); + for (ComparableMark> mark : set) { + result.add(mark.getContent()); + } + + if (result.size() != inputSize) { + throw new RuntimeException("visualSort: result.size() != inputSize"); + } + + return result; + } + + /** + * Given a location, return its position: coordinates relative to + * the top left position of the first page of the document. + * + * Note: for text layouts with two or more columns, this gives the + * wrong order: top-down/left-to-right does not match + * reading order. + * + * Note: The "relative to the top left position of the first page" + * is meant "as it appears on the screen". + * + * In particular: when viewing pages side-by-side, the top + * half of the right page is higher than the lower half of + * the left page. Again, top-down/left-to-right does not + * match reading order. + * + * @param range Location. + * @param cursor To get the position, we need az XTextViewCursor. + * It will be moved to the range. + */ + private static Point findPositionOfTextRange(XTextRange range, XTextViewCursor cursor) { + cursor.gotoRange(range, false); + return cursor.getPosition(); + } + + /** + * A reference mark name paired with its visual position. + * + * Comparison is based on (Y,X,indexInPosition): vertical compared + * first, horizontal second, indexInPosition third. + * + * Used for sorting reference marks by their visual positions. + */ + private static class ComparableMark implements Comparable> { + + private final Point position; + private final int indexInPosition; + private final T content; + + public ComparableMark(Point position, int indexInPosition, T content) { + this.position = position; + this.indexInPosition = indexInPosition; + this.content = content; + } + + @Override + public int compareTo(ComparableMark other) { + + if (position.Y != other.position.Y) { + return position.Y - other.position.Y; + } + if (position.X != other.position.X) { + return position.X - other.position.X; + } + return indexInPosition - other.indexInPosition; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (o instanceof ComparableMark) { + ComparableMark other = (ComparableMark) o; + return ((this.position.X == other.position.X) + && (this.position.Y == other.position.Y) + && (this.indexInPosition == other.indexInPosition) + && Objects.equals(this.content, other.content)); + } + return false; + } + + public T getContent() { + return content; + } + + @Override + public int hashCode() { + return Objects.hash(position, indexInPosition, content); + } + } + +} diff --git a/src/main/java/org/jabref/model/openoffice/rangesort/RangeSortable.java b/src/main/java/org/jabref/model/openoffice/rangesort/RangeSortable.java new file mode 100644 index 00000000000..39a16b4ecb2 --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/rangesort/RangeSortable.java @@ -0,0 +1,24 @@ +package org.jabref.model.openoffice.rangesort; + +import com.sun.star.text.XTextRange; + +/** + * This is what {@code visualSort} needs in its input. + */ +public interface RangeSortable { + + /** The XTextRange + * + * For citation marks in footnotes this may be the range of the + * footnote mark. + */ + public XTextRange getRange(); + + /** + * For citation marks in footnotes this may provide order within + * the footnote. + */ + public int getIndexInPosition(); + + public T getContent(); +} From 314902cc8720f5228d08a5c0fe21369e1150f377 Mon Sep 17 00:00:00 2001 From: Antal K Date: Thu, 3 Jun 2021 10:33:20 +0200 Subject: [PATCH 12/51] delNamesArray size correction --- .../java/org/jabref/model/openoffice/ootext/OOTextIntoOO.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/jabref/model/openoffice/ootext/OOTextIntoOO.java b/src/main/java/org/jabref/model/openoffice/ootext/OOTextIntoOO.java index f062bed7c40..298329f3a43 100644 --- a/src/main/java/org/jabref/model/openoffice/ootext/OOTextIntoOO.java +++ b/src/main/java/org/jabref/model/openoffice/ootext/OOTextIntoOO.java @@ -604,7 +604,7 @@ void apply(XTextCursor cursor) { } // namesArray must be alphabetically sorted. String[] namesArray = names.toArray(new String[names.size()]); - String[] delNamesArray = delNames.toArray(new String[names.size()]); + String[] delNamesArray = delNames.toArray(new String[delNames.size()]); mpss.setPropertiesToDefault(delNamesArray); mps.setPropertyValues(namesArray, values.toArray()); } catch (UnknownPropertyException ex) { From dfe18bc1149bd8b67d0791de228f385172e7d415 Mon Sep 17 00:00:00 2001 From: Antal K Date: Thu, 3 Jun 2021 10:34:14 +0200 Subject: [PATCH 13/51] rangeSort update --- .../openoffice/rangesort/RangeHolder.java | 7 + .../openoffice/rangesort/RangeKeyedMap.java | 90 ------------- .../rangesort/RangeKeyedMapList.java | 49 ------- .../rangesort/RangeOverlapBetween.java | 91 +++++++++++++ .../rangesort/RangeOverlapFinder.java | 61 --------- .../rangesort/RangeOverlapWithin.java | 120 ++++++++++++++++++ .../model/openoffice/rangesort/RangeSet.java | 48 +++++++ .../model/openoffice/rangesort/RangeSort.java | 90 +++++++++++++ .../openoffice/rangesort/RangeSortVisual.java | 16 ++- .../openoffice/rangesort/RangeSortable.java | 2 +- 10 files changed, 366 insertions(+), 208 deletions(-) create mode 100644 src/main/java/org/jabref/model/openoffice/rangesort/RangeHolder.java delete mode 100644 src/main/java/org/jabref/model/openoffice/rangesort/RangeKeyedMap.java delete mode 100644 src/main/java/org/jabref/model/openoffice/rangesort/RangeKeyedMapList.java create mode 100644 src/main/java/org/jabref/model/openoffice/rangesort/RangeOverlapBetween.java delete mode 100644 src/main/java/org/jabref/model/openoffice/rangesort/RangeOverlapFinder.java create mode 100644 src/main/java/org/jabref/model/openoffice/rangesort/RangeOverlapWithin.java create mode 100644 src/main/java/org/jabref/model/openoffice/rangesort/RangeSet.java create mode 100644 src/main/java/org/jabref/model/openoffice/rangesort/RangeSort.java diff --git a/src/main/java/org/jabref/model/openoffice/rangesort/RangeHolder.java b/src/main/java/org/jabref/model/openoffice/rangesort/RangeHolder.java new file mode 100644 index 00000000000..cae6f99e34e --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/rangesort/RangeHolder.java @@ -0,0 +1,7 @@ +package org.jabref.model.openoffice.rangesort; + +import com.sun.star.text.XTextRange; + +public interface RangeHolder { + XTextRange getRange(); +} diff --git a/src/main/java/org/jabref/model/openoffice/rangesort/RangeKeyedMap.java b/src/main/java/org/jabref/model/openoffice/rangesort/RangeKeyedMap.java deleted file mode 100644 index 094705f5fa8..00000000000 --- a/src/main/java/org/jabref/model/openoffice/rangesort/RangeKeyedMap.java +++ /dev/null @@ -1,90 +0,0 @@ -package org.jabref.model.openoffice.rangesort; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.TreeMap; - -import com.sun.star.text.XText; -import com.sun.star.text.XTextRange; -import com.sun.star.text.XTextRangeCompare; -import com.sun.star.uno.UnoRuntime; - -/** - * Purpose: in order to check overlaps of XTextRange values, sort - * them, and allow recovering some corresponding information - * (of type V) - * - * Since XTextRange values are only comparable if they share the same - * range.getText(), we group them by these. - * - * Within such groups (partitions) we may define comparison, here - * based on (range.getStart(),range.getEnd()), where equality means identical - * ranges. - * - * For finding overlapping ranges this class proved insufficient, - * beacause it does not allow multiple values to be associated with a - * single XTextRange. The class RangeKeyedMapList solves this. - * - */ -public class RangeKeyedMap { - - private final Map> partitions; - - public RangeKeyedMap() { - this.partitions = new HashMap<>(); - } - - public boolean containsKey(XTextRange range) { - Objects.requireNonNull(range); - XText partitionKey = range.getText(); - if (!partitions.containsKey(partitionKey)) { - return false; - } - return partitions.get(partitionKey).containsKey(range); - } - - public V get(XTextRange range) { - TreeMap partition = partitions.get(range.getText()); - if (partition == null) { - return null; - } - return partition.get(range); - } - - /* - * Same as UnoTextRange.compareStartsThenEnds in logic. - */ - private static int comparator(XTextRange a, XTextRange b) { - if (a.getText() != b.getText()) { - throw new RuntimeException("comparator: got incomparable regions"); - } - - final XTextRangeCompare compare = - UnoRuntime.queryInterface(XTextRangeCompare.class, a.getText()); - - int cmpStart = (-1) * compare.compareRegionStarts(a, b); - if (cmpStart != 0) { - return cmpStart; - } - return (-1) * compare.compareRegionEnds(a, b); - } - - public V put(XTextRange range, V value) { - TreeMap partition = partitions.get(range.getText()); - if (partition == null) { - partition = new TreeMap<>(RangeKeyedMap::comparator); - partitions.put(range.getText(), partition); - } - return partition.put(range, value); - } - - /** - * @return A list of the partitions. - */ - public List> partitionValues() { - return new ArrayList<>(partitions.values()); - } -} diff --git a/src/main/java/org/jabref/model/openoffice/rangesort/RangeKeyedMapList.java b/src/main/java/org/jabref/model/openoffice/rangesort/RangeKeyedMapList.java deleted file mode 100644 index 9e6edd69738..00000000000 --- a/src/main/java/org/jabref/model/openoffice/rangesort/RangeKeyedMapList.java +++ /dev/null @@ -1,49 +0,0 @@ -package org.jabref.model.openoffice.rangesort; - -import java.util.ArrayList; -import java.util.List; -import java.util.TreeMap; - -import com.sun.star.text.XTextRange; - -/* - * Partition by XTextRange.getText() and sort within the partitions a - * set of XTextRange values, while keeping their associated data - * recoverable. Allows identical XTextRange values, their data is - * collected in a list. - */ -public class RangeKeyedMapList { - - private RangeKeyedMap> partitions; - - public RangeKeyedMapList() { - this.partitions = new RangeKeyedMap<>(); - } - - public boolean containsKey(XTextRange range) { - return partitions.containsKey(range); - } - - public List get(XTextRange range) { - return partitions.get(range); - } - - public void add(XTextRange range, V value) { - List values = partitions.get(range); - if (values == null) { - values = new ArrayList<>(); - values.add(value); - partitions.put(range, values); - } else { - values.add(value); - } - } - - /** - * @return A list of the partitions. - */ - public List>> partitionValues() { - return this.partitions.partitionValues(); - } - -} diff --git a/src/main/java/org/jabref/model/openoffice/rangesort/RangeOverlapBetween.java b/src/main/java/org/jabref/model/openoffice/rangesort/RangeOverlapBetween.java new file mode 100644 index 00000000000..35d2d006fab --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/rangesort/RangeOverlapBetween.java @@ -0,0 +1,91 @@ +package org.jabref.model.openoffice.rangesort; + +import java.util.ArrayList; +import java.util.List; + +import org.jabref.model.openoffice.uno.UnoCast; +import org.jabref.model.openoffice.uno.UnoTextRange; +import org.jabref.model.openoffice.util.OOTuple3; + +import com.sun.star.text.XText; +import com.sun.star.text.XTextDocument; +import com.sun.star.text.XTextRange; +import com.sun.star.text.XTextRangeCompare; + +public class RangeOverlapBetween { + + private RangeOverlapBetween() { } + + /** + * Check for any overlap between two sets of XTextRange values. + * + * Assume fewHolders is small (usually 1 elements for checking the cursor) + * Returns on first problem found. + */ + public static + List> findFirst(XTextDocument doc, + List fewHolders, + List manyHolders, + boolean includeTouching) { + + List> result = new ArrayList<>(); + + if (fewHolders.size() == 0) { + return result; + } + + List> fewTuples = + new ArrayList<>(fewHolders.size()); + + for (V aHolder : fewHolders) { + XText aText = aHolder.getRange().getText(); + fewTuples.add(new OOTuple3<>(aText, + UnoCast.unoQI(XTextRangeCompare.class, aText), + aHolder)); + } + + for (V bHolder : manyHolders) { + XTextRange bRange = bHolder.getRange(); + XText bText = bRange.getText(); + XTextRange bRangeStart = bRange.getStart(); + XTextRange bRangeEnd = bRange.getEnd(); + + for (OOTuple3 tup : fewTuples) { + XText aText = tup.a; + XTextRangeCompare cmp = tup.b; + V aHolder = tup.c; + XTextRange aRange = aHolder.getRange(); + if (aText != bText) { + continue; + } + int abEndToStart = -1 * cmp.compareRegionStarts(aRange.getEnd(), bRangeStart); + if (abEndToStart < 0 || (!includeTouching && (abEndToStart == 0))) { + continue; + } + int baEndToStart = -1 * cmp.compareRegionStarts(bRangeEnd, aRange.getStart()); + if (baEndToStart < 0 || (!includeTouching && (baEndToStart == 0))) { + continue; + } + + boolean equal = UnoTextRange.compareStartsThenEnds(aRange, bRange) == 0; + boolean touching = (abEndToStart == 0 || baEndToStart == 0); + + // In case of two equal collapsed ranges there is an + // ambiguity : TOUCH or EQUAL_RANGE ? + // We return EQUAL_RANGE + RangeOverlapKind kind = + (equal ? RangeOverlapKind.EQUAL_RANGE + : (touching ? RangeOverlapKind.TOUCH + : RangeOverlapKind.OVERLAP)); + + List valuesForOverlappingRanges = new ArrayList<>(); + valuesForOverlappingRanges.add(aHolder); + valuesForOverlappingRanges.add(bHolder); + + result.add(new RangeOverlap(kind, valuesForOverlappingRanges)); + return result; + } + } + return result; + } +} diff --git a/src/main/java/org/jabref/model/openoffice/rangesort/RangeOverlapFinder.java b/src/main/java/org/jabref/model/openoffice/rangesort/RangeOverlapFinder.java deleted file mode 100644 index c5b74a0b42d..00000000000 --- a/src/main/java/org/jabref/model/openoffice/rangesort/RangeOverlapFinder.java +++ /dev/null @@ -1,61 +0,0 @@ -package org.jabref.model.openoffice.rangesort; - -import java.util.ArrayList; -import java.util.List; -import java.util.TreeMap; - -import org.jabref.model.openoffice.uno.UnoTextRange; - -import com.sun.star.text.XTextRange; - -public class RangeOverlapFinder { - /** - * Report identical, overlapping or touching ranges. - * - * For overlapping and touching, only report consecutive ranges - * and only with a single sample of otherwise identical ranges. - * - * @param atMost Limit the number of records returned to atMost. - * Zero or negative {@code atMost} means no limit. - * - * @param includeTouching Should the result contain ranges - * sharing only a boundary? - */ - public static List> findOverlappingRanges(RangeKeyedMapList input, - int atMost, - boolean includeTouching) { - List> result = new ArrayList<>(); - for (TreeMap> partition : input.partitionValues()) { - List orderedRanges = new ArrayList<>(partition.keySet()); - for (int i = 0; i < orderedRanges.size(); i++) { - XTextRange aRange = orderedRanges.get(i); - List aValues = partition.get(aRange); - if (aValues.size() > 1) { - result.add(new RangeOverlap(RangeOverlapKind.EQUAL_RANGE, aValues)); - if (atMost > 0 && result.size() >= atMost) { - return result; - } - } - if ((i + 1) < orderedRanges.size()) { - XTextRange bRange = orderedRanges.get(i + 1); - int cmp = UnoTextRange.compareStarts(aRange.getEnd(), bRange.getStart()); - if (cmp > 0 || (includeTouching && (cmp == 0))) { - // found overlap or touch - List bValues = partition.get(bRange); - List valuesForOverlappingRanges = new ArrayList<>(); - valuesForOverlappingRanges.add(aValues.get(0)); - valuesForOverlappingRanges.add(bValues.get(0)); - result.add(new RangeOverlap((cmp == 0) - ? RangeOverlapKind.TOUCH - : RangeOverlapKind.OVERLAP, - valuesForOverlappingRanges)); - } - if (atMost > 0 && result.size() >= atMost) { - return result; - } - } - } - } - return result; - } -} diff --git a/src/main/java/org/jabref/model/openoffice/rangesort/RangeOverlapWithin.java b/src/main/java/org/jabref/model/openoffice/rangesort/RangeOverlapWithin.java new file mode 100644 index 00000000000..62ee6d82ca0 --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/rangesort/RangeOverlapWithin.java @@ -0,0 +1,120 @@ +package org.jabref.model.openoffice.rangesort; + +import java.util.ArrayList; +import java.util.List; + +import org.jabref.model.openoffice.uno.UnoCast; +import org.jabref.model.openoffice.uno.UnoTextRange; + +import com.sun.star.text.XTextDocument; +import com.sun.star.text.XTextRange; +import com.sun.star.text.XTextRangeCompare; + +public class RangeOverlapWithin { + + private RangeOverlapWithin() { } + + /** + * Report identical, overlapping or touching ranges between + * elements of rangeHolders. + * + * For overlapping and touching, only report consecutive ranges + * and only with a single sample of otherwise identical ranges. + * + * @param reportAtMost Limit the number of records returned to atMost. + * Zero {@code reportAtMost} means no limit. + * + * @param includeTouching Should the result contain ranges + * sharing only a boundary? + */ + public static + List> findOverlappingRanges(XTextDocument doc, + List rangeHolders, + boolean includeTouching, + int reportAtMost) { + + RangeSort.RangePartitions partitions = RangeSort.partitionAndSortRanges(rangeHolders); + + List> overlaps = findOverlappingRanges(partitions, reportAtMost, includeTouching); + + return overlaps; + } + + /** + * Report identical, overlapping or touching ranges. + * + * For overlapping and touching, only report consecutive ranges + * and only with a single sample of otherwise identical ranges. + * + * @param atMost Limit the number of records returned to atMost. + * Zero {@code atMost} means no limit. + * + * @param includeTouching Should the result contain ranges + * sharing only a boundary? + */ + public static + List> findOverlappingRanges(RangeSort.RangePartitions input, + int atMost, + boolean includeTouching) { + assert atMost >= 0; + + List> result = new ArrayList<>(); + + for (List partition : input.getPartitions()) { + if (partition.size() == 0) { + continue; + } + XTextRangeCompare cmp = UnoCast.unoQI(XTextRangeCompare.class, + partition.get(0).getRange().getText()); + + for (int i = 0; i < (partition.size() - 1); i++) { + V aHolder = partition.get(i); + V bHolder = partition.get(i + 1); + XTextRange aRange = aHolder.getRange(); + XTextRange bRange = bHolder.getRange(); + + // check equal values + int cmpResult = UnoTextRange.compareStartsThenEndsUnsafe(cmp, aRange, bRange); + if (cmpResult == 0) { + List aValues = new ArrayList<>(); + aValues.add(aHolder); + // aValues.add(bHolder); + // collect those equal + while (i < (partition.size() - 1) && + UnoTextRange.compareStartsThenEndsUnsafe( + cmp, + aRange, + partition.get(i + 1).getRange()) == 0) { + bHolder = partition.get(i + 1); + aValues.add(bHolder); + i++; + } + result.add(new RangeOverlap(RangeOverlapKind.EQUAL_RANGE, aValues)); + if (atMost > 0 && result.size() >= atMost) { + return result; + } + continue; + } + + // Not equal, and (a <= b) since sorted. + // Check if a.end >= b.start + cmpResult = UnoTextRange.compareStartsUnsafe(cmp, aRange.getEnd(), bRange.getStart()); + if (cmpResult > 0 || (includeTouching && (cmpResult == 0))) { + // found overlap or touch + List valuesForOverlappingRanges = new ArrayList<>(); + valuesForOverlappingRanges.add(aHolder); + valuesForOverlappingRanges.add(bHolder); + result.add(new RangeOverlap((cmpResult == 0) + ? RangeOverlapKind.TOUCH + : RangeOverlapKind.OVERLAP, + valuesForOverlappingRanges)); + } + if (atMost > 0 && result.size() >= atMost) { + return result; + } + } + } + return result; + } + +} diff --git a/src/main/java/org/jabref/model/openoffice/rangesort/RangeSet.java b/src/main/java/org/jabref/model/openoffice/rangesort/RangeSet.java new file mode 100644 index 00000000000..f7fbe37ba25 --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/rangesort/RangeSet.java @@ -0,0 +1,48 @@ +package org.jabref.model.openoffice.rangesort; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.TreeSet; + +import org.jabref.model.openoffice.uno.UnoTextRange; + +import com.sun.star.text.XText; +import com.sun.star.text.XTextRange; + +public class RangeSet { + + private final Map> partitions; + + public RangeSet() { + this.partitions = new HashMap<>(); + } + + public boolean contains(XTextRange range) { + Objects.requireNonNull(range); + XText partitionKey = range.getText(); + if (!this.partitions.containsKey(partitionKey)) { + return false; + } + return partitions.get(partitionKey).contains(range); + } + + /* return false if already contained */ + public boolean add(XTextRange range) { + TreeSet partition = partitions.get(range.getText()); + if (partition == null) { + partition = new TreeSet<>(UnoTextRange::compareStartsThenEnds); + partitions.put(range.getText(), partition); + } + return partition.add(range); + } + + /** + * @return A list of the partitions. + */ + public List> partitionValues() { + return new ArrayList<>(partitions.values()); + } +} diff --git a/src/main/java/org/jabref/model/openoffice/rangesort/RangeSort.java b/src/main/java/org/jabref/model/openoffice/rangesort/RangeSort.java new file mode 100644 index 00000000000..2f5daa0e1aa --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/rangesort/RangeSort.java @@ -0,0 +1,90 @@ +package org.jabref.model.openoffice.rangesort; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.jabref.model.openoffice.uno.UnoCast; +import org.jabref.model.openoffice.uno.UnoTextRange; + +import com.sun.star.text.XText; +import com.sun.star.text.XTextRangeCompare; + +public class RangeSort { + + /* + * Sort within a partition + */ + + public static class HolderComparatorWithinPartition implements Comparator { + + XTextRangeCompare cmp; + + HolderComparatorWithinPartition(XText text) { + cmp = UnoCast.unoQI(XTextRangeCompare.class, text); + } + + /* + * Assumes a and b belong to the same XText as cmp. + */ + @Override + public int compare(RangeHolder a, RangeHolder b) { + return UnoTextRange.compareStartsThenEndsUnsafe(cmp, a.getRange(), b.getRange()); + } + } + + public static + void sortWithinPartition(List rangeHolders) { + if (rangeHolders.isEmpty()) { + return; + } + XText text = rangeHolders.get(0).getRange().getText(); + rangeHolders.sort(new HolderComparatorWithinPartition(text)); + } + + /* + * Partitioning + */ + + public static class RangePartitions { + private final Map> partitions; + + public RangePartitions() { + this.partitions = new HashMap<>(); + } + + public void add(V holder) { + XText partitionKey = holder.getRange().getText(); + List partition = partitions.get(partitionKey); + if (partition == null) { + partition = new ArrayList<>(); + partitions.put(partitionKey, partition); + } + partition.add(holder); + } + + public List> getPartitions() { + return new ArrayList<>(partitions.values()); + } + } + + public static + RangePartitions partitionRanges(List holders) { + RangePartitions result = new RangePartitions<>(); + for (V holder : holders) { + result.add(holder); + } + return result; + } + + public static + RangePartitions partitionAndSortRanges(List holders) { + RangePartitions result = partitionRanges(holders); + for (List partition : result.getPartitions()) { + sortWithinPartition(partition); + } + return result; + } +} diff --git a/src/main/java/org/jabref/model/openoffice/rangesort/RangeSortVisual.java b/src/main/java/org/jabref/model/openoffice/rangesort/RangeSortVisual.java index 67b1fa0471d..2131c3cecb9 100644 --- a/src/main/java/org/jabref/model/openoffice/rangesort/RangeSortVisual.java +++ b/src/main/java/org/jabref/model/openoffice/rangesort/RangeSortVisual.java @@ -1,10 +1,9 @@ package org.jabref.model.openoffice.rangesort; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Objects; -import java.util.Set; -import java.util.TreeSet; import org.jabref.model.openoffice.uno.NoDocumentException; import org.jabref.model.openoffice.uno.UnoScreenRefresh; @@ -53,18 +52,19 @@ public static List> visualSort(List> input LOGGER.warn("visualSort:" + " with ControllersLocked, viewCursor.gotoRange" + " is probably useless"); + throw new RuntimeException("visualSort:" + + " with ControllersLocked, viewCursor.gotoRange" + + " is probably useless"); } XTextViewCursor viewCursor = fcursor.getViewCursor(); // find coordinates List positions = new ArrayList<>(inputSize); - for (RangeSortable v : inputs) { positions.add(findPositionOfTextRange(v.getRange(), viewCursor)); } - fcursor.restore(doc); if (positions.size() != inputSize) { @@ -72,12 +72,14 @@ public static List> visualSort(List> input } // order by position - Set>> set = new TreeSet<>(); + ArrayList>> set = new ArrayList<>(inputSize); for (int i = 0; i < inputSize; i++) { + RangeSortable input = inputs.get(i); set.add(new ComparableMark<>(positions.get(i), - inputs.get(i).getIndexInPosition(), - inputs.get(i))); + input.getIndexInPosition(), + input)); } + Collections.sort(set); if (set.size() != inputSize) { throw new RuntimeException("visualSort: set.size() != inputSize"); diff --git a/src/main/java/org/jabref/model/openoffice/rangesort/RangeSortable.java b/src/main/java/org/jabref/model/openoffice/rangesort/RangeSortable.java index 39a16b4ecb2..66e17a5b35b 100644 --- a/src/main/java/org/jabref/model/openoffice/rangesort/RangeSortable.java +++ b/src/main/java/org/jabref/model/openoffice/rangesort/RangeSortable.java @@ -5,7 +5,7 @@ /** * This is what {@code visualSort} needs in its input. */ -public interface RangeSortable { +public interface RangeSortable extends RangeHolder { /** The XTextRange * From 6e4c7c299a6033816486ed76c5e492433adb4563 Mon Sep 17 00:00:00 2001 From: Antal K Date: Thu, 3 Jun 2021 11:02:12 +0200 Subject: [PATCH 14/51] cleanup --- .../openoffice/rangesort/RangeKeyedMap.java | 90 ------------------- .../rangesort/RangeKeyedMapList.java | 49 ---------- .../rangesort/RangeOverlapFinder.java | 61 ------------- 3 files changed, 200 deletions(-) delete mode 100644 src/main/java/org/jabref/model/openoffice/rangesort/RangeKeyedMap.java delete mode 100644 src/main/java/org/jabref/model/openoffice/rangesort/RangeKeyedMapList.java delete mode 100644 src/main/java/org/jabref/model/openoffice/rangesort/RangeOverlapFinder.java diff --git a/src/main/java/org/jabref/model/openoffice/rangesort/RangeKeyedMap.java b/src/main/java/org/jabref/model/openoffice/rangesort/RangeKeyedMap.java deleted file mode 100644 index 094705f5fa8..00000000000 --- a/src/main/java/org/jabref/model/openoffice/rangesort/RangeKeyedMap.java +++ /dev/null @@ -1,90 +0,0 @@ -package org.jabref.model.openoffice.rangesort; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.TreeMap; - -import com.sun.star.text.XText; -import com.sun.star.text.XTextRange; -import com.sun.star.text.XTextRangeCompare; -import com.sun.star.uno.UnoRuntime; - -/** - * Purpose: in order to check overlaps of XTextRange values, sort - * them, and allow recovering some corresponding information - * (of type V) - * - * Since XTextRange values are only comparable if they share the same - * range.getText(), we group them by these. - * - * Within such groups (partitions) we may define comparison, here - * based on (range.getStart(),range.getEnd()), where equality means identical - * ranges. - * - * For finding overlapping ranges this class proved insufficient, - * beacause it does not allow multiple values to be associated with a - * single XTextRange. The class RangeKeyedMapList solves this. - * - */ -public class RangeKeyedMap { - - private final Map> partitions; - - public RangeKeyedMap() { - this.partitions = new HashMap<>(); - } - - public boolean containsKey(XTextRange range) { - Objects.requireNonNull(range); - XText partitionKey = range.getText(); - if (!partitions.containsKey(partitionKey)) { - return false; - } - return partitions.get(partitionKey).containsKey(range); - } - - public V get(XTextRange range) { - TreeMap partition = partitions.get(range.getText()); - if (partition == null) { - return null; - } - return partition.get(range); - } - - /* - * Same as UnoTextRange.compareStartsThenEnds in logic. - */ - private static int comparator(XTextRange a, XTextRange b) { - if (a.getText() != b.getText()) { - throw new RuntimeException("comparator: got incomparable regions"); - } - - final XTextRangeCompare compare = - UnoRuntime.queryInterface(XTextRangeCompare.class, a.getText()); - - int cmpStart = (-1) * compare.compareRegionStarts(a, b); - if (cmpStart != 0) { - return cmpStart; - } - return (-1) * compare.compareRegionEnds(a, b); - } - - public V put(XTextRange range, V value) { - TreeMap partition = partitions.get(range.getText()); - if (partition == null) { - partition = new TreeMap<>(RangeKeyedMap::comparator); - partitions.put(range.getText(), partition); - } - return partition.put(range, value); - } - - /** - * @return A list of the partitions. - */ - public List> partitionValues() { - return new ArrayList<>(partitions.values()); - } -} diff --git a/src/main/java/org/jabref/model/openoffice/rangesort/RangeKeyedMapList.java b/src/main/java/org/jabref/model/openoffice/rangesort/RangeKeyedMapList.java deleted file mode 100644 index 9e6edd69738..00000000000 --- a/src/main/java/org/jabref/model/openoffice/rangesort/RangeKeyedMapList.java +++ /dev/null @@ -1,49 +0,0 @@ -package org.jabref.model.openoffice.rangesort; - -import java.util.ArrayList; -import java.util.List; -import java.util.TreeMap; - -import com.sun.star.text.XTextRange; - -/* - * Partition by XTextRange.getText() and sort within the partitions a - * set of XTextRange values, while keeping their associated data - * recoverable. Allows identical XTextRange values, their data is - * collected in a list. - */ -public class RangeKeyedMapList { - - private RangeKeyedMap> partitions; - - public RangeKeyedMapList() { - this.partitions = new RangeKeyedMap<>(); - } - - public boolean containsKey(XTextRange range) { - return partitions.containsKey(range); - } - - public List get(XTextRange range) { - return partitions.get(range); - } - - public void add(XTextRange range, V value) { - List values = partitions.get(range); - if (values == null) { - values = new ArrayList<>(); - values.add(value); - partitions.put(range, values); - } else { - values.add(value); - } - } - - /** - * @return A list of the partitions. - */ - public List>> partitionValues() { - return this.partitions.partitionValues(); - } - -} diff --git a/src/main/java/org/jabref/model/openoffice/rangesort/RangeOverlapFinder.java b/src/main/java/org/jabref/model/openoffice/rangesort/RangeOverlapFinder.java deleted file mode 100644 index c5b74a0b42d..00000000000 --- a/src/main/java/org/jabref/model/openoffice/rangesort/RangeOverlapFinder.java +++ /dev/null @@ -1,61 +0,0 @@ -package org.jabref.model.openoffice.rangesort; - -import java.util.ArrayList; -import java.util.List; -import java.util.TreeMap; - -import org.jabref.model.openoffice.uno.UnoTextRange; - -import com.sun.star.text.XTextRange; - -public class RangeOverlapFinder { - /** - * Report identical, overlapping or touching ranges. - * - * For overlapping and touching, only report consecutive ranges - * and only with a single sample of otherwise identical ranges. - * - * @param atMost Limit the number of records returned to atMost. - * Zero or negative {@code atMost} means no limit. - * - * @param includeTouching Should the result contain ranges - * sharing only a boundary? - */ - public static List> findOverlappingRanges(RangeKeyedMapList input, - int atMost, - boolean includeTouching) { - List> result = new ArrayList<>(); - for (TreeMap> partition : input.partitionValues()) { - List orderedRanges = new ArrayList<>(partition.keySet()); - for (int i = 0; i < orderedRanges.size(); i++) { - XTextRange aRange = orderedRanges.get(i); - List aValues = partition.get(aRange); - if (aValues.size() > 1) { - result.add(new RangeOverlap(RangeOverlapKind.EQUAL_RANGE, aValues)); - if (atMost > 0 && result.size() >= atMost) { - return result; - } - } - if ((i + 1) < orderedRanges.size()) { - XTextRange bRange = orderedRanges.get(i + 1); - int cmp = UnoTextRange.compareStarts(aRange.getEnd(), bRange.getStart()); - if (cmp > 0 || (includeTouching && (cmp == 0))) { - // found overlap or touch - List bValues = partition.get(bRange); - List valuesForOverlappingRanges = new ArrayList<>(); - valuesForOverlappingRanges.add(aValues.get(0)); - valuesForOverlappingRanges.add(bValues.get(0)); - result.add(new RangeOverlap((cmp == 0) - ? RangeOverlapKind.TOUCH - : RangeOverlapKind.OVERLAP, - valuesForOverlappingRanges)); - } - if (atMost > 0 && result.size() >= atMost) { - return result; - } - } - } - } - return result; - } -} From 9388b2a1e6373ef139bd1e4366f3353d2b197036 Mon Sep 17 00:00:00 2001 From: Antal K Date: Thu, 3 Jun 2021 11:21:54 +0200 Subject: [PATCH 15/51] style additions --- .../logic/openoffice/style/OOBibStyle.java | 457 ++++++++++- .../style/OOBibStyleGetCitationMarker.java | 767 ++++++++++++++++++ .../style/OOBibStyleGetNumCitationMarker.java | 278 +++++++ .../style/OOFormatBibliography.java | 197 +++++ .../logic/openoffice/style/OOProcess.java | 90 ++ .../style/OOProcessAuthorYearMarkers.java | 162 ++++ .../style/OOProcessCitationKeyMarkers.java | 30 + .../style/OOProcessNumericMarkers.java | 43 + .../logic/openoffice/style/StyleLoader.java | 2 +- .../model/openoffice/style/Citation.java | 142 ++++ .../model/openoffice/style/CitationGroup.java | 157 ++++ .../openoffice/style/CitationGroupId.java | 19 + .../openoffice/style/CitationGroups.java | 315 +++++++ .../style/CitationLookupResult.java | 49 ++ .../openoffice/style/CitationMarkerEntry.java | 30 + .../style/CitationMarkerNormEntry.java | 24 + .../style/CitationMarkerNumericBibEntry.java | 19 + .../style/CitationMarkerNumericEntry.java | 20 + .../model/openoffice/style/CitationPath.java | 17 + .../model/openoffice/style/CitationType.java | 24 + .../model/openoffice/style/CitedKey.java | 141 ++++ .../model/openoffice/style/CitedKeys.java | 84 ++ .../openoffice/style/ComparableCitation.java | 13 + .../openoffice/style/ComparableCitedKey.java | 16 + .../openoffice/style/CompareCitation.java | 30 + .../openoffice/style/CompareCitedKey.java | 41 + .../style/NonUniqueCitationMarker.java | 15 + .../model/openoffice/style/OODataModel.java | 38 + .../model/openoffice/style/PageInfo.java | 47 ++ .../openoffice/style/OOBibStyleTest.java | 401 +++++++++ .../style/OOBibStyleTestHelper.java | 359 ++++++++ 31 files changed, 4022 insertions(+), 5 deletions(-) create mode 100644 src/main/java/org/jabref/logic/openoffice/style/OOBibStyleGetCitationMarker.java create mode 100644 src/main/java/org/jabref/logic/openoffice/style/OOBibStyleGetNumCitationMarker.java create mode 100644 src/main/java/org/jabref/logic/openoffice/style/OOFormatBibliography.java create mode 100644 src/main/java/org/jabref/logic/openoffice/style/OOProcess.java create mode 100644 src/main/java/org/jabref/logic/openoffice/style/OOProcessAuthorYearMarkers.java create mode 100644 src/main/java/org/jabref/logic/openoffice/style/OOProcessCitationKeyMarkers.java create mode 100644 src/main/java/org/jabref/logic/openoffice/style/OOProcessNumericMarkers.java create mode 100644 src/main/java/org/jabref/model/openoffice/style/Citation.java create mode 100644 src/main/java/org/jabref/model/openoffice/style/CitationGroup.java create mode 100644 src/main/java/org/jabref/model/openoffice/style/CitationGroupId.java create mode 100644 src/main/java/org/jabref/model/openoffice/style/CitationGroups.java create mode 100644 src/main/java/org/jabref/model/openoffice/style/CitationLookupResult.java create mode 100644 src/main/java/org/jabref/model/openoffice/style/CitationMarkerEntry.java create mode 100644 src/main/java/org/jabref/model/openoffice/style/CitationMarkerNormEntry.java create mode 100644 src/main/java/org/jabref/model/openoffice/style/CitationMarkerNumericBibEntry.java create mode 100644 src/main/java/org/jabref/model/openoffice/style/CitationMarkerNumericEntry.java create mode 100644 src/main/java/org/jabref/model/openoffice/style/CitationPath.java create mode 100644 src/main/java/org/jabref/model/openoffice/style/CitationType.java create mode 100644 src/main/java/org/jabref/model/openoffice/style/CitedKey.java create mode 100644 src/main/java/org/jabref/model/openoffice/style/CitedKeys.java create mode 100644 src/main/java/org/jabref/model/openoffice/style/ComparableCitation.java create mode 100644 src/main/java/org/jabref/model/openoffice/style/ComparableCitedKey.java create mode 100644 src/main/java/org/jabref/model/openoffice/style/CompareCitation.java create mode 100644 src/main/java/org/jabref/model/openoffice/style/CompareCitedKey.java create mode 100644 src/main/java/org/jabref/model/openoffice/style/NonUniqueCitationMarker.java create mode 100644 src/main/java/org/jabref/model/openoffice/style/OODataModel.java create mode 100644 src/main/java/org/jabref/model/openoffice/style/PageInfo.java create mode 100644 src/test/java/org/jabref/logic/openoffice/style/OOBibStyleTestHelper.java diff --git a/src/main/java/org/jabref/logic/openoffice/style/OOBibStyle.java b/src/main/java/org/jabref/logic/openoffice/style/OOBibStyle.java index 561099afe96..113cef246e2 100644 --- a/src/main/java/org/jabref/logic/openoffice/style/OOBibStyle.java +++ b/src/main/java/org/jabref/logic/openoffice/style/OOBibStyle.java @@ -31,9 +31,19 @@ import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.field.Field; import org.jabref.model.entry.field.FieldFactory; +import org.jabref.model.entry.field.OrFields; import org.jabref.model.entry.field.StandardField; import org.jabref.model.entry.types.EntryType; import org.jabref.model.entry.types.EntryTypeFactory; +import org.jabref.model.openoffice.ootext.OOFormat; +import org.jabref.model.openoffice.ootext.OOText; +import org.jabref.model.openoffice.style.CitationMarkerEntry; +import org.jabref.model.openoffice.style.CitationMarkerNormEntry; +import org.jabref.model.openoffice.style.CitationMarkerNumericBibEntry; +import org.jabref.model.openoffice.style.CitationMarkerNumericEntry; +import org.jabref.model.openoffice.style.NonUniqueCitationMarker; +import org.jabref.model.openoffice.style.PageInfo; +import org.jabref.model.openoffice.util.OOListUtil; import org.jabref.model.strings.StringUtil; import org.slf4j.Logger; @@ -84,6 +94,22 @@ public class OOBibStyle implements Comparable { private static final String CITATION_CHARACTER_FORMAT = "CitationCharacterFormat"; private static final String FORMAT_CITATIONS = "FormatCitations"; private static final String GROUPED_NUMBERS_SEPARATOR = "GroupedNumbersSeparator"; + + // These two can do what ItalicCitations, BoldCitations, + // SuperscriptCitations and SubscriptCitations were supposed to do, + // as well as underline smallcaps and strikeout. + private static final String CITATION_GROUP_MARKUP_BEFORE = "CitationGroupMarkupBefore"; + private static final String CITATION_GROUP_MARKUP_AFTER = "CitationGroupMarkupAfter"; + + private static final String AUTHORS_PART_MARKUP_BEFORE = "AuthorsPartMarkupBefore"; + private static final String AUTHORS_PART_MARKUP_AFTER = "AuthorsPartMarkupAfter"; + + private static final String AUTHOR_NAMES_LIST_MARKUP_BEFORE = "AuthorNamesListMarkupBefore"; + private static final String AUTHOR_NAMES_LIST_MARKUP_AFTER = "AuthorNamesListMarkupAfter"; + + private static final String AUTHOR_NAME_MARKUP_BEFORE = "AuthorNameMarkupBefore"; + private static final String AUTHOR_NAME_MARKUP_AFTER = "AuthorNameMarkupAfter"; + private static final String PAGE_INFO_SEPARATOR = "PageInfoSeparator"; private static final String CITATION_SEPARATOR = "CitationSeparator"; private static final String IN_TEXT_YEAR_SEPARATOR = "InTextYearSeparator"; @@ -157,11 +183,24 @@ private void setDefaultProperties() { properties.put(IS_NUMBER_ENTRIES, Boolean.FALSE); properties.put(BRACKET_BEFORE, "["); properties.put(BRACKET_AFTER, "]"); - properties.put(REFERENCE_PARAGRAPH_FORMAT, "Default"); + properties.put(REFERENCE_PARAGRAPH_FORMAT, "Standard"); properties.put(REFERENCE_HEADER_PARAGRAPH_FORMAT, "Heading 1"); // Set default properties for the citation marker: citProperties.put(AUTHOR_FIELD, FieldFactory.serializeOrFields(StandardField.AUTHOR, StandardField.EDITOR)); + + citProperties.put(CITATION_GROUP_MARKUP_BEFORE, ""); + citProperties.put(CITATION_GROUP_MARKUP_AFTER, ""); + + citProperties.put(AUTHORS_PART_MARKUP_BEFORE, ""); + citProperties.put(AUTHORS_PART_MARKUP_AFTER, ""); + + citProperties.put(AUTHOR_NAMES_LIST_MARKUP_BEFORE, ""); + citProperties.put(AUTHOR_NAMES_LIST_MARKUP_AFTER, ""); + + citProperties.put(AUTHOR_NAME_MARKUP_BEFORE, ""); + citProperties.put(AUTHOR_NAME_MARKUP_AFTER, ""); + citProperties.put(YEAR_FIELD, StandardField.YEAR.getName()); citProperties.put(MAX_AUTHORS, 3); citProperties.put(MAX_AUTHORS_FIRST, -1); @@ -178,7 +217,7 @@ private void setDefaultProperties() { citProperties.put(GROUPED_NUMBERS_SEPARATOR, "-"); citProperties.put(MINIMUM_GROUPING_COUNT, 3); citProperties.put(FORMAT_CITATIONS, Boolean.FALSE); - citProperties.put(CITATION_CHARACTER_FORMAT, "Default"); + citProperties.put(CITATION_CHARACTER_FORMAT, "Standard"); citProperties.put(ITALIC_CITATIONS, Boolean.FALSE); citProperties.put(BOLD_CITATIONS, Boolean.FALSE); citProperties.put(SUPERSCRIPT_CITATIONS, Boolean.FALSE); @@ -379,8 +418,8 @@ private void handlePropertiesLine(String line, Map map) { value = value.trim().substring(1, value.trim().length() - 1); } Object toSet = value; - if (NUM_PATTERN.matcher(value).matches()) { - toSet = Integer.parseInt(value); + if (NUM_PATTERN.matcher(value.trim()).matches()) { + toSet = Integer.parseInt(value.trim()); } else if ("true".equalsIgnoreCase(value.trim())) { toSet = Boolean.TRUE; } else if ("false".equalsIgnoreCase(value.trim())) { @@ -408,6 +447,7 @@ public Layout getReferenceFormat(EntryType type) { } } + /* begin_old */ /** * Format a number-based citation marker for the given number. * @@ -474,7 +514,9 @@ public String getNumCitationMarker(List number, int minGroupingCount, b sb.append(bracketAfter); return sb.toString(); } + /* end_old */ + /* begin_old */ /** * Format the marker for the in-text citation according to this BIB style. Uniquefier letters are added as * provided by the uniquefiers argument. If successive entries within the citation are uniquefied from each other, @@ -552,7 +594,9 @@ public String getCitationMarker(List entries, Map entries, String[] uniquefiers, int from, int t } uniquefiers[from] = sb.toString(); } + /* end_old */ + /* begin_old */ /** * This method produces (Author, year) style citation strings in many different forms. * @@ -623,7 +669,9 @@ private String getAuthorYearParenthesisMarker(List entries, Map entries, Map getBibLayout() { + return bibLayout; + } + + protected Map getProperties() { + return properties; + } + + protected Map getCitProperties() { + return citProperties; + } + + protected void addJournal(String s) { + journals.add(s); + } + + protected void setLocalCopy(String s) { + localCopy = s; + } + + protected void setName(String s) { + name = s; + } + + protected boolean getIsDefaultLayoutPresent() { + return isDefaultLayoutPresent; + } + + protected void setIsDefaultLayoutPresent(boolean b) { + isDefaultLayoutPresent = b; + } + + protected void setValid(boolean b) { + valid = b; + } + + protected LayoutFormatterPreferences getPrefs() { + return prefs; + } + + protected void setDefaultBibLayout(Layout l) { + defaultBibLayout = l; + } + + /** + * Format a number-based citation marker for the given entries. + * + * @return The text for the citation. + */ + public OOText getNumCitationMarker2(List entries) { + final int minGroupingCount = this.getMinimumGroupingCount(); + return OOBibStyleGetNumCitationMarker.getNumCitationMarker2(this, + entries, + minGroupingCount); + } + + /** + * For some tests we need to override minGroupingCount. + */ + public OOText getNumCitationMarker2(List entries, + int minGroupingCount) { + return OOBibStyleGetNumCitationMarker.getNumCitationMarker2(this, + entries, + minGroupingCount); + } + + /** + * Format a number-based bibliography label for the given number. + */ + public OOText getNumCitationMarkerForBibliography(CitationMarkerNumericBibEntry entry) { + return OOBibStyleGetNumCitationMarker.getNumCitationMarkerForBibliography(this, entry); + } + + /** + * Make sure that (1) we have exactly one entry for each + * citation, (2) each entry is either Optional.empty or its content is not empty when trimmed. + * + * As a special case: pageInfos may be null. In this case + * the result is a list filled with Optional.empty() values. + */ + static List> + normalizePageInfos(List> pageInfos, int nCitations) { + + // translate null to all-empty + if (pageInfos == null) { + List> res = new ArrayList<>(nCitations); + for (int i = 0; i < nCitations; i++) { + res.add(Optional.empty()); + } + return res; + } + + // not null, check size + if (pageInfos.size() != nCitations) { + throw new RuntimeException("normalizePageInfos: pageInfos.size() != nCitations"); + } + + // not null, normalize elementwise + return OOListUtil.map(pageInfos, PageInfo::normalizePageInfo); + } + + public OOText getNormalizedCitationMarker(CitationMarkerNormEntry ce) { + return OOBibStyleGetCitationMarker.getNormalizedCitationMarker(this, ce, Optional.empty()); + } + + /** + * Format the marker for the in-text citation according to this + * BIB style. Uniquefier letters are added as provided by the + * citationMarkerEntries argument. If successive entries within + * the citation are uniquefied from each other, this method will + * perform a grouping of these entries. + * + * If successive entries within the citation are uniquefied from + * each other, this method will perform a grouping of these + * entries. + * + * @param citationMarkerEntries The list of entries providing the + * data. + * + * @param inParenthesis Signals whether a parenthesized citation + * or an in-text citation is wanted. + * + * @param nonUniqueCitationMarkerHandling + * + * THROWS : Should throw if finds that uniqueLetters + * provided do not make the entries unique. + * + * FORGIVEN : is needed to allow preliminary markers + * for freshly inserted citations without + * going throw the uniquefication process. + * + * @return The formatted citation. The result does not include + * the standard wrappers: + * OOFormat.setLocaleNone() and OOFormat.setCharStyle(). + * These are added by decorateCitationMarker() + */ + public OOText createCitationMarker(List citationMarkerEntries, + boolean inParenthesis, + NonUniqueCitationMarker nonUniqueCitationMarkerHandling) { + return OOBibStyleGetCitationMarker.createCitationMarker(this, + citationMarkerEntries, + inParenthesis, + nonUniqueCitationMarkerHandling); + } + + /** + * Add setLocaleNone and optionally setCharStyle(CitationCharacterFormat) around + * citationText. Called in fillCitationMarkInCursor, so these are + * also applied to "Unresolved()" entries and numeric styles. + */ + public OOText decorateCitationMarker(OOText citationText) { + OOBibStyle style = this; + OOText citationText2 = OOFormat.setLocaleNone(citationText); + if (style.isFormatCitations()) { + String charStyle = style.getCitationCharacterFormat(); + citationText2 = OOFormat.setCharStyle(citationText2, charStyle); + } + return citationText2; + } + + /* + * + * Property getters + * + */ + + /** + * Minimal number of consecutive citation numbers needed to start + * replacing with an range like "10-13". + */ + public int getMinimumGroupingCount() { + return getIntCitProperty(OOBibStyle.MINIMUM_GROUPING_COUNT); + } + + /** + * Used in number ranges like "10-13" in numbered citations. + */ + public String getGroupedNumbersSeparator() { + return getStringCitProperty(OOBibStyle.GROUPED_NUMBERS_SEPARATOR); + } + + private boolean getBooleanProperty(String propName) { + return (Boolean) properties.get(propName); + } + + private String getStringProperty(String propName) { + return (String) properties.get(propName); + } + + /** + * Should citation markers be italicized? + * + */ + public String getCitationGroupMarkupBefore() { + return getStringCitProperty(CITATION_GROUP_MARKUP_BEFORE); + } + + public String getCitationGroupMarkupAfter() { + return getStringCitProperty(CITATION_GROUP_MARKUP_AFTER); + } + + /** Author list, including " et al." */ + public String getAuthorsPartMarkupBefore() { + return getStringCitProperty(AUTHORS_PART_MARKUP_BEFORE); + } + + public String getAuthorsPartMarkupAfter() { + return getStringCitProperty(AUTHORS_PART_MARKUP_AFTER); + } + + /** Author list, excluding " et al." */ + public String getAuthorNamesListMarkupBefore() { + return getStringCitProperty(AUTHOR_NAMES_LIST_MARKUP_BEFORE); + } + + public String getAuthorNamesListMarkupAfter() { + return getStringCitProperty(AUTHOR_NAMES_LIST_MARKUP_AFTER); + } + + /** Author names. Excludes Author separators */ + public String getAuthorNameMarkupBefore() { + return getStringCitProperty(AUTHOR_NAME_MARKUP_BEFORE); + } + + public String getAuthorNameMarkupAfter() { + return getStringCitProperty(AUTHOR_NAME_MARKUP_AFTER); + } + + public boolean getMultiCiteChronological() { + // "MultiCiteChronological" + return this.getBooleanCitProperty(OOBibStyle.MULTI_CITE_CHRONOLOGICAL); + } + + // Probably obsolete, now we can use " et al." instead in EtAlString + public boolean getItalicEtAl() { + // "ItalicEtAl" + return this.getBooleanCitProperty(OOBibStyle.ITALIC_ET_AL); + } + + /** + * @return Names of fields containing authors: the first + * non-empty field will be used. + */ + protected OrFields getAuthorFieldNames() { + String authorFieldNamesString = this.getStringCitProperty(OOBibStyle.AUTHOR_FIELD); + return FieldFactory.parseOrFields(authorFieldNamesString); + } + + /** + * @return Field containing year, with fallback fields. + */ + protected OrFields getYearFieldNames() { + String yearFieldNamesString = this.getStringCitProperty(OOBibStyle.YEAR_FIELD); + return FieldFactory.parseOrFields(yearFieldNamesString); + } + + /* The String to add between the two last author names, e.g. " & ". */ + protected String getAuthorLastSeparator() { + return getStringCitProperty(OOBibStyle.AUTHOR_LAST_SEPARATOR); + } + + /* As getAuthorLastSeparator, for in-text citation. */ + protected String getAuthorLastSeparatorInTextWithFallBack() { + String a = getStringCitProperty(OOBibStyle.AUTHOR_LAST_SEPARATOR_IN_TEXT); + String b = getStringCitProperty(OOBibStyle.AUTHOR_LAST_SEPARATOR); + return Objects.requireNonNullElse(a, b); + } + + protected String getPageInfoSeparator() { + return getStringCitProperty(OOBibStyle.PAGE_INFO_SEPARATOR); + } + + protected String getUniquefierSeparator() { + return getStringCitProperty(OOBibStyle.UNIQUEFIER_SEPARATOR); + } + + protected String getCitationSeparator() { + return getStringCitProperty(OOBibStyle.CITATION_SEPARATOR); + } + + protected String getYearSeparator() { + return getStringCitProperty(OOBibStyle.YEAR_SEPARATOR); + } + + protected String getYearSeparatorInText() { + return getStringCitProperty(OOBibStyle.IN_TEXT_YEAR_SEPARATOR); + } + + /** The maximum number of authors to write out in full without + * using "et al." Set to -1 to always write out all authors. + */ + protected int getMaxAuthors() { + return getIntCitProperty(OOBibStyle.MAX_AUTHORS); + } + + public int getMaxAuthorsFirst() { + return getIntCitProperty(OOBibStyle.MAX_AUTHORS_FIRST); + } + + /** Opening parenthesis before citation (or year, for in-text) */ + protected String getBracketBefore() { + return getStringCitProperty(OOBibStyle.BRACKET_BEFORE); + } + + /** Closing parenthesis after citation */ + protected String getBracketAfter() { + return getStringCitProperty(OOBibStyle.BRACKET_AFTER); + } + + /** Opening parenthesis before citation marker in the bibliography. */ + private String getBracketBeforeInList() { + return getStringCitProperty(OOBibStyle.BRACKET_BEFORE_IN_LIST); + } + + public String getBracketBeforeInListWithFallBack() { + return Objects.requireNonNullElse(getBracketBeforeInList(), getBracketBefore()); + } + + /** Closing parenthesis after citation marker in the bibliography */ + private String getBracketAfterInList() { + return getStringCitProperty(OOBibStyle.BRACKET_AFTER_IN_LIST); + } + + String getBracketAfterInListWithFallBack() { + return Objects.requireNonNullElse(getBracketAfterInList(), getBracketAfter()); + } + + public OOText getFormattedBibliographyTitle() { + OOBibStyle style = this; + OOText title = style.getReferenceHeaderText(); + String parStyle = style.getReferenceHeaderParagraphFormat(); + if (parStyle != null) { + title = OOFormat.paragraph(title, parStyle); + } else { + title = OOFormat.paragraph(title); + } + return title; + } + } diff --git a/src/main/java/org/jabref/logic/openoffice/style/OOBibStyleGetCitationMarker.java b/src/main/java/org/jabref/logic/openoffice/style/OOBibStyleGetCitationMarker.java new file mode 100644 index 00000000000..ee2bba27e7e --- /dev/null +++ b/src/main/java/org/jabref/logic/openoffice/style/OOBibStyleGetCitationMarker.java @@ -0,0 +1,767 @@ +package org.jabref.logic.openoffice.style; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +import org.jabref.model.database.BibDatabase; +import org.jabref.model.entry.Author; +import org.jabref.model.entry.AuthorList; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.field.Field; +import org.jabref.model.entry.field.OrFields; +import org.jabref.model.openoffice.ootext.OOText; +import org.jabref.model.openoffice.style.CitationLookupResult; +import org.jabref.model.openoffice.style.CitationMarkerEntry; +import org.jabref.model.openoffice.style.CitationMarkerNormEntry; +import org.jabref.model.openoffice.style.NonUniqueCitationMarker; +import org.jabref.model.openoffice.style.PageInfo; +import org.jabref.model.strings.StringUtil; + +class OOBibStyleGetCitationMarker { + + /** + * Look up the nth author and return the "proper" last name for + * citation markers. + * + * Note: "proper" in the sense that it includes the "von" part + * of the name (followed by a space) if there is one. + * + * @param al The author list. + * @param number The number of the author to return. + * @return The author name, or an empty String if inapplicable. + */ + private static String getAuthorLastName(AuthorList al, int number) { + StringBuilder sb = new StringBuilder(); + + if (al.getNumberOfAuthors() > number) { + Author a = al.getAuthor(number); + // "von " if von exists + Optional von = a.getVon(); + if (von.isPresent() && !von.get().isEmpty()) { + sb.append(von.get()); + sb.append(' '); + } + // last name if it exists + sb.append(a.getLast().orElse("")); + } + + return sb.toString(); + } + + private static String markupAuthorName(OOBibStyle style, String name) { + return (style.getAuthorNameMarkupBefore() + + name + + style.getAuthorNameMarkupAfter()); + } + + /** + * @param authorList Parsed list of authors. + * + * @param maxAuthors The maximum number of authors to write out. + * If there are more authors, then ET_AL_STRING is emitted + * to mark their omission. + * Set to -1 to write out all authors. + * + * maxAuthors=0 is pointless, now throws RuntimeException + * (Earlier it behaved as maxAuthors=1) + * + * maxAuthors less than -1 : throw RuntimeException + * + * @param andString For "A, B[ and ]C" + * + * @return "Au[AS]Bu[AS]Cu[OXFORD_COMMA][andString]Du[yearSep]" + * or "Au[etAlString][yearSep]" + * + * where AS = AUTHOR_SEPARATOR + * Au, Bu, Cu, Du are last names of authors. + * + * Note: + * - The "Au[AS]Bu[AS]Cu" (or the "Au") part may be empty (maxAuthors==0 or nAuthors==0). + * - OXFORD_COMMA is only emitted if nAuthors is at least 3. + * - andString is only emitted if nAuthors is at least 2. + */ + private static String formatAuthorList(OOBibStyle style, + AuthorList authorList, + int maxAuthors, + String andString) { + + Objects.requireNonNull(authorList); + + // Apparently maxAuthorsBeforeEtAl is always 1 for in-text citations. + // In reference lists can be for example 7, + // (https://www.chicagomanualofstyle.org/turabian/turabian-author-date-citation-quick-guide.html) + // but those are handled elsewhere. + // + // There is also + // https://apastyle.apa.org/style-grammar-guidelines/ ... + // ... citations/basic-principles/same-year-first-author + // suggesting the to avoid ambiguity, we may need more than one name + // before "et al.". We do not currently do this kind of disambiguation, + // but we might, one day. + // + final int maxAuthorsBeforeEtAl = 1; + + // The String to represent authors that are not mentioned, + // e.g. " et al." + String etAlString = style.getEtAlString(); + + // getItalicEtAl is not necessary now, since etAlString could + // itself contain the markup. + // This is for backward compatibility. + if (style.getItalicEtAl()) { + etAlString = "" + etAlString + ""; + } + + // The String to add between author names except the last two, + // e.g. ", ". + String authorSep = style.getAuthorSeparator(); + + // The String to put after the second to last author in case + // of three or more authors: (A, B[,] and C) + String oxfordComma = style.getOxfordComma(); + + StringBuilder sb = new StringBuilder(); + + final int nAuthors = authorList.getNumberOfAuthors(); + + // To reduce ambiguity, throw on unexpected values of maxAuthors + if (maxAuthors == 0 && nAuthors != 0) { + throw new RuntimeException("maxAuthors = 0 in formatAuthorList"); + } + if (maxAuthors < -1) { + throw new RuntimeException("maxAuthors < -1 in formatAuthorList"); + } + + // emitAllAuthors == false means use "et al." + boolean emitAllAuthors = ((nAuthors <= maxAuthors) || (maxAuthors == -1)); + + int nAuthorsToEmit = (emitAllAuthors + ? nAuthors + // If we use "et al." maxAuthorsBeforeEtAl also limits the + // number of authors emitted. + : Math.min(maxAuthorsBeforeEtAl, nAuthors)); + + if (nAuthorsToEmit >= 1) { + sb.append(style.getAuthorsPartMarkupBefore()); + sb.append(style.getAuthorNamesListMarkupBefore()); + // The first author + String name = getAuthorLastName(authorList, 0); + sb.append(markupAuthorName(style, name)); + } + + if (nAuthors >= 2) { + + if (emitAllAuthors) { + // Emit last names, except for the last author + int j = 1; + while (j < (nAuthors - 1)) { + sb.append(authorSep); + String name = getAuthorLastName(authorList, j); + sb.append(markupAuthorName(style, name)); + j++; + } + // oxfordComma if at least 3 authors + if (nAuthors >= 3) { + sb.append(oxfordComma); + } + // Emit " and "+"LastAuthor" + sb.append(andString); + String name = getAuthorLastName(authorList, nAuthors - 1); + sb.append(markupAuthorName(style, name)); + + } else { + // Emit last names up to nAuthorsToEmit. + // + // The (maxAuthorsBeforeEtAl > 1) test is intended to + // make sure the compiler eliminates this block as + // long as maxAuthorsBeforeEtAl is fixed to 1. + if (maxAuthorsBeforeEtAl > 1) { + int j = 1; + while (j < nAuthorsToEmit) { + sb.append(authorSep); + String name = getAuthorLastName(authorList, j); + sb.append(markupAuthorName(style, name)); + j++; + } + } + } + } + + if (nAuthorsToEmit >= 1) { + sb.append(style.getAuthorNamesListMarkupAfter()); + } + + if (nAuthors >= 2 && !emitAllAuthors) { + sb.append(etAlString); + } + + sb.append(style.getAuthorsPartMarkupAfter()); + return sb.toString(); + } + + /** + * On success, getRawCitationMarkerField returns content, + * but we also need to know which field matched, because + * for some fields (actually: for author names) we need to + * reproduce the surrounding braces to inform AuthorList.parse + * not to split up the content. + */ + private static class FieldAndContent { + Field field; + String content; + FieldAndContent(Field field, String content) { + this.field = field; + this.content = content; + } + } + + /** + * @return the field and the content of the first nonempty (after trimming) + * field (or alias) from {@code fields} found in {@code entry}. + * Return {@code Optional.empty()} if found nothing. + */ + private static Optional getRawCitationMarkerField(BibEntry entry, + BibDatabase database, + OrFields fields) { + Objects.requireNonNull(entry, "Entry cannot be null"); + Objects.requireNonNull(database, "database cannot be null"); + + for (Field field : fields /* FieldFactory.parseOrFields(fields)*/) { + Optional optionalContent = entry.getResolvedFieldOrAlias(field, database); + final boolean foundSomething = (optionalContent.isPresent() + && !optionalContent.get().trim().isEmpty()); + if (foundSomething) { + return Optional.of(new FieldAndContent(field, optionalContent.get())); + } + } + return Optional.empty(); + } + + /** + * This method looks up a field for an entry in a database. + * + * Any number of backup fields can be used if the primary field is + * empty. + * + * @param fields A list of fields, to look up, using first nonempty hit. + * + * If backup fields are needed, separate field + * names by /. + * + * E.g. to use "author" with "editor" as backup, + * specify + * FieldFactory.serializeOrFields(StandardField.AUTHOR, + * StandardField.EDITOR) + * + * @return The resolved field content, or an empty string if the + * field(s) were empty. + * + * + * + */ + private static String getCitationMarkerField(OOBibStyle style, + CitationLookupResult db, + OrFields fields) { + Objects.requireNonNull(db); + + Optional optionalFieldAndContent = + getRawCitationMarkerField(db.entry, db.database, fields); + + if (optionalFieldAndContent.isEmpty()) { + // No luck? Return an empty string: + return ""; + } + + FieldAndContent fc = optionalFieldAndContent.get(); + String result = style.getFieldFormatter().format(fc.content); + + // If the field we found is mentioned in authorFieldNames and + // content has a pair of braces around it, we add a pair of + // braces around the result, so that AuthorList.parse does not split + // the content. + final OrFields fieldsToRebrace = style.getAuthorFieldNames(); + if (fieldsToRebrace.contains(fc.field) && StringUtil.isInCurlyBrackets(fc.content)) { + result = "{" + result + "}"; + } + return result; + } + + private static AuthorList getAuthorList(OOBibStyle style, CitationLookupResult db) { + + // The bibtex fields providing author names, e.g. "author" or + // "editor". + OrFields authorFieldNames = style.getAuthorFieldNames(); + + String authorListAsString = getCitationMarkerField(style, db, authorFieldNames); + return AuthorList.parse(authorListAsString); + } + + private enum AuthorYearMarkerPurpose { + IN_PARENTHESIS, + IN_TEXT, + NORMALIZED + } + + /** + * How many authors would be emitted for ce, considering + * style and ce.getIsFirstAppearanceOfSource() + * + * If ce is unresolved, return 0. + */ + private static int calculateNAuthorsToEmit(OOBibStyle style, CitationMarkerEntry ce) { + + int maxAuthors = (ce.getIsFirstAppearanceOfSource() + ? style.getMaxAuthorsFirst() + : style.getMaxAuthors()); + + if (ce.getLookupResult().isEmpty()) { + // unresolved + return 0; + } + + AuthorList authorList = getAuthorList(style, ce.getLookupResult().get()); + int nAuthors = authorList.getNumberOfAuthors(); + + if (maxAuthors == -1) { + return nAuthors; + } else { + return Integer.min(nAuthors, maxAuthors); + } + } + + /** + * Produce (Author, year) or "Author (year)" style citation strings. + * + * @param purpose IN_PARENTHESIS and NORMALIZED puts parentheses around the whole, + * IN_TEXT around each (year,uniqueLetter,pageInfo) part. + * + * NORMALIZED omits uniqueLetter and pageInfo, + * ignores isFirstAppearanceOfSource (always + * style.getMaxAuthors, not getMaxAuthorsFirst) + * + * @param ces The list of CitationMarkerEntry values to process. + * + * Here we do not check for duplicate entries: those + * are handled by {@code getCitationMarker} by + * omitting them from the list. + * + * Unresolved citations recognized by + * ce.getBibEntry() and/or + * ce.getDatabase() returning empty, and + * emitted as "Unresolved${citationKey}". + * + * Neither uniqueLetter nor pageInfo are emitted + * for unresolved citations. + * + * @param startsNewGroup Should have the same length as {@code ces}, and + * contain true for entries starting a new group, + * false for those that only add a uniqueLetter to + * the grouped presentation. + * + * @param maxAuthorsOverride If not empty, always show this number of authors. + * Added to allow NORMALIZED to use maxAuthors value that differs from + * style.getMaxAuthors() + * + * @return The formatted citation. + * + */ + private static OOText getAuthorYearParenthesisMarker2(OOBibStyle style, + AuthorYearMarkerPurpose purpose, + List ces, + boolean[] startsNewGroup, + Optional maxAuthorsOverride) { + + boolean inParenthesis = (purpose == AuthorYearMarkerPurpose.IN_PARENTHESIS + || purpose == AuthorYearMarkerPurpose.NORMALIZED); + + // The String to separate authors from year, e.g. "; ". + String yearSep = (inParenthesis + ? style.getYearSeparator() + : style.getYearSeparatorInText()); + + // The opening parenthesis. + String startBrace = style.getBracketBefore(); + + // The closing parenthesis. + String endBrace = style.getBracketAfter(); + + // The String to separate citations from each other. + String citationSeparator = style.getCitationSeparator(); + + // The bibtex field providing the year, e.g. "year". + OrFields yearFieldNames = style.getYearFieldNames(); + + // The String to add between the two last author names, e.g. " & ". + String andString = (inParenthesis + ? style.getAuthorLastSeparator() + : style.getAuthorLastSeparatorInTextWithFallBack()); + + String pageInfoSeparator = style.getPageInfoSeparator(); + String uniquefierSeparator = style.getUniquefierSeparator(); + + StringBuilder sb = new StringBuilder(); + sb.append(style.getCitationGroupMarkupBefore()); + + if (inParenthesis) { + sb.append(startBrace); // shared parenthesis + } + + for (int j = 0; j < ces.size(); j++) { + CitationMarkerEntry ce = ces.get(j); + boolean startingNewGroup = startsNewGroup[j]; + boolean endingAGroup = (j + 1 == ces.size()) || startsNewGroup[j + 1]; + + if (!startingNewGroup) { + // Just add our uniqueLetter + String uniqueLetter = ce.getUniqueLetter().orElse(null); + if (uniqueLetter != null) { + sb.append(uniquefierSeparator); + sb.append(uniqueLetter); + } + + // And close the brace, if we are the last in the group. + if (!inParenthesis && endingAGroup) { + sb.append(endBrace); + } + continue; + } + + if (j > 0) { + sb.append(citationSeparator); + } + + StringBuilder pageInfoPart = new StringBuilder(""); + if (purpose != AuthorYearMarkerPurpose.NORMALIZED) { + Optional pageInfo = + PageInfo.normalizePageInfo(ce.getPageInfo()); + if (pageInfo.isPresent()) { + pageInfoPart.append(pageInfoSeparator); + pageInfoPart.append(OOText.toString(pageInfo.get())); + } + } + + final boolean isUnresolved = ce.getLookupResult().isEmpty(); + if (isUnresolved) { + sb.append(String.format("Unresolved(%s)", ce.getCitationKey())); + if (purpose != AuthorYearMarkerPurpose.NORMALIZED) { + sb.append(pageInfoPart); + } + } else { + + CitationLookupResult db = ce.getLookupResult().get(); + + int maxAuthors = (purpose == AuthorYearMarkerPurpose.NORMALIZED + ? style.getMaxAuthors() + : calculateNAuthorsToEmit(style, ce)); + + if (maxAuthorsOverride.isPresent()) { + maxAuthors = maxAuthorsOverride.get(); + } + + AuthorList authorList = getAuthorList(style, db); + String authorString = formatAuthorList(style, authorList, maxAuthors, andString); + sb.append(authorString); + sb.append(yearSep); + + if (!inParenthesis) { + sb.append(startBrace); // parenthesis before year + } + + String year = getCitationMarkerField(style, db, yearFieldNames); + if (year != null) { + sb.append(year); + } + + if (purpose != AuthorYearMarkerPurpose.NORMALIZED) { + String uniqueLetter = ce.getUniqueLetter().orElse(null); + if (uniqueLetter != null) { + sb.append(uniqueLetter); + } + } + + if (purpose != AuthorYearMarkerPurpose.NORMALIZED) { + sb.append(pageInfoPart); + } + + if (!inParenthesis && endingAGroup) { + sb.append(endBrace); // parenthesis after year + } + } + } // for j + + if (inParenthesis) { + sb.append(endBrace); // shared parenthesis + } + sb.append(style.getCitationGroupMarkupAfter()); + return OOText.fromString(sb.toString()); + } + + private static class CitationMarkerNormEntryWrap implements CitationMarkerEntry { + + CitationMarkerNormEntry inner; + + CitationMarkerNormEntryWrap(CitationMarkerNormEntry inner) { + this.inner = inner; + } + + @Override + public String getCitationKey() { + return inner.getCitationKey(); + } + + @Override + public Optional getLookupResult() { + return inner.getLookupResult(); + } + + @Override + public Optional getUniqueLetter() { + return Optional.empty(); + } + + @Override + public Optional getPageInfo() { + return Optional.empty(); + } + + @Override + public boolean getIsFirstAppearanceOfSource() { + return false; + } + } + + /** + * @param cne A citation to process. + * + * @return A normalized citation marker for deciding which + * citations need uniqueLetters. + * + * For details of what "normalized" means: {@see getAuthorYearParenthesisMarker2} + * + * Note: now includes some markup. + */ + static OOText getNormalizedCitationMarker(OOBibStyle style, + CitationMarkerNormEntry cne, + Optional maxAuthorsOverride) { + boolean[] startsNewGroup = {true}; + CitationMarkerEntry ce = new CitationMarkerNormEntryWrap(cne); + return getAuthorYearParenthesisMarker2(style, + AuthorYearMarkerPurpose.NORMALIZED, + Collections.singletonList(ce), + startsNewGroup, + maxAuthorsOverride); + } + + private static List + getNormalizedCitationMarkers(OOBibStyle style, + List citationMarkerEntries, + Optional maxAuthorsOverride) { + + List normalizedMarkers = new ArrayList<>(citationMarkerEntries.size()); + for (CitationMarkerEntry citationMarkerEntry : citationMarkerEntries) { + OOText nm = getNormalizedCitationMarker(style, + citationMarkerEntry, + maxAuthorsOverride); + normalizedMarkers.add(nm); + } + return normalizedMarkers; + } + + /** + * Produce citation marker for a citation group. + * + * Attempts to join consecutive citations: if normalized citations + * markers match and no pageInfo is present, the second entry + * can be presented by appending its uniqueLetter to the + * previous. + * + * If either entry has pageInfo, join is inhibited. + * If the previous entry has more names than we need + * we check with extended normalizedMarkers if they match. + * + * For consecutive identical entries, the second one is omitted. + * Identical requires same pageInfo here, we do not try to merge them. + * Note: notifying the user about them would be nice. + * + * @param citationMarkerEntries A group of citations to process. + * + * @param inParenthesis If true, put parenthesis around the whole group, + * otherwise around each (year,uniqueLetter,pageInfo) part. + * + * @param nonUniqueCitationMarkerHandling What should happen if we + * stumble upon citations with identical normalized + * citation markers which cite different sources and + * are not distinguished by uniqueLetters. + * + * Note: only consecutive citations are checked. + * + */ + public static OOText + createCitationMarker(OOBibStyle style, + List citationMarkerEntries, + boolean inParenthesis, + NonUniqueCitationMarker nonUniqueCitationMarkerHandling) { + + final int nEntries = citationMarkerEntries.size(); + + // Original: + // + // Look for groups of uniquefied entries that should be combined in the output. + // E.g. (Olsen, 2005a, b) should be output instead of (Olsen, 2005a; Olsen, 2005b). + // + // Now: + // - handle pageInfos + // - allow duplicate entries with same or different pageInfos. + // + // We assume entries are already sorted, all we need is to + // group consecutive entries if we can. + // + // We also assume, that identical entries have the same uniqueLetters. + // + + List normalizedMarkers = getNormalizedCitationMarkers(style, + citationMarkerEntries, + Optional.empty()); + + // How many authors would be emitted without grouping. + int[] nAuthorsToEmit = new int[nEntries]; + int[] nAuthorsToEmitRevised = new int[nEntries]; + for (int i = 0; i < nEntries; i++) { + CitationMarkerEntry ce = citationMarkerEntries.get(i); + int n = calculateNAuthorsToEmit(style, ce); + nAuthorsToEmit[i] = n; + nAuthorsToEmitRevised[i] = n; + } + + boolean[] startsNewGroup = new boolean[nEntries]; + List filteredCitationMarkerEntries = new ArrayList<>(nEntries); + int i_out = 0; + + if (nEntries > 0) { + filteredCitationMarkerEntries.add(citationMarkerEntries.get(0)); + startsNewGroup[i_out] = true; + i_out++; + } + + for (int i = 1; i < nEntries; i++) { + final CitationMarkerEntry ce1 = citationMarkerEntries.get(i - 1); + final CitationMarkerEntry ce2 = citationMarkerEntries.get(i); + + final String nm1 = OOText.toString(normalizedMarkers.get(i - 1)); + final String nm2 = OOText.toString(normalizedMarkers.get(i)); + + final boolean isUnresolved1 = ce1.getLookupResult().isEmpty(); + final boolean isUnresolved2 = ce2.getLookupResult().isEmpty(); + + boolean startingNewGroup; + boolean sameAsPrev; /* true indicates ce2 may be omitted from output */ + if (isUnresolved2) { + startingNewGroup = true; + sameAsPrev = false; // keep it visible + } else { + // Does the number of authors to be shown differ? + // Since we compared normalizedMarkers, the difference + // between maxAuthors and maxAuthorsFirst may invalidate + // our expectation that adding uniqueLetter is valid. + + boolean nAuthorsShownInhibitsJoin; + if (isUnresolved1) { + nAuthorsShownInhibitsJoin = true; // no join for unresolved + } else { + final boolean isFirst1 = ce1.getIsFirstAppearanceOfSource(); + final boolean isFirst2 = ce2.getIsFirstAppearanceOfSource(); + + // nAuthorsToEmitRevised[i-1] may have been indirectly increased, + // we have to check that too. + if (!isFirst1 && + !isFirst2 && + (nAuthorsToEmitRevised[i - 1] == nAuthorsToEmit[i - 1])) { + // we can rely on normalizedMarkers + nAuthorsShownInhibitsJoin = false; + } else if (style.getMaxAuthors() == style.getMaxAuthorsFirst()) { + // we can rely on normalizedMarkers + nAuthorsShownInhibitsJoin = false; + } else { + final int prevShown = nAuthorsToEmitRevised[i - 1]; + final int need = nAuthorsToEmit[i]; + + if (prevShown < need) { + // We do not retrospectively change the number of authors shown + // at the previous entry, take that as decided. + nAuthorsShownInhibitsJoin = true; + } else { + // prevShown >= need + // Check with extended normalizedMarkers. + OOText nmx1 = + getNormalizedCitationMarker(style, ce1, Optional.of(prevShown)); + OOText nmx2 = + getNormalizedCitationMarker(style, ce2, Optional.of(prevShown)); + boolean extendedMarkersDiffer = !nmx2.equals(nmx1); + nAuthorsShownInhibitsJoin = extendedMarkersDiffer; + } + } + } + + final boolean citationKeysDiffer = !ce2.getCitationKey().equals(ce1.getCitationKey()); + final boolean normalizedMarkersDiffer = !nm2.equals(nm1); + + Optional pageInfo2 = PageInfo.normalizePageInfo(ce2.getPageInfo()); + Optional pageInfo1 = PageInfo.normalizePageInfo(ce1.getPageInfo()); + final boolean bothPageInfosAreEmpty = pageInfo2.isEmpty() && pageInfo1.isEmpty(); + final boolean pageInfosDiffer = !pageInfo2.equals(pageInfo1); + + Optional ul2 = ce2.getUniqueLetter(); + Optional ul1 = ce1.getUniqueLetter(); + final boolean uniqueLetterPresenceChanged = (ul2.isPresent() != ul1.isPresent()); + final boolean uniqueLettersDiffer = !ul2.equals(ul1); + + final boolean uniqueLetterDoesNotMakeUnique = (citationKeysDiffer + && !normalizedMarkersDiffer + && !uniqueLettersDiffer); + + if (uniqueLetterDoesNotMakeUnique && + nonUniqueCitationMarkerHandling.equals(NonUniqueCitationMarker.THROWS)) { + throw new RuntimeException("different citation keys," + + " but same normalizedMarker and uniqueLetter"); + } + + final boolean pageInfoInhibitsJoin = (bothPageInfosAreEmpty + ? false + : (citationKeysDiffer || pageInfosDiffer)); + + startingNewGroup = (normalizedMarkersDiffer + || nAuthorsShownInhibitsJoin + || pageInfoInhibitsJoin + || uniqueLetterPresenceChanged + || uniqueLetterDoesNotMakeUnique); + + if (!startingNewGroup) { + // inherit from first of group. Used at next i. + nAuthorsToEmitRevised[i] = nAuthorsToEmitRevised[i - 1]; + } + + sameAsPrev = (!startingNewGroup + && !uniqueLettersDiffer + && !citationKeysDiffer + && !pageInfosDiffer); + } + + if (!sameAsPrev) { + filteredCitationMarkerEntries.add(ce2); + startsNewGroup[i_out] = startingNewGroup; + i_out++; + } + } + + return getAuthorYearParenthesisMarker2(style, + (inParenthesis + ? AuthorYearMarkerPurpose.IN_PARENTHESIS + : AuthorYearMarkerPurpose.IN_TEXT), + filteredCitationMarkerEntries, + startsNewGroup, + Optional.empty()); + } +} diff --git a/src/main/java/org/jabref/logic/openoffice/style/OOBibStyleGetNumCitationMarker.java b/src/main/java/org/jabref/logic/openoffice/style/OOBibStyleGetNumCitationMarker.java new file mode 100644 index 00000000000..376c4478421 --- /dev/null +++ b/src/main/java/org/jabref/logic/openoffice/style/OOBibStyleGetNumCitationMarker.java @@ -0,0 +1,278 @@ +package org.jabref.logic.openoffice.style; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import org.jabref.model.openoffice.ootext.OOText; +import org.jabref.model.openoffice.style.CitationMarkerNumericBibEntry; +import org.jabref.model.openoffice.style.CitationMarkerNumericEntry; +import org.jabref.model.openoffice.style.PageInfo; +import org.jabref.model.openoffice.util.OOListUtil; + +class OOBibStyleGetNumCitationMarker { + + /* + * The number encoding "this entry is unresolved" + */ + public final static int UNRESOLVED_ENTRY_NUMBER = 0; + + /** + * Defines sort order for CitationMarkerNumericEntry. + */ + private static int compareCitationMarkerNumericEntry(CitationMarkerNumericEntry a, + CitationMarkerNumericEntry b) { + int na = a.getNumber().orElse(UNRESOLVED_ENTRY_NUMBER); + int nb = b.getNumber().orElse(UNRESOLVED_ENTRY_NUMBER); + int res = Integer.compare(na, nb); + if (res == 0) { + res = PageInfo.comparePageInfo(a.getPageInfo(), b.getPageInfo()); + } + return res; + } + + /** + * Create a numeric marker for use in the bibliography as label for the entry. + * + * To support for example numbers in superscript without brackets for the text, + * but "[1]" form for the bibliography, the style can provide + * the optional "BracketBeforeInList" and "BracketAfterInList" strings + * to be used in the bibliography instead of "BracketBefore" and "BracketAfter" + * + * @return "[${number}]" where + * "[" stands for BRACKET_BEFORE_IN_LIST (with fallback BRACKET_BEFORE) + * "]" stands for BRACKET_AFTER_IN_LIST (with fallback BRACKET_AFTER) + * "${number}" stands for the formatted number. + */ + public static OOText getNumCitationMarkerForBibliography(OOBibStyle style, + CitationMarkerNumericBibEntry entry) { + // prefer BRACKET_BEFORE_IN_LIST and BRACKET_AFTER_IN_LIST + String bracketBefore = style.getBracketBeforeInListWithFallBack(); + String bracketAfter = style.getBracketAfterInListWithFallBack(); + StringBuilder sb = new StringBuilder(); + sb.append(style.getCitationGroupMarkupBefore()); + sb.append(bracketBefore); + final Optional current = entry.getNumber(); + sb.append(current.isPresent() + ? String.valueOf(current.get()) + : (OOBibStyle.UNDEFINED_CITATION_MARKER + entry.getCitationKey())); + sb.append(bracketAfter); + sb.append(style.getCitationGroupMarkupAfter()); + return OOText.fromString(sb.toString()); + } + + /* + * emitBlock : a helper for getNumCitationMarker2 + * + * Given a block containing either a single entry or two or more + * entries that are joinable into an "i-j" form, append to {@code sb} the + * formatted text. + * + * Assumes: + * + * - block is not empty + * + * - For a block with a single element the element may have + * pageInfo and its num part may be Optional.empty() + * + * - For a block with two or more elements + * + * - The elements do not have pageInfo and their number part is + * not empty. + * + * - The elements number parts are consecutive positive integers, + * without repetition. + * + */ + private static void emitBlock(List block, + OOBibStyle style, + int minGroupingCount, + StringBuilder sb) { + + final int blockSize = block.size(); + if (blockSize == 0) { + throw new RuntimeException("The block is empty"); + } + + if (blockSize == 1) { + // Add single entry: + CitationMarkerNumericEntry entry = block.get(0); + final Optional num = entry.getNumber(); + sb.append(num.isEmpty() + ? (OOBibStyle.UNDEFINED_CITATION_MARKER + entry.getCitationKey()) + : String.valueOf(num.get())); + // Emit pageInfo + Optional pageInfo = entry.getPageInfo(); + if (pageInfo.isPresent()) { + sb.append(style.getPageInfoSeparator()); + sb.append(OOText.toString(pageInfo.get())); + } + return; + } + + if (blockSize >= 2) { + + /* + * Check assumptions + */ + + if (block.stream().anyMatch(x -> x.getPageInfo().isPresent())) { + throw new RuntimeException("Found pageInfo in a block with more than one elements"); + } + + if (block.stream().anyMatch(x -> x.getNumber().isEmpty())) { + throw new RuntimeException("Found unresolved entry" + + " in a block with more than one elements"); + } + + for (int j = 1; j < blockSize; j++) { + if ((block.get(j).getNumber().get() - block.get(j - 1).getNumber().get()) != 1) { + throw new RuntimeException("Numbers are not consecutive"); + } + } + + /* + * Do the actual work + */ + + if (blockSize >= minGroupingCount) { + int first = block.get(0).getNumber().get(); + int last = block.get(blockSize - 1).getNumber().get(); + if (last != (first + blockSize - 1)) { + throw new RuntimeException("blockSize and length of num range differ"); + } + + // Emit: "first-last" + sb.append(first); + sb.append(style.getGroupedNumbersSeparator()); + sb.append(last); + } else { + + // Emit: first, first+1,..., last + for (int j = 0; j < blockSize; j++) { + if (j > 0) { + sb.append(style.getCitationSeparator()); + } + sb.append(block.get(j).getNumber().get()); + } + } + return; + } + } + + /** + * Format a number-based citation marker for the given number or numbers. + * + * @param entries Provide the citation numbers. + * + * An Optional.empty() number means: could not look this up + * in the databases. Positive integers are the valid numbers. + * + * Duplicate citation numbers are allowed: + * + * - If their pageInfos are identical, only a + * single instance is emitted. + * + * - If their pageInfos differ, the number is emitted with each + * distinct pageInfo. + * + * pageInfos are expected to be normalized + * + * @param minGroupingCount Zero and negative means never group. + * Only used by tests to override the value in style. + * + * @return The text for the citation. + * + */ + public static OOText getNumCitationMarker2(OOBibStyle style, + List entries, + int minGroupingCount) { + + final boolean joinIsDisabled = (minGroupingCount <= 0); + final int nCitations = entries.size(); + + String bracketBefore = style.getBracketBefore(); + String bracketAfter = style.getBracketAfter(); + + // Sort a copy of entries + List sorted = OOListUtil.map(entries, e -> e); + sorted.sort(OOBibStyleGetNumCitationMarker::compareCitationMarkerNumericEntry); + + // "[" + StringBuilder sb = new StringBuilder(bracketBefore); + + /* + * Original: + * [2,3,4] -> [2-4] + * [0,1,2] -> [??,1,2] + * [0,1,2,3] -> [??,1-3] + * + * Now we have to consider: duplicate numbers and pageInfos + * [1,1] -> [1] + * [1,1 "pp nn"] -> keep separate if pageInfo differs + * [1 "pp nn",1 "pp nn"] -> [1 "pp nn"] + */ + + boolean blocksEmitted = false; + List currentBlock = new ArrayList<>(); + List nextBlock = new ArrayList<>(); + + for (int i = 0; i < nCitations; i++) { + + final CitationMarkerNumericEntry current = sorted.get(i); + if (current.getNumber().isPresent() && current.getNumber().get() < 0) { + throw new RuntimeException("getNumCitationMarker2: found negative value"); + } + + if (currentBlock.size() == 0) { + currentBlock.add(current); + } else { + CitationMarkerNumericEntry prev = currentBlock.get(currentBlock.size() - 1); + if (current.getNumber().isEmpty() || prev.getNumber().isEmpty()) { + nextBlock.add(current); // do not join if not found + } else if (joinIsDisabled) { + nextBlock.add(current); // join disabled + } else if (compareCitationMarkerNumericEntry(current, prev) == 0) { + // Same as prev, just forget it. + } else if ((current.getNumber().get() == (prev.getNumber().get() + 1)) + && (prev.getPageInfo().isEmpty()) + && (current.getPageInfo().isEmpty())) { + // Just two consecutive numbers without pageInfo: join + currentBlock.add(current); + } else { + // do not join + nextBlock.add(current); + } + } + + if (nextBlock.size() > 0) { + // emit current block + if (blocksEmitted) { + sb.append(style.getCitationSeparator()); + } + emitBlock(currentBlock, style, minGroupingCount, sb); + blocksEmitted = true; + currentBlock = nextBlock; + nextBlock = new ArrayList<>(); + } + + } + + if (nextBlock.size() != 0) { + throw new RuntimeException("impossible: (nextBlock.size() != 0) after loop"); + } + + if (currentBlock.size() > 0) { + // We are emitting a block + if (blocksEmitted) { + sb.append(style.getCitationSeparator()); + } + emitBlock(currentBlock, style, minGroupingCount, sb); + } + + // Emit: "]" + sb.append(bracketAfter); + return OOText.fromString(sb.toString()); + } + +} diff --git a/src/main/java/org/jabref/logic/openoffice/style/OOFormatBibliography.java b/src/main/java/org/jabref/logic/openoffice/style/OOFormatBibliography.java new file mode 100644 index 00000000000..c9ae527dcec --- /dev/null +++ b/src/main/java/org/jabref/logic/openoffice/style/OOFormatBibliography.java @@ -0,0 +1,197 @@ +package org.jabref.logic.openoffice.style; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import org.jabref.logic.l10n.Localization; +import org.jabref.logic.layout.Layout; +import org.jabref.model.database.BibDatabase; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.field.Field; +import org.jabref.model.entry.field.UnknownField; +import org.jabref.model.openoffice.ootext.OOFormat; +import org.jabref.model.openoffice.ootext.OOText; +import org.jabref.model.openoffice.style.CitationGroup; +import org.jabref.model.openoffice.style.CitationGroupId; +import org.jabref.model.openoffice.style.CitationGroups; +import org.jabref.model.openoffice.style.CitationPath; +import org.jabref.model.openoffice.style.CitedKey; +import org.jabref.model.openoffice.style.CitedKeys; + +public class OOFormatBibliography { + private static final OOPreFormatter POSTFORMATTER = new OOPreFormatter(); + private static final Field UNIQUEFIER_FIELD = new UnknownField("uniq"); + + private OOFormatBibliography() { + } + + /** + * @return The formatted bibliography, including its title. + */ + public static OOText formatBibliography(CitationGroups cgs, + CitedKeys bibliography, + OOBibStyle style, + boolean alwaysAddCitedOnPages) { + + OOText title = style.getFormattedBibliographyTitle(); + OOText body = formatBibliographyBody(cgs, bibliography, style, alwaysAddCitedOnPages); + return OOText.fromString(title.asString() + body.asString()); + } + + /** + * @return Formatted body of the bibliography. Excludes the title. + */ + public static OOText formatBibliographyBody(CitationGroups cgs, + CitedKeys bibliography, + OOBibStyle style, + boolean alwaysAddCitedOnPages) { + + StringBuilder stringBuilder = new StringBuilder(); + + for (CitedKey ck : bibliography.values()) { + OOText entryText = formatBibliographyEntry(cgs, ck, style, alwaysAddCitedOnPages); + stringBuilder.append(entryText.asString()); + } + + return OOText.fromString(stringBuilder.toString()); + } + + /** + * @return A paragraph. Includes label and "Cited on pages". + */ + public static OOText formatBibliographyEntry(CitationGroups cgs, + CitedKey ck, + OOBibStyle style, + boolean alwaysAddCitedOnPages) { + StringBuilder sb = new StringBuilder(); + + // insert marker "[1]" + if (style.isNumberEntries()) { + sb.append(style.getNumCitationMarkerForBibliography(ck).asString()); + } else { + // !style.isNumberEntries() : emit no prefix + // Note: We might want [citationKey] prefix for style.isCitationKeyCiteMarkers(); + } + + // Add entry body + sb.append(formatBibliographyEntryBody(ck, style).asString()); + + // Add "Cited on pages" + if (ck.getLookupResult().isEmpty() || alwaysAddCitedOnPages) { + sb.append(formatCitedOnPages(cgs, ck).asString()); + } + + // Add paragraph + OOText entryText = OOText.fromString(sb.toString()); + String parStyle = style.getReferenceParagraphFormat(); + return OOFormat.paragraph(entryText, parStyle); + } + + /** + * @return just the body of a bibliography entry. No label, "Cited on pages" or paragraph. + */ + public static OOText formatBibliographyEntryBody(CitedKey ck, OOBibStyle style) { + if (ck.getLookupResult().isEmpty()) { + // Unresolved entry + return OOText.fromString(String.format("Unresolved(%s)", ck.citationKey)); + } else { + // Resolved entry, use the layout engine + BibEntry bibentry = ck.getLookupResult().get().entry; + Layout layout = style.getReferenceFormat(bibentry.getType()); + layout.setPostFormatter(POSTFORMATTER); + + return formatFullReferenceOfBibEntry(layout, + bibentry, + ck.getLookupResult().get().database, + ck.getUniqueLetter().orElse(null)); + } + } + + /** + * Format the reference part of a bibliography entry using a Layout. + * + * @param layout The Layout to format the reference with. + * @param entry The entry to insert. + * @param database The database the entry belongs to. + * @param uniquefier Uniqiefier letter, if any, to append to the entry's year. + * + * @return OOText The reference part of a bibliography entry formatted as OOText + */ + private static OOText formatFullReferenceOfBibEntry(Layout layout, + BibEntry entry, + BibDatabase database, + String uniquefier) { + + // Backup the value of the uniq field, just in case the entry already has it: + Optional oldUniqVal = entry.getField(UNIQUEFIER_FIELD); + + // Set the uniq field with the supplied uniquefier: + if (uniquefier == null) { + entry.clearField(UNIQUEFIER_FIELD); + } else { + entry.setField(UNIQUEFIER_FIELD, uniquefier); + } + + // Do the layout for this entry: + OOText formattedText = OOText.fromString(layout.doLayout(entry, database)); + + // Afterwards, reset the old value: + if (oldUniqVal.isPresent()) { + entry.setField(UNIQUEFIER_FIELD, oldUniqVal.get()); + } else { + entry.clearField(UNIQUEFIER_FIELD); + } + + return formattedText; + } + + /** + * Format links to citations of the source (ck). + * + * Requires reference marks for the citation groups. + * + * - The links are created as references that show page numbers of the reference marks. + * - We do not control the text shown, that is provided by OpenOffice. + */ + private static OOText formatCitedOnPages(CitationGroups cgs, CitedKey ck) { + + if (!cgs.citationGroupsProvideReferenceMarkNameForLinking()) { + return OOText.fromString(""); + } + + StringBuilder sb = new StringBuilder(); + + String prefix = String.format(" (%s: ", Localization.lang("Cited on pages")); + String suffix = ")"; + sb.append(prefix); + + List citationGroups = new ArrayList<>(); + for (CitationPath p : ck.getCitationPaths()) { + CitationGroupId cgid = p.group; + CitationGroup cg = cgs.getCitationGroupOrThrow(cgid); + citationGroups.add(cg); + } + + // sort the citationGroups according to their indexInGlobalOrder + citationGroups.sort((a, b) -> { + Integer aa = a.getIndexInGlobalOrder().orElseThrow(RuntimeException::new); + Integer bb = b.getIndexInGlobalOrder().orElseThrow(RuntimeException::new); + return (aa.compareTo(bb)); + }); + + int i = 0; + for (CitationGroup cg : citationGroups) { + if (i > 0) { + sb.append(", "); + } + String markName = cg.getReferenceMarkNameForLinking().orElseThrow(RuntimeException::new); + OOText xref = OOFormat.formatReferenceToPageNumberOfReferenceMark(markName); + sb.append(xref.asString()); + i++; + } + sb.append(suffix); + return OOText.fromString(sb.toString()); + } + +} diff --git a/src/main/java/org/jabref/logic/openoffice/style/OOProcess.java b/src/main/java/org/jabref/logic/openoffice/style/OOProcess.java new file mode 100644 index 00000000000..6f7709aac10 --- /dev/null +++ b/src/main/java/org/jabref/logic/openoffice/style/OOProcess.java @@ -0,0 +1,90 @@ +package org.jabref.logic.openoffice.style; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +import org.jabref.logic.bibtex.comparator.FieldComparator; +import org.jabref.logic.bibtex.comparator.FieldComparatorStack; +import org.jabref.model.database.BibDatabase; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.field.StandardField; +import org.jabref.model.openoffice.style.CitationGroups; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class OOProcess { + + static final Comparator AUTHOR_YEAR_TITLE_COMPARATOR = makeAuthorYearTitleComparator(); + static final Comparator YEAR_AUTHOR_TITLE_COMPARATOR = makeYearAuthorTitleComparator(); + + private static final Logger LOGGER = LoggerFactory.getLogger(OOProcess.class); + + private static Comparator makeAuthorYearTitleComparator() { + FieldComparator a = new FieldComparator(StandardField.AUTHOR); + FieldComparator y = new FieldComparator(StandardField.YEAR); + FieldComparator t = new FieldComparator(StandardField.TITLE); + + List> ayt = new ArrayList<>(3); + ayt.add(a); + ayt.add(y); + ayt.add(t); + return new FieldComparatorStack<>(ayt); + } + + private static Comparator makeYearAuthorTitleComparator() { + FieldComparator y = new FieldComparator(StandardField.YEAR); + FieldComparator a = new FieldComparator(StandardField.AUTHOR); + FieldComparator t = new FieldComparator(StandardField.TITLE); + + List> yat = new ArrayList<>(3); + yat.add(y); + yat.add(a); + yat.add(t); + return new FieldComparatorStack<>(yat); + } + + /** + * The comparator used to sort within a group of merged + * citations. + * + * The term used here is "multicite". The option controlling the + * order is "MultiCiteChronological" in style files. + * + * Yes, they are always sorted one way or another. + */ + public static Comparator comparatorForMulticite(OOBibStyle style) { + if (style.getMultiCiteChronological()) { + return OOProcess.YEAR_AUTHOR_TITLE_COMPARATOR; + } else { + return OOProcess.AUTHOR_YEAR_TITLE_COMPARATOR; + } + } + + /** + * Fill cgs.bibliography and cgs.citationGroupsUnordered//CitationMarker + * according to style. + */ + public static void produceCitationMarkers(CitationGroups cgs, + List databases, + OOBibStyle style) { + + if (!cgs.hasGlobalOrder()) { + throw new RuntimeException("produceCitationMarkers: globalOrder is misssing in cgs"); + } + + cgs.lookupCitations(databases); + cgs.imposeLocalOrder(comparatorForMulticite(style)); + + // fill CitationGroup.citationMarker + if (style.isCitationKeyCiteMarkers()) { + OOProcessCitationKeyMarkers.produceCitationMarkers(cgs, style); + } else if (style.isNumberEntries()) { + OOProcessNumericMarkers.produceCitationMarkers(cgs, style); + } else { + OOProcessAuthorYearMarkers.produceCitationMarkers(cgs, style); + } + } + +} diff --git a/src/main/java/org/jabref/logic/openoffice/style/OOProcessAuthorYearMarkers.java b/src/main/java/org/jabref/logic/openoffice/style/OOProcessAuthorYearMarkers.java new file mode 100644 index 00000000000..a55d59107a8 --- /dev/null +++ b/src/main/java/org/jabref/logic/openoffice/style/OOProcessAuthorYearMarkers.java @@ -0,0 +1,162 @@ +package org.jabref.logic.openoffice.style; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import org.jabref.model.openoffice.ootext.OOText; +import org.jabref.model.openoffice.style.Citation; +import org.jabref.model.openoffice.style.CitationGroup; +import org.jabref.model.openoffice.style.CitationGroups; +import org.jabref.model.openoffice.style.CitationMarkerEntry; +import org.jabref.model.openoffice.style.CitationType; +import org.jabref.model.openoffice.style.CitedKey; +import org.jabref.model.openoffice.style.CitedKeys; +import org.jabref.model.openoffice.style.NonUniqueCitationMarker; +import org.jabref.model.openoffice.util.OOListUtil; + +class OOProcessAuthorYearMarkers { + + /** + * Fills {@code sortedCitedKeys//normCitMarker} + */ + private static void createNormalizedCitationMarkers(CitedKeys sortedCitedKeys, OOBibStyle style) { + + for (CitedKey ck : sortedCitedKeys.values()) { + ck.setNormalizedCitationMarker(Optional.of(style.getNormalizedCitationMarker(ck))); + } + } + + /** + * For each cited source make the citation keys unique by setting + * the uniqueLetter fields to letters ("a", "b") or Optional.empty() + * + * precondition: sortedCitedKeys already has normalized citation markers. + * precondition: sortedCitedKeys is sorted (according to the order we want the letters to be assigned) + * + * Expects to see data for all cited sources here. + * Clears uniqueLetters before filling. + * + * On return: Each citedKey in sortedCitedKeys has uniqueLetter set as needed. + * The same values are copied to the corresponding citations in cgs. + * + * Depends on: style, citations and their order. + */ + private static void createUniqueLetters(CitedKeys sortedCitedKeys, CitationGroups cgs) { + + // The entries in the clashingKeys lists preserve + // firstAppearance order from sortedCitedKeys.values(). + // + // The index of the citationKey in this order will decide + // which unique letter it receives. + // + Map> normCitMarkerToClachingKeys = new HashMap<>(); + for (CitedKey citedKey : sortedCitedKeys.values()) { + String normCitMarker = OOText.toString(citedKey.getNormalizedCitationMarker().get()); + String citationKey = citedKey.citationKey; + + if (!normCitMarkerToClachingKeys.containsKey(normCitMarker)) { + // Found new normCitMarker + List clashingKeys = new ArrayList<>(1); + normCitMarkerToClachingKeys.put(normCitMarker, clashingKeys); + clashingKeys.add(citationKey); + } else { + List clashingKeys = normCitMarkerToClachingKeys.get(normCitMarker); + if (!clashingKeys.contains(citationKey)) { + // First appearance of citationKey, add to list. + clashingKeys.add(citationKey); + } + } + } + + // Clear old uniqueLetter values. + for (CitedKey citedKey : sortedCitedKeys.values()) { + citedKey.setUniqueLetter(Optional.empty()); + } + + // For sets of citation keys figthing for a normCitMarker + // add unique letters to the year. + for (List clashingKeys : normCitMarkerToClachingKeys.values()) { + if (clashingKeys.size() <= 1) { + continue; // No fight, no letters. + } + // Multiple citation keys: they get their letters + // according to their order in clashingKeys. + int nextUniqueLetter = 'a'; + for (String citationKey : clashingKeys) { + String ul = String.valueOf((char) nextUniqueLetter); + sortedCitedKeys.get(citationKey).setUniqueLetter(Optional.of(ul)); + nextUniqueLetter++; + } + } + sortedCitedKeys.distributeUniqueLetters(cgs); + } + + /* *************************************** + * + * Calculate presentation of citation groups + * (create citMarkers) + * + * **************************************/ + + /** + * Set isFirstAppearanceOfSource in each citation. + * + * Preconditions: globalOrder, localOrder + */ + private static void setIsFirstAppearanceOfSourceInCitations(CitationGroups cgs) { + Set seenBefore = new HashSet<>(); + for (CitationGroup cg : cgs.getCitationGroupsInGlobalOrder()) { + for (Citation cit : cg.getCitationsInLocalOrder()) { + String currentKey = cit.citationKey; + if (!seenBefore.contains(currentKey)) { + cit.setIsFirstAppearanceOfSource(true); + seenBefore.add(currentKey); + } else { + cit.setIsFirstAppearanceOfSource(false); + } + } + } + } + + /** + * Produce citMarkers for normal + * (!isCitationKeyCiteMarkers && !isNumberEntries) styles. + * + * @param cgs + * @param style Bibliography style. + */ + static void produceCitationMarkers(CitationGroups cgs, OOBibStyle style) { + + assert !style.isCitationKeyCiteMarkers(); + assert !style.isNumberEntries(); + // Citations in (Au1, Au2 2000) form + + CitedKeys citedKeys = cgs.getCitedKeysSortedInOrderOfAppearance(); + + createNormalizedCitationMarkers(citedKeys, style); + createUniqueLetters(citedKeys, cgs); + cgs.createPlainBibliographySortedByComparator(OOProcess.AUTHOR_YEAR_TITLE_COMPARATOR); + + // Mark first appearance of each citationKey + setIsFirstAppearanceOfSourceInCitations(cgs); + + for (CitationGroup cg : cgs.getCitationGroupsInGlobalOrder()) { + + final boolean inParenthesis = (cg.citationType == CitationType.AUTHORYEAR_PAR); + final NonUniqueCitationMarker strictlyUnique = NonUniqueCitationMarker.THROWS; + + List cits = cg.getCitationsInLocalOrder(); + List citationMarkerEntries = OOListUtil.map(cits, e -> e); + OOText citMarker = style.createCitationMarker(citationMarkerEntries, + inParenthesis, + strictlyUnique); + cg.setCitationMarker(Optional.of(citMarker)); + } + } + +} diff --git a/src/main/java/org/jabref/logic/openoffice/style/OOProcessCitationKeyMarkers.java b/src/main/java/org/jabref/logic/openoffice/style/OOProcessCitationKeyMarkers.java new file mode 100644 index 00000000000..c63a4b3d0e4 --- /dev/null +++ b/src/main/java/org/jabref/logic/openoffice/style/OOProcessCitationKeyMarkers.java @@ -0,0 +1,30 @@ +package org.jabref.logic.openoffice.style; + +import java.util.Optional; + +import org.jabref.model.openoffice.ootext.OOText; +import org.jabref.model.openoffice.style.Citation; +import org.jabref.model.openoffice.style.CitationGroup; +import org.jabref.model.openoffice.style.CitationGroups; +import org.jabref.model.openoffice.util.OOListUtil; + +class OOProcessCitationKeyMarkers { + /** + * Produce citation markers for the case when the citation + * markers are the citation keys themselves, separated by commas. + */ + static void produceCitationMarkers(CitationGroups cgs, OOBibStyle style) { + + assert style.isCitationKeyCiteMarkers(); + + cgs.createPlainBibliographySortedByComparator(OOProcess.AUTHOR_YEAR_TITLE_COMPARATOR); + + for (CitationGroup cg : cgs.getCitationGroupsInGlobalOrder()) { + String citMarker = + style.getCitationGroupMarkupBefore() + + String.join(",", OOListUtil.map(cg.getCitationsInLocalOrder(), Citation::getCitationKey)) + + style.getCitationGroupMarkupAfter(); + cg.setCitationMarker(Optional.of(OOText.fromString(citMarker))); + } + } +} diff --git a/src/main/java/org/jabref/logic/openoffice/style/OOProcessNumericMarkers.java b/src/main/java/org/jabref/logic/openoffice/style/OOProcessNumericMarkers.java new file mode 100644 index 00000000000..4023ed0e028 --- /dev/null +++ b/src/main/java/org/jabref/logic/openoffice/style/OOProcessNumericMarkers.java @@ -0,0 +1,43 @@ +package org.jabref.logic.openoffice.style; + +import java.util.List; +import java.util.Optional; + +import org.jabref.model.openoffice.ootext.OOText; +import org.jabref.model.openoffice.style.CitationGroup; +import org.jabref.model.openoffice.style.CitationGroups; +import org.jabref.model.openoffice.style.CitationMarkerNumericEntry; +import org.jabref.model.openoffice.util.OOListUtil; + +class OOProcessNumericMarkers { + + /** + * Produce citation markers for the case of numbered citations + * with bibliography sorted by first appearance in the text. + * + * Numbered citation markers for each CitationGroup. + * Numbering is according to first appearance. + * Assumes global order and local order are already applied. + * + * @param cgs + * @param style + * + */ + static void produceCitationMarkers(CitationGroups cgs, OOBibStyle style) { + + assert style.isNumberEntries(); + + if (style.isSortByPosition()) { + cgs.createNumberedBibliographySortedInOrderOfAppearance(); + } else { + cgs.createNumberedBibliographySortedByComparator(OOProcess.AUTHOR_YEAR_TITLE_COMPARATOR); + } + + for (CitationGroup cg : cgs.getCitationGroupsInGlobalOrder()) { + List cits = OOListUtil.map(cg.getCitationsInLocalOrder(), e -> e); + OOText citMarker = style.getNumCitationMarker2(cits); + cg.setCitationMarker(Optional.of(citMarker)); + } + } + +} diff --git a/src/main/java/org/jabref/logic/openoffice/style/StyleLoader.java b/src/main/java/org/jabref/logic/openoffice/style/StyleLoader.java index 6d7651a24de..727db88cbc4 100644 --- a/src/main/java/org/jabref/logic/openoffice/style/StyleLoader.java +++ b/src/main/java/org/jabref/logic/openoffice/style/StyleLoader.java @@ -92,7 +92,7 @@ private void loadExternalStyles() { } } catch (FileNotFoundException e) { // The file couldn't be found... should we tell anyone? - LOGGER.info("Cannot find external style file " + filename, e); + LOGGER.info("Cannot find external style file " + filename); } catch (IOException e) { LOGGER.info("Problem reading external style file " + filename, e); } diff --git a/src/main/java/org/jabref/model/openoffice/style/Citation.java b/src/main/java/org/jabref/model/openoffice/style/Citation.java new file mode 100644 index 00000000000..f0b732a9240 --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/style/Citation.java @@ -0,0 +1,142 @@ +package org.jabref.model.openoffice.style; + +import java.util.List; +import java.util.Optional; + +import org.jabref.model.database.BibDatabase; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.openoffice.ootext.OOText; +import org.jabref.model.openoffice.util.OOPair; + +public class Citation implements ComparableCitation, CitationMarkerEntry, CitationMarkerNumericEntry { + + /** key in database */ + public final String citationKey; + + /** Result from database lookup. Optional.empty() if not found. */ + private Optional db; + + /** The number used for numbered citation styles . */ + private Optional number; + + /** Letter that makes the in-text citation unique. */ + private Optional uniqueLetter; + + /** pageInfo */ + private Optional pageInfo; + + /** isFirstAppearanceOfSource */ + private boolean isFirstAppearanceOfSource; + + /** + * + */ + public Citation(String citationKey) { + this.citationKey = citationKey; + this.db = Optional.empty(); + this.number = Optional.empty(); + this.uniqueLetter = Optional.empty(); + this.pageInfo = Optional.empty(); + this.isFirstAppearanceOfSource = false; + } + + @Override + public String getCitationKey() { + return citationKey; + } + + @Override + public Optional getPageInfo() { + return pageInfo; + } + + @Override + public boolean getIsFirstAppearanceOfSource() { + return isFirstAppearanceOfSource; + } + + @Override + public Optional getBibEntry() { + return (db.isPresent() + ? Optional.of(db.get().entry) + : Optional.empty()); + } + + public static Optional lookup(List databases, String key) { + for (BibDatabase database : databases) { + Optional entry = database.getEntryByCitationKey(key); + if (entry.isPresent()) { + return Optional.of(new CitationLookupResult(entry.get(), database)); + } + } + return Optional.empty(); + } + + public void lookupInDatabases(List databases) { + db = Citation.lookup(databases, citationKey); + } + + public Optional getLookupResult() { + return db; + } + + public void setLookupResult(Optional db) { + this.db = db; + } + + public boolean isUnresolved() { + return db.isEmpty(); + } + + @Override + public Optional getNumber() { + return number; + } + + public void setNumber(Optional n) { + number = n; + } + + public int getNumberOrThrow() { + return number.get(); + } + + public Optional getUniqueLetter() { + return uniqueLetter; + } + + public void setUniqueLetter(Optional uniqueLetter) { + this.uniqueLetter = uniqueLetter; + } + + public void setPageInfo(Optional v) { + Optional vv = PageInfo.normalizePageInfo(v); + if (!vv.equals(v)) { + throw new RuntimeException("setPageInfo argument is not normalized"); + } + this.pageInfo = vv; + } + + public void setIsFirstAppearanceOfSource(boolean value) { + isFirstAppearanceOfSource = value; + } + + /* + * Setters for CitationGroups.distribute() + */ + public static void setLookupResult(OOPair> x) { + Citation cit = x.a; + cit.db = x.b; + } + + public static void setNumber(OOPair> x) { + Citation cit = x.a; + cit.number = x.b; + } + + public static void setUniqueLetter(OOPair> x) { + Citation cit = x.a; + cit.uniqueLetter = x.b; + } + +} diff --git a/src/main/java/org/jabref/model/openoffice/style/CitationGroup.java b/src/main/java/org/jabref/model/openoffice/style/CitationGroup.java new file mode 100644 index 00000000000..075c55cc2ef --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/style/CitationGroup.java @@ -0,0 +1,157 @@ +package org.jabref.model.openoffice.style; + +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Optional; + +import org.jabref.model.entry.BibEntry; +import org.jabref.model.openoffice.ootext.OOText; +import org.jabref.model.openoffice.util.OOListUtil; + +/** + * A CitationGroup describes a group of citations. + */ +public class CitationGroup { + + public final OODataModel dataModel; + + /* + * Identifies this citation group. + */ + public final CitationGroupId cgid; + + /* + * The core data, stored in the document: + * The type of citation and citations in storage order. + */ + public final CitationType citationType; + public final List citationsInStorageOrder; + + /* + * Extra data + */ + + /* + * A name of a reference mark to link to by formatCitedOnPages. + * May be initially empty, if backend does not use reference marks. + * + * produceCitationMarkers might want fill it to support + * cross-references to citation groups from the bibliography. + */ + private Optional referenceMarkNameForLinking; + + /* + * Indices into citations: citations[localOrder[i]] provides ith + * citation according to the currently imposed local order for + * presentation. + * + * Initialized to (0..(nCitations-1)) in the constructor. + */ + private List localOrder; + + /* + * "Cited on pages" uses this to sort the cross-references. + */ + private Optional indexInGlobalOrder; + + /* + * Citation marker. + */ + private Optional citationMarker; + + public CitationGroup(OODataModel dataModel, + CitationGroupId cgid, + CitationType citationType, + List citationsInStorageOrder, + Optional referenceMarkNameForLinking) { + this.dataModel = dataModel; + this.cgid = cgid; + this.citationType = citationType; + this.citationsInStorageOrder = Collections.unmodifiableList(citationsInStorageOrder); + this.localOrder = OOListUtil.makeIndices(citationsInStorageOrder.size()); + this.referenceMarkNameForLinking = referenceMarkNameForLinking; + this.indexInGlobalOrder = Optional.empty(); + this.citationMarker = Optional.empty(); + } + + public int numberOfCitations() { + return citationsInStorageOrder.size(); + } + + /* + * localOrder + */ + + /** + * Sort citations for presentation within a CitationGroup. + */ + void imposeLocalOrder(Comparator entryComparator) { + + // For JabRef52 the single pageInfo is always in the last-in-localorder citation. + // We adjust here accordingly by taking it out and adding it back after sorting. + final int last = this.numberOfCitations() - 1; + Optional lastPageInfo = Optional.empty(); + if (dataModel == OODataModel.JabRef52) { + Citation lastCitation = getCitationsInLocalOrder().get(last); + lastPageInfo = lastCitation.getPageInfo(); + lastCitation.setPageInfo(Optional.empty()); + } + + this.localOrder = OOListUtil.order(citationsInStorageOrder, + new CompareCitation(entryComparator, true)); + + if (dataModel == OODataModel.JabRef52) { + getCitationsInLocalOrder().get(last).setPageInfo(lastPageInfo); + } + } + + public List getLocalOrder() { + return Collections.unmodifiableList(localOrder); + } + + /* + * citations + */ + + public List getCitationsInLocalOrder() { + return OOListUtil.map(localOrder, i -> citationsInStorageOrder.get(i)); + } + + /* + * indexInGlobalOrder + */ + + public void setIndexInGlobalOrder(Optional i) { + this.indexInGlobalOrder = i; + } + + public Optional getIndexInGlobalOrder() { + return this.indexInGlobalOrder; + } + + /* + * referenceMarkNameForLinking + */ + + public Optional getReferenceMarkNameForLinking() { + return referenceMarkNameForLinking; + } + + public void setReferenceMarkNameForLinking(Optional referenceMarkNameForLinking) { + this.referenceMarkNameForLinking = referenceMarkNameForLinking; + } + + /* + * citationMarker + */ + + public void setCitationMarker(Optional citationMarker) { + this.citationMarker = citationMarker; + } + + public Optional getCitationMarker() { + return this.citationMarker; + } + +} diff --git a/src/main/java/org/jabref/model/openoffice/style/CitationGroupId.java b/src/main/java/org/jabref/model/openoffice/style/CitationGroupId.java new file mode 100644 index 00000000000..bac1e3c1387 --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/style/CitationGroupId.java @@ -0,0 +1,19 @@ +package org.jabref.model.openoffice.style; + +/** + * Identifies a citation group in a document. + */ +public class CitationGroupId { + String id; + public CitationGroupId(String id) { + this.id = id; + } + + /** + * CitationEntry needs some string identifying the group + * that it can pass back later. + */ + public String citationGroupIdAsString() { + return id; + } +} diff --git a/src/main/java/org/jabref/model/openoffice/style/CitationGroups.java b/src/main/java/org/jabref/model/openoffice/style/CitationGroups.java new file mode 100644 index 00000000000..f68aa4f115c --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/style/CitationGroups.java @@ -0,0 +1,315 @@ +package org.jabref.model.openoffice.style; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Consumer; + +import org.jabref.model.database.BibDatabase; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.openoffice.util.OOListUtil; +import org.jabref.model.openoffice.util.OOPair; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * CitationGroups : the set of citation groups in the document. + * + * This is the main input (as well as output) for creating citation + * markers and bibliography. + * + */ +public class CitationGroups { + + private static final Logger LOGGER = LoggerFactory.getLogger(CitationGroups.class); + + private Map citationGroupsUnordered; + + /** + * Provides order of appearance for the citation groups. + */ + private Optional> globalOrder; + + /** + * This is going to be the bibliography + */ + private Optional bibliography; + + /** + * Constructor + */ + public CitationGroups(Map citationGroups) { + + this.citationGroupsUnordered = citationGroups; + + this.globalOrder = Optional.empty(); + this.bibliography = Optional.empty(); + } + + public int numberOfCitationGroups() { + return citationGroupsUnordered.size(); + } + + /** + * For each citation in {@code where} + * call {@code fun.accept(new Pair(citation, value));} + */ + public void distributeToCitations(List where, + Consumer> fun, + T value) { + + for (CitationPath p : where) { + CitationGroup cg = citationGroupsUnordered.get(p.group); + if (cg == null) { + LOGGER.warn("CitationGroups.distributeToCitations: group missing"); + continue; + } + Citation cit = cg.citationsInStorageOrder.get(p.storageIndexInGroup); + fun.accept(new OOPair<>(cit, value)); + } + } + + /* + * Look up each Citation in databases. + */ + public void lookupCitations(List databases) { + /* + * It is not clear which of the two solutions below is better. + */ + if (true) { + // collect-lookup-distribute + // + // CitationDatabaseLookupResult for the same citation key + // is the same object. Until we insert a new citation from the GUI. + CitedKeys cks = getCitedKeysUnordered(); + cks.lookupInDatabases(databases); + cks.distributeLookupResults(this); + } else { + // lookup each citation directly + // + // CitationDatabaseLookupResult for the same citation key + // may be a different object: CitedKey.addPath has to use equals, + // so CitationDatabaseLookupResult has to override Object.equals, + // which depends on BibEntry.equals and BibDatabase.equals + // doing the right thing. Seems to work. But what we gained + // from avoiding collect-and-distribute may be lost in more + // complicated consistency checking in addPath. + // + for (CitationGroup cg : getCitationGroupsUnordered()) { + for (Citation cit : cg.citationsInStorageOrder) { + cit.lookupInDatabases(databases); + } + } + } + } + + public List getCitationGroupsUnordered() { + return new ArrayList<>(citationGroupsUnordered.values()); + } + + /** + * Citation groups in {@code globalOrder} + */ + public List getCitationGroupsInGlobalOrder() { + if (globalOrder.isEmpty()) { + throw new RuntimeException("getCitationGroupsInGlobalOrder: not ordered yet"); + } + return OOListUtil.map(globalOrder.get(), cgid -> citationGroupsUnordered.get(cgid)); + } + + /** + * Impose an order of citation groups by providing the order + * of their citation group idendifiers. + * + * Also set indexInGlobalOrder for each citation group. + */ + public void setGlobalOrder(List globalOrder) { + Objects.requireNonNull(globalOrder); + if (globalOrder.size() != numberOfCitationGroups()) { + throw new RuntimeException("setGlobalOrder:" + + " globalOrder.size() != numberOfCitationGroups()"); + } + this.globalOrder = Optional.of(globalOrder); + + // Propagate to each CitationGroup + int i = 0; + for (CitationGroupId cgid : globalOrder) { + citationGroupsUnordered.get(cgid).setIndexInGlobalOrder(Optional.of(i)); + i++; + } + } + + public boolean hasGlobalOrder() { + return globalOrder.isPresent(); + } + + /** + * Impose an order for citations within each group. + */ + public void imposeLocalOrder(Comparator entryComparator) { + for (CitationGroup cg : citationGroupsUnordered.values()) { + cg.imposeLocalOrder(entryComparator); + } + } + + /** + * Collect citations into a list of cited sources using neither + * CitationGroup.globalOrder or Citation.localOrder + */ + public CitedKeys getCitedKeysUnordered() { + LinkedHashMap res = new LinkedHashMap<>(); + for (CitationGroup cg : citationGroupsUnordered.values()) { + int storageIndexInGroup = 0; + for (Citation cit : cg.citationsInStorageOrder) { + String key = cit.citationKey; + CitationPath p = new CitationPath(cg.cgid, storageIndexInGroup); + if (res.containsKey(key)) { + res.get(key).addPath(p, cit); + } else { + res.put(key, new CitedKey(key, p, cit)); + } + storageIndexInGroup++; + } + } + return new CitedKeys(res); + } + + /** + * CitedKeys created iterating citations in (globalOrder,localOrder) + */ + public CitedKeys getCitedKeysSortedInOrderOfAppearance() { + LinkedHashMap res = new LinkedHashMap<>(); + if (!hasGlobalOrder()) { + throw new RuntimeException("getSortedCitedKeys: no globalOrder"); + } + for (CitationGroup cg : getCitationGroupsInGlobalOrder()) { + for (int i : cg.getLocalOrder()) { + Citation cit = cg.citationsInStorageOrder.get(i); + String citationKey = cit.citationKey; + CitationPath p = new CitationPath(cg.cgid, i); + if (res.containsKey(citationKey)) { + res.get(citationKey).addPath(p, cit); + } else { + res.put(citationKey, new CitedKey(citationKey, p, cit)); + } + } + } + return new CitedKeys(res); + } + + public Optional getBibliography() { + return bibliography; + } + + /** + * @return Citation keys where lookupCitations() failed. + */ + public List getUnresolvedKeys() { + + CitedKeys bib = getBibliography().orElse(getCitedKeysUnordered()); + + List unresolvedKeys = new ArrayList<>(); + for (CitedKey ck : bib.values()) { + if (ck.getLookupResult().isEmpty()) { + unresolvedKeys.add(ck.citationKey); + } + } + return unresolvedKeys; + } + + public void createNumberedBibliographySortedInOrderOfAppearance() { + if (!bibliography.isEmpty()) { + throw new RuntimeException("createNumberedBibliographySortedInOrderOfAppearance:" + + " already have a bibliography"); + } + CitedKeys citedKeys = getCitedKeysSortedInOrderOfAppearance(); + citedKeys.numberCitedKeysInCurrentOrder(); + citedKeys.distributeNumbers(this); + bibliography = Optional.of(citedKeys); + } + + /** + * precondition: database lookup already performed (otherwise we just sort citation keys) + */ + public void createPlainBibliographySortedByComparator(Comparator entryComparator) { + if (!bibliography.isEmpty()) { + throw new RuntimeException("createPlainBibliographySortedByComparator:" + + " already have a bibliography"); + } + CitedKeys citedKeys = getCitedKeysUnordered(); + citedKeys.sortByComparator(entryComparator); + bibliography = Optional.of(citedKeys); + } + + /** + * precondition: database lookup already performed (otherwise we just sort citation keys) + */ + public void createNumberedBibliographySortedByComparator(Comparator entryComparator) { + if (!bibliography.isEmpty()) { + throw new RuntimeException("createNumberedBibliographySortedByComparator:" + + " already have a bibliography"); + } + CitedKeys citedKeys = getCitedKeysUnordered(); + citedKeys.sortByComparator(entryComparator); + citedKeys.numberCitedKeysInCurrentOrder(); + citedKeys.distributeNumbers(this); + bibliography = Optional.of(citedKeys); + } + + /* + * Query by CitationGroupId + */ + + public Optional getCitationGroup(CitationGroupId cgid) { + CitationGroup cg = citationGroupsUnordered.get(cgid); + return Optional.ofNullable(cg); + } + + /** + * Call this when the citation group is unquestionably there. + */ + public CitationGroup getCitationGroupOrThrow(CitationGroupId cgid) { + CitationGroup cg = citationGroupsUnordered.get(cgid); + if (cg == null) { + throw new RuntimeException("getCitationGroupOrThrow:" + + " the requested CitationGroup is not available"); + } + return cg; + } + + /* + * @return true if all citation groups have referenceMarkNameForLinking + */ + public boolean citationGroupsProvideReferenceMarkNameForLinking() { + for (CitationGroup cg : citationGroupsUnordered.values()) { + if (cg.getReferenceMarkNameForLinking().isEmpty()) { + return false; + } + } + return true; + } + + /* + * Callbacks. + */ + + public void afterCreateCitationGroup(CitationGroup cg) { + citationGroupsUnordered.put(cg.cgid, cg); + + globalOrder = Optional.empty(); + bibliography = Optional.empty(); + } + + public void afterRemoveCitationGroup(CitationGroup cg) { + citationGroupsUnordered.remove(cg.cgid); + globalOrder.map(l -> l.remove(cg.cgid)); + + bibliography = Optional.empty(); + } + +} diff --git a/src/main/java/org/jabref/model/openoffice/style/CitationLookupResult.java b/src/main/java/org/jabref/model/openoffice/style/CitationLookupResult.java new file mode 100644 index 00000000000..866a0de34c3 --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/style/CitationLookupResult.java @@ -0,0 +1,49 @@ +package org.jabref.model.openoffice.style; + +import java.util.Objects; + +import org.jabref.model.database.BibDatabase; +import org.jabref.model.entry.BibEntry; + +public class CitationLookupResult { + + public final BibEntry entry; + public final BibDatabase database; + + public CitationLookupResult(BibEntry entry, BibDatabase database) { + Objects.requireNonNull(entry); + Objects.requireNonNull(database); + this.entry = entry; + this.database = database; + } + + /** + * Note: BibEntry overrides Object.equals, but BibDatabase does not. + * + * Consequently, {@code this.database.equals(that.database)} below + * is equivalent to {@code this.database == that.database}. + * + * Since within each GUI call we use a fixed list of + * databases, it is OK. + */ + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + if (!(o instanceof CitationLookupResult)) { + return false; + } + CitationLookupResult that = (CitationLookupResult) o; + return this.entry.equals(that.entry) && this.database.equals(that.database); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + entry.hashCode(); + result = prime * result + database.hashCode(); + return result; + } +} diff --git a/src/main/java/org/jabref/model/openoffice/style/CitationMarkerEntry.java b/src/main/java/org/jabref/model/openoffice/style/CitationMarkerEntry.java new file mode 100644 index 00000000000..4ab2c626b68 --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/style/CitationMarkerEntry.java @@ -0,0 +1,30 @@ +package org.jabref.model.openoffice.style; + +import java.util.Optional; + +import org.jabref.model.openoffice.ootext.OOText; + +/** + * This is what we need for createCitationMarker to produce author-year + * citation markers. + */ +public interface CitationMarkerEntry extends CitationMarkerNormEntry { + + /** + * uniqueLetter or Optional.empty() if not needed. + */ + Optional getUniqueLetter(); + + /** + * pageInfo for this citation, provided by the user. + * May be empty, for none. + */ + Optional getPageInfo(); + + /** + * @return true if this citation is the first appearance of the + * source cited. Some styles use different limit on the number of + * authors shown in this case. + */ + boolean getIsFirstAppearanceOfSource(); +} diff --git a/src/main/java/org/jabref/model/openoffice/style/CitationMarkerNormEntry.java b/src/main/java/org/jabref/model/openoffice/style/CitationMarkerNormEntry.java new file mode 100644 index 00000000000..e9df5ec961f --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/style/CitationMarkerNormEntry.java @@ -0,0 +1,24 @@ +package org.jabref.model.openoffice.style; + +import java.util.Optional; + +/** + * This is what we need to produce normalized author-year citation + * markers. + */ +public interface CitationMarkerNormEntry { + + /** Citation key. This is what we usually get from the document. + * + * Used if getBibEntry() and/or getDatabase() returns + * empty, which indicates failure to lookup in the databases. + * + */ + String getCitationKey(); + + /** Result of looking up citation key in databases. + * + * Optional.empty() indicates unresolved citation. + */ + Optional getLookupResult(); +} diff --git a/src/main/java/org/jabref/model/openoffice/style/CitationMarkerNumericBibEntry.java b/src/main/java/org/jabref/model/openoffice/style/CitationMarkerNumericBibEntry.java new file mode 100644 index 00000000000..398282113b8 --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/style/CitationMarkerNumericBibEntry.java @@ -0,0 +1,19 @@ +package org.jabref.model.openoffice.style; + +import java.util.Optional; + +/** + * This is for the numeric bibliography labels. + */ +public interface CitationMarkerNumericBibEntry { + + /** + * For unresolved citation we show the citation key. + */ + String getCitationKey(); + + /** + * @return Optional.empty() for unresolved + */ + Optional getNumber(); +} diff --git a/src/main/java/org/jabref/model/openoffice/style/CitationMarkerNumericEntry.java b/src/main/java/org/jabref/model/openoffice/style/CitationMarkerNumericEntry.java new file mode 100644 index 00000000000..9c0c6081489 --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/style/CitationMarkerNumericEntry.java @@ -0,0 +1,20 @@ +package org.jabref.model.openoffice.style; + +import java.util.Optional; + +import org.jabref.model.openoffice.ootext.OOText; + +/** + * This is what we need for numeric citation markers. + */ +public interface CitationMarkerNumericEntry { + + String getCitationKey(); + + /** + * @return Optional.empty() for unresolved + */ + Optional getNumber(); + + Optional getPageInfo(); +} diff --git a/src/main/java/org/jabref/model/openoffice/style/CitationPath.java b/src/main/java/org/jabref/model/openoffice/style/CitationPath.java new file mode 100644 index 00000000000..05aeb6547f6 --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/style/CitationPath.java @@ -0,0 +1,17 @@ +package org.jabref.model.openoffice.style; + +/** + * Identifies a citation with the citation group containing it and + * its storage index within. + */ +public class CitationPath { + + public final CitationGroupId group; + + public final int storageIndexInGroup; + + CitationPath(CitationGroupId group, int storageIndexInGroup) { + this.group = group; + this.storageIndexInGroup = storageIndexInGroup; + } +} diff --git a/src/main/java/org/jabref/model/openoffice/style/CitationType.java b/src/main/java/org/jabref/model/openoffice/style/CitationType.java new file mode 100644 index 00000000000..14189e903e3 --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/style/CitationType.java @@ -0,0 +1,24 @@ +package org.jabref.model.openoffice.style; + +/* + * Presentation types of citation groups. + */ +public enum CitationType { + + AUTHORYEAR_PAR, + AUTHORYEAR_INTEXT, + INVISIBLE_CIT; + + public boolean inParenthesis() { + return switch (this) { + case AUTHORYEAR_PAR, INVISIBLE_CIT -> true; + case AUTHORYEAR_INTEXT -> false; + }; + } + + public boolean withText() { + return (this != INVISIBLE_CIT); + } +} + + diff --git a/src/main/java/org/jabref/model/openoffice/style/CitedKey.java b/src/main/java/org/jabref/model/openoffice/style/CitedKey.java new file mode 100644 index 00000000000..08b2ffb1bb8 --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/style/CitedKey.java @@ -0,0 +1,141 @@ +package org.jabref.model.openoffice.style; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import org.jabref.model.database.BibDatabase; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.openoffice.ootext.OOText; + +/** + * Cited keys are collected from the citations in citation groups. + * + * They contain backreferences to the corresponding citations in + * {@code where}. This allows the extra information generated using + * CitedKeys to be distributed back to the in-text citations. + */ +public class CitedKey implements + ComparableCitedKey, + CitationMarkerNormEntry, + CitationMarkerNumericBibEntry { + + public final String citationKey; + private final List where; + + private Optional db; + private Optional number; // For Numbered citation styles. + private Optional uniqueLetter; // For AuthorYear citation styles. + private Optional normCitMarker; // For AuthorYear citation styles. + + CitedKey(String citationKey, CitationPath p, Citation cit) { + + this.citationKey = citationKey; + this.where = new ArrayList<>(); // remember order + this.where.add(p); + + // synchronized with Citation + this.db = cit.getLookupResult(); + this.number = cit.getNumber(); + this.uniqueLetter = cit.getUniqueLetter(); + + // CitedKey only + this.normCitMarker = Optional.empty(); + } + + /* + * Implement ComparableCitedKey + */ + @Override + public String getCitationKey() { + return citationKey; + } + + @Override + public Optional getBibEntry() { + return (db.isPresent() + ? Optional.of(db.get().entry) + : Optional.empty()); + } + + /* + * Implement CitationMarkerNormEntry + */ + @Override + public Optional getLookupResult() { + return db; + } + + /* + * Implement CitationMarkerNumericBibEntry + */ + @Override + public Optional getNumber() { + return number; + } + + public void setNumber(Optional number) { + this.number = number; + } + + public List getCitationPaths() { + return new ArrayList<>(where); + } + + public Optional getUniqueLetter() { + return uniqueLetter; + } + + public void setUniqueLetter(Optional uniqueLetter) { + this.uniqueLetter = uniqueLetter; + } + + public Optional getNormalizedCitationMarker() { + return normCitMarker; + } + + public void setNormalizedCitationMarker(Optional normCitMarker) { + this.normCitMarker = normCitMarker; + } + + /** + * Appends to end of {@code where} + */ + void addPath(CitationPath p, Citation cit) { + this.where.add(p); + + // Check consistency + if (!cit.getLookupResult().equals(this.db)) { + throw new RuntimeException("CitedKey.addPath: mismatch on cit.db"); + } + if (!cit.getNumber().equals(this.number)) { + throw new RuntimeException("CitedKey.addPath: mismatch on cit.number"); + } + if (!cit.getUniqueLetter().equals(this.uniqueLetter)) { + throw new RuntimeException("CitedKey.addPath: mismatch on cit.uniqueLetter"); + } + } + + /* + * Lookup + */ + void lookupInDatabases(List databases) { + this.db = Citation.lookup(databases, this.citationKey); + } + + void distributeLookupResult(CitationGroups cgs) { + cgs.distributeToCitations(where, Citation::setLookupResult, db); + } + + /* + * Make unique using a letter or by numbering + */ + + void distributeNumber(CitationGroups cgs) { + cgs.distributeToCitations(where, Citation::setNumber, number); + } + + void distributeUniqueLetter(CitationGroups cgs) { + cgs.distributeToCitations(where, Citation::setUniqueLetter, uniqueLetter); + } +} diff --git a/src/main/java/org/jabref/model/openoffice/style/CitedKeys.java b/src/main/java/org/jabref/model/openoffice/style/CitedKeys.java new file mode 100644 index 00000000000..898fadbebe9 --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/style/CitedKeys.java @@ -0,0 +1,84 @@ +package org.jabref.model.openoffice.style; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Optional; + +import org.jabref.model.database.BibDatabase; +import org.jabref.model.entry.BibEntry; + +public class CitedKeys { + + /** + * Order-preserving map from citation keys to associated data. + */ + private LinkedHashMap data; + + CitedKeys(LinkedHashMap data) { + this.data = data; + } + + /** + * The cited keys in their current order. + */ + public List values() { + return new ArrayList<>(data.values()); + } + + public CitedKey get(String citationKey) { + return data.get(citationKey); + } + + /** + * Sort entries for the bibliography. + */ + void sortByComparator(Comparator entryComparator) { + List cks = new ArrayList<>(data.values()); + cks.sort(new CompareCitedKey(entryComparator, true)); + LinkedHashMap newData = new LinkedHashMap<>(); + for (CitedKey ck : cks) { + newData.put(ck.citationKey, ck); + } + data = newData; + } + + void numberCitedKeysInCurrentOrder() { + int i = 1; + for (CitedKey ck : data.values()) { + if (ck.getLookupResult().isPresent()) { + ck.setNumber(Optional.of(i)); + i++; + } else { + // Unresolved citations do not get a number. + ck.setNumber(Optional.empty()); + } + } + } + + public void lookupInDatabases(List databases) { + for (CitedKey ck : this.data.values()) { + ck.lookupInDatabases(databases); + } + } + + void distributeLookupResults(CitationGroups cgs) { + for (CitedKey ck : this.data.values()) { + ck.distributeLookupResult(cgs); + } + } + + void distributeNumbers(CitationGroups cgs) { + for (CitedKey ck : this.data.values()) { + ck.distributeNumber(cgs); + } + } + + public void distributeUniqueLetters(CitationGroups cgs) { + for (CitedKey ck : this.data.values()) { + ck.distributeUniqueLetter(cgs); + } + } + +} diff --git a/src/main/java/org/jabref/model/openoffice/style/ComparableCitation.java b/src/main/java/org/jabref/model/openoffice/style/ComparableCitation.java new file mode 100644 index 00000000000..a6ac799b017 --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/style/ComparableCitation.java @@ -0,0 +1,13 @@ +package org.jabref.model.openoffice.style; + +import java.util.Optional; + +import org.jabref.model.openoffice.ootext.OOText; + +/** + * When sorting citations (in a group), we also consider pageInfo. + * Otherwise we sort citations as cited keys. + */ +public interface ComparableCitation extends ComparableCitedKey { + public Optional getPageInfo(); +} diff --git a/src/main/java/org/jabref/model/openoffice/style/ComparableCitedKey.java b/src/main/java/org/jabref/model/openoffice/style/ComparableCitedKey.java new file mode 100644 index 00000000000..0d4c8e2efce --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/style/ComparableCitedKey.java @@ -0,0 +1,16 @@ +package org.jabref.model.openoffice.style; + +import java.util.Optional; + +import org.jabref.model.entry.BibEntry; + +/** + * This is what we need to sort bibliography entries. + */ +public interface ComparableCitedKey { + + public String getCitationKey(); + + public Optional getBibEntry(); +} + diff --git a/src/main/java/org/jabref/model/openoffice/style/CompareCitation.java b/src/main/java/org/jabref/model/openoffice/style/CompareCitation.java new file mode 100644 index 00000000000..916f8ec610c --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/style/CompareCitation.java @@ -0,0 +1,30 @@ +package org.jabref.model.openoffice.style; + +import java.util.Comparator; + +import org.jabref.model.entry.BibEntry; + +/* + * Given a Comparator provide a Comparator + * that can handle unresolved citation keys and takes pageInfo into account. + */ +public class CompareCitation implements Comparator { + + private CompareCitedKey citedKeyComparator; + + CompareCitation(Comparator entryComparator, boolean unresolvedComesFirst) { + this.citedKeyComparator = new CompareCitedKey(entryComparator, unresolvedComesFirst); + } + + public int compare(ComparableCitation a, ComparableCitation b) { + int res = citedKeyComparator.compare(a, b); + + // Also consider pageInfo + if (res == 0) { + res = PageInfo.comparePageInfo(a.getPageInfo(), b.getPageInfo()); + } + return res; + } +} + + diff --git a/src/main/java/org/jabref/model/openoffice/style/CompareCitedKey.java b/src/main/java/org/jabref/model/openoffice/style/CompareCitedKey.java new file mode 100644 index 00000000000..439d0556c2d --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/style/CompareCitedKey.java @@ -0,0 +1,41 @@ +package org.jabref.model.openoffice.style; + +import java.util.Comparator; +import java.util.Optional; + +import org.jabref.model.entry.BibEntry; + +/* + * Given a Comparator provide a Comparator + * that also handles unresolved citation keys. + */ +public class CompareCitedKey implements Comparator { + + Comparator entryComparator; + boolean unresolvedComesFirst; + + CompareCitedKey(Comparator entryComparator, boolean unresolvedComesFirst) { + this.entryComparator = entryComparator; + this.unresolvedComesFirst = unresolvedComesFirst; + } + + public int compare(ComparableCitedKey a, ComparableCitedKey b) { + Optional aBibEntry = a.getBibEntry(); + Optional bBibEntry = b.getBibEntry(); + final int mul = unresolvedComesFirst ? (+1) : (-1); + + int res = 0; + if (aBibEntry.isEmpty() && bBibEntry.isEmpty()) { + // Both are unresolved: compare them by citation key. + res = a.getCitationKey().compareTo(b.getCitationKey()); + } else if (aBibEntry.isEmpty()) { + return -mul; + } else if (bBibEntry.isEmpty()) { + return mul; + } else { + // Proper comparison of entries + res = entryComparator.compare(aBibEntry.get(), bBibEntry.get()); + } + return res; + } +} diff --git a/src/main/java/org/jabref/model/openoffice/style/NonUniqueCitationMarker.java b/src/main/java/org/jabref/model/openoffice/style/NonUniqueCitationMarker.java new file mode 100644 index 00000000000..e7d988ee216 --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/style/NonUniqueCitationMarker.java @@ -0,0 +1,15 @@ +package org.jabref.model.openoffice.style; + +/** + * What should createCitationMarker do if it discovers that + * uniqueLetters provided are not sufficient for unique presentation? + */ +public enum NonUniqueCitationMarker { + + /** Give an insufficient representation anyway. */ + FORGIVEN, + + /** Throw a RuntimeException */ + THROWS +} + diff --git a/src/main/java/org/jabref/model/openoffice/style/OODataModel.java b/src/main/java/org/jabref/model/openoffice/style/OODataModel.java new file mode 100644 index 00000000000..50c3a9e4842 --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/style/OODataModel.java @@ -0,0 +1,38 @@ +package org.jabref.model.openoffice.style; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import org.jabref.model.openoffice.ootext.OOText; + +/** What is the data stored? */ +public enum OODataModel { + + /** + * JabRef52: pageInfo belongs to CitationGroup, not Citation. + */ + JabRef52, + + /** + * JabRef53: pageInfo belongs to Citation. + */ + JabRef53; + + /** + * @param pageInfo Nullable. + * @return JabRef53 style pageInfo list with pageInfo in the last slot. + */ + public static List> fakePageInfos(String pageInfo, int nCitations) { + List> pageInfos = new ArrayList<>(nCitations); + for (int i = 0; i < nCitations; i++) { + pageInfos.add(Optional.empty()); + } + if (pageInfo != null) { + final int last = nCitations - 1; + Optional pi = Optional.ofNullable(OOText.fromString(pageInfo)); + pageInfos.set(last, PageInfo.normalizePageInfo(pi)); + } + return pageInfos; + } +} diff --git a/src/main/java/org/jabref/model/openoffice/style/PageInfo.java b/src/main/java/org/jabref/model/openoffice/style/PageInfo.java new file mode 100644 index 00000000000..6807ecf0753 --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/style/PageInfo.java @@ -0,0 +1,47 @@ +package org.jabref.model.openoffice.style; + +import java.util.Optional; + +import org.jabref.model.openoffice.ootext.OOText; + +public class PageInfo { + + private PageInfo() { + // hide public constructor + } + + /* + * pageInfo normalization + */ + public static Optional normalizePageInfo(Optional o) { + if (o == null || o.isEmpty() || "".equals(OOText.toString(o.get()))) { + return Optional.empty(); + } + String s = OOText.toString(o.get()); + if (s.trim().equals("")) { + return Optional.empty(); + } + return Optional.of(OOText.fromString(s.trim())); + } + + /** + * Defines sort order for pageInfo strings. + * + * Optional.empty comes before non-empty. + */ + public static int comparePageInfo(Optional a, Optional b) { + + Optional aa = PageInfo.normalizePageInfo(a); + Optional bb = PageInfo.normalizePageInfo(b); + if (aa.isEmpty() && bb.isEmpty()) { + return 0; + } + if (aa.isEmpty()) { + return -1; + } + if (bb.isEmpty()) { + return +1; + } + return aa.get().asString().compareTo(bb.get().asString()); + } +} diff --git a/src/test/java/org/jabref/logic/openoffice/style/OOBibStyleTest.java b/src/test/java/org/jabref/logic/openoffice/style/OOBibStyleTest.java index 786e678324c..bf0bdf945a4 100644 --- a/src/test/java/org/jabref/logic/openoffice/style/OOBibStyleTest.java +++ b/src/test/java/org/jabref/logic/openoffice/style/OOBibStyleTest.java @@ -11,7 +11,11 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; +import java.util.Optional; import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.jabref.logic.layout.Layout; import org.jabref.logic.layout.LayoutFormatterPreferences; @@ -20,6 +24,14 @@ import org.jabref.model.entry.field.StandardField; import org.jabref.model.entry.types.StandardEntryType; import org.jabref.model.entry.types.UnknownEntryType; +import org.jabref.model.openoffice.ootext.OOText; +import org.jabref.model.openoffice.style.Citation; +import org.jabref.model.openoffice.style.CitationLookupResult; +import org.jabref.model.openoffice.style.CitationMarkerEntry; +import org.jabref.model.openoffice.style.CitationMarkerNumericBibEntry; +import org.jabref.model.openoffice.style.CitationMarkerNumericEntry; +import org.jabref.model.openoffice.style.NonUniqueCitationMarker; +import org.jabref.model.openoffice.style.PageInfo; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -80,13 +92,86 @@ void testNumerical() throws IOException { assertTrue(style.isSortByPosition()); } + /* + * begin helpers + */ + static String runGetNumCitationMarker2a(OOBibStyle style, + List num, int minGroupingCount, boolean inList ) { + return OOBibStyleTestHelper.runGetNumCitationMarker2a(style, num, minGroupingCount, inList); + } + + static CitationMarkerNumericEntry numEntry(String key, int num, String pageInfoOrNull) { + return OOBibStyleTestHelper.numEntry(key, num, pageInfoOrNull); + } + + static CitationMarkerNumericBibEntry numBibEntry(String key, Optional num) { + return OOBibStyleTestHelper.numBibEntry(key, num); + } + + static String runGetNumCitationMarker2b(OOBibStyle style, + int minGroupingCount, + CitationMarkerNumericEntry... s) { + List input = Stream.of(s).collect(Collectors.toList()); + OOText res = style.getNumCitationMarker2(input, minGroupingCount); + return res.asString(); + } + + static CitationMarkerEntry makeCitationMarkerEntry(BibEntry entry, + BibDatabase database, + String uniqueLetterQ, + String pageInfoQ, + boolean isFirstAppearanceOfSource) { + return OOBibStyleTestHelper.makeCitationMarkerEntry(entry, + database, + uniqueLetterQ, + pageInfoQ, + isFirstAppearanceOfSource ); + } + + /* + * Similar to old API. pageInfo is new, and unlimAuthors is + * replaced with isFirstAppearanceOfSource + */ + static String getCitationMarker2(OOBibStyle style, + List entries, + Map entryDBMap, + boolean inParenthesis, + String[] uniquefiers, + Boolean[] isFirstAppearanceOfSource, + String[] pageInfo) { + return OOBibStyleTestHelper.getCitationMarker2(style, + entries, + entryDBMap, + inParenthesis, + uniquefiers, + isFirstAppearanceOfSource, + pageInfo ); + } + + /* + * end helpers + */ + + @Test void testGetNumCitationMarker() throws IOException { OOBibStyle style = new OOBibStyle(StyleLoader.DEFAULT_NUMERICAL_STYLE_PATH, layoutFormatterPreferences); assertEquals("[1] ", style.getNumCitationMarker(Arrays.asList(1), -1, true)); + assertEquals("[1] ", runGetNumCitationMarker2a(style, Arrays.asList(1), -1, true)); + assertEquals("[1]", style.getNumCitationMarker(Arrays.asList(1), -1, false)); + assertEquals("[1]", runGetNumCitationMarker2a(style, Arrays.asList(1), -1, false)); + assertEquals("[1]", runGetNumCitationMarker2b(style, -1, numEntry("key", 1, null))); + assertEquals("[1] ", style.getNumCitationMarker(Arrays.asList(1), 0, true)); + assertEquals("[1] ", runGetNumCitationMarker2a(style, Arrays.asList(1), 0, true)); + + /* + * The following tests as for a numeric label for a + * bibliography entry containing more than one numbers. + * We do not need this, not reproduced. + */ assertEquals("[1-3] ", style.getNumCitationMarker(Arrays.asList(1, 2, 3), 1, true)); assertEquals("[1; 2; 3] ", style.getNumCitationMarker(Arrays.asList(1, 2, 3), 5, true)); assertEquals("[1; 2; 3] ", style.getNumCitationMarker(Arrays.asList(1, 2, 3), -1, true)); @@ -95,12 +180,24 @@ void testGetNumCitationMarker() throws IOException { String citation = style.getNumCitationMarker(Arrays.asList(1), -1, false); assertEquals("[1; pp. 55-56]", style.insertPageInfo(citation, "pp. 55-56")); + + CitationMarkerNumericEntry e2 = numEntry("key", 1, "pp. 55-56"); + assertEquals(true, e2.getPageInfo().isPresent()); + assertEquals("pp. 55-56", e2.getPageInfo().get().asString()); + citation = runGetNumCitationMarker2b(style, -1, e2); + assertEquals("[1; pp. 55-56]", citation); + + OOBibStyleTestHelper.testGetNumCitationMarkerExtra(style); } @Test void testGetNumCitationMarkerUndefined() throws IOException { OOBibStyle style = new OOBibStyle(StyleLoader.DEFAULT_NUMERICAL_STYLE_PATH, layoutFormatterPreferences); + /* + * Testing bibliography labels with multiple numbers again. + * Not reproduced. + */ assertEquals("[" + OOBibStyle.UNDEFINED_CITATION_MARKER + "; 2-4] ", style.getNumCitationMarker(Arrays.asList(4, 2, 3, 0), 1, true)); @@ -113,6 +210,55 @@ void testGetNumCitationMarkerUndefined() throws IOException { assertEquals("[" + OOBibStyle.UNDEFINED_CITATION_MARKER + "; " + OOBibStyle.UNDEFINED_CITATION_MARKER + "; " + OOBibStyle.UNDEFINED_CITATION_MARKER + "] ", style.getNumCitationMarker(Arrays.asList(0, 0, 0), 1, true)); + + /* + * We have these instead: + */ + + // unresolved citations look like [??key] + assertEquals("[" + OOBibStyle.UNDEFINED_CITATION_MARKER + "key" + "]", + runGetNumCitationMarker2b(style, 1, + numEntry("key",0,null))); + + // pageInfo is shown for unresolved citations + assertEquals("[" + OOBibStyle.UNDEFINED_CITATION_MARKER + "key" + "; p1]", + runGetNumCitationMarker2b(style, 1, + numEntry("key",0,"p1"))); + + // unresolved citations sorted to the front + assertEquals("[" + OOBibStyle.UNDEFINED_CITATION_MARKER + "key" + "; 2-4]", + runGetNumCitationMarker2b(style, 1, + numEntry("x4",4,""), + numEntry("x2",2,""), + numEntry("x3",3,""), + numEntry("key",0,""))); + + assertEquals("[" + OOBibStyle.UNDEFINED_CITATION_MARKER + "key" + "; 1-3]", + runGetNumCitationMarker2b(style, 1, + numEntry("x1",1,""), + numEntry("x2",2,""), + numEntry("y3",3,""), + numEntry("key",0,""))); + + // multiple unresolved citations are not collapsed + assertEquals("[" + + OOBibStyle.UNDEFINED_CITATION_MARKER + "x1" + "; " + + OOBibStyle.UNDEFINED_CITATION_MARKER + "x2" + "; " + + OOBibStyle.UNDEFINED_CITATION_MARKER + "x3" + "]", + runGetNumCitationMarker2b(style, 1, + numEntry("x1",0,""), + numEntry("x2",0,""), + numEntry("x3",0,""))); + + /* + * BIBLIOGRAPHY + */ + { + CitationMarkerNumericBibEntry x = numBibEntry("key", Optional.empty()); + assertEquals("[" + OOBibStyle.UNDEFINED_CITATION_MARKER + "key" + "] ", + style.getNumCitationMarkerForBibliography(x).asString()); + } + } @Test @@ -120,14 +266,21 @@ void testGetCitProperty() throws IOException { OOBibStyle style = new OOBibStyle(StyleLoader.DEFAULT_NUMERICAL_STYLE_PATH, layoutFormatterPreferences); assertEquals(", ", style.getStringCitProperty("AuthorSeparator")); + + // old assertEquals(3, style.getIntCitProperty("MaxAuthors")); assertTrue(style.getBooleanCitProperty(OOBibStyle.MULTI_CITE_CHRONOLOGICAL)); + // new + assertEquals(3, style.getMaxAuthors()); + assertTrue(style.getMultiCiteChronological()); + assertEquals("Default", style.getCitationCharacterFormat()); assertEquals("Default [number] style file.", style.getName()); Set journals = style.getJournals(); assertTrue(journals.contains("Journal name 1")); } + @Test void testGetCitationMarker() throws IOException { OOBibStyle style = new OOBibStyle(StyleLoader.DEFAULT_NUMERICAL_STYLE_PATH, layoutFormatterPreferences); @@ -138,17 +291,39 @@ void testGetCitationMarker() throws IOException { .withField(StandardField.PUBLISHER, "ACM") .withField(StandardField.TITLE, "Extending XP practices to support security requirements engineering") .withField(StandardField.PAGES, "11--18"); + entry.setCitationKey("Bostrom2006"); // citation key is not optional now BibDatabase database = new BibDatabase(); database.insertEntry(entry); Map entryDBMap = new HashMap<>(); entryDBMap.put(entry, database); + // Check what unlimAuthors values correspond to isFirstAppearanceOfSource false/true + assertEquals(3, style.getMaxAuthors()); + assertEquals(-1, style.getMaxAuthorsFirst()); + assertEquals("[Boström et al., 2006]", style.getCitationMarker(Collections.singletonList(entry), entryDBMap, true, null, null)); + assertEquals("[Boström et al., 2006]", + getCitationMarker2(style, + Collections.singletonList(entry), entryDBMap, + true, null, null, null)); + assertEquals("Boström et al. [2006]", style.getCitationMarker(Collections.singletonList(entry), entryDBMap, false, null, new int[]{3})); + assertEquals("Boström et al. [2006]", + getCitationMarker2(style, + Collections.singletonList(entry), entryDBMap, + false, null, new Boolean[]{false}, null)); + assertEquals("[Boström, Wäyrynen, Bodén, Beznosov & Kruchten, 2006]", style.getCitationMarker(Collections.singletonList(entry), entryDBMap, true, null, new int[]{5})); + assertEquals("[Boström, Wäyrynen, Bodén, Beznosov & Kruchten, 2006]", + getCitationMarker2(style, + Collections.singletonList(entry), entryDBMap, + true, + null, + new Boolean[]{true} /* corresponds to -1, not 5 */, + null)); } @Test @@ -225,6 +400,7 @@ void testInstitutionAuthorMarker() throws IOException { BibDatabase database = new BibDatabase(); BibEntry entry = new BibEntry(); + entry.setCitationKey("JabRef2016"); entry.setType(StandardEntryType.Article); entry.setField(StandardField.AUTHOR, "{JabRef Development Team}"); entry.setField(StandardField.TITLE, "JabRef Manual"); @@ -233,6 +409,10 @@ void testInstitutionAuthorMarker() throws IOException { entries.add(entry); entryDBMap.put(entry, database); assertEquals("[JabRef Development Team, 2016]", style.getCitationMarker(entries, entryDBMap, true, null, null)); + + assertEquals("[JabRef Development Team, 2016]", + getCitationMarker2(style, + entries, entryDBMap, true, null, null, null)); } @Test @@ -513,4 +693,225 @@ void testIsValidWithDefaultSectionAtTheStart() throws Exception { OOBibStyle style = new OOBibStyle("testWithDefaultAtFirstLIne.jstyle", layoutFormatterPreferences); assertTrue(style.isValid()); } + + @Test + void testGetCitationMarkerJoinFirst() throws IOException { + OOBibStyle style = new OOBibStyle(StyleLoader.DEFAULT_NUMERICAL_STYLE_PATH, + layoutFormatterPreferences); + + // Question: What should happen if some of the sources is + // marked as isFirstAppearanceOfSource? + // This test documents what is happening now. + + // Two entries with identical normalizedMarkers and many authors. + BibEntry entry1 = new BibEntry() + .withField(StandardField.AUTHOR, + "Gustav Bostr\\\"{o}m" + + " and Jaana W\\\"{a}yrynen" + + " and Marine Bod\\'{e}n" + + " and Konstantin Beznosov" + + " and Philippe Kruchten") + .withField(StandardField.YEAR, "2006") + .withField(StandardField.BOOKTITLE, "A book 1") + .withField(StandardField.PUBLISHER, "ACM") + .withField(StandardField.TITLE, "Title 1") + .withField(StandardField.PAGES, "11--18"); + entry1.setCitationKey("b1"); + + BibEntry entry2 = new BibEntry() + .withField(StandardField.AUTHOR, + "Gustav Bostr\\\"{o}m" + + " and Jaana W\\\"{a}yrynen" + + " and Marine Bod\\'{e}n" + + " and Konstantin Beznosov" + + " and Philippe Kruchten") + .withField(StandardField.YEAR, "2006") + .withField(StandardField.BOOKTITLE, "A book 2") + .withField(StandardField.PUBLISHER, "ACM") + .withField(StandardField.TITLE, "title2") + .withField(StandardField.PAGES, "11--18"); + entry2.setCitationKey("b2"); + + // Last Author differs. + BibEntry entry3 = new BibEntry() + .withField(StandardField.AUTHOR, + "Gustav Bostr\\\"{o}m" + + " and Jaana W\\\"{a}yrynen" + + " and Marine Bod\\'{e}n" + + " and Konstantin Beznosov" + + " and Philippe NotKruchten") + .withField(StandardField.YEAR, "2006") + .withField(StandardField.BOOKTITLE, "A book 3") + .withField(StandardField.PUBLISHER, "ACM") + .withField(StandardField.TITLE, "title3") + .withField(StandardField.PAGES, "11--18"); + entry3.setCitationKey("b3"); + + BibDatabase database = new BibDatabase(); + database.insertEntry(entry1); + database.insertEntry(entry2); + database.insertEntry(entry3); + + // Without pageInfo, two isFirstAppearanceOfSource may be joined. + // The third is NotKruchten, should not be joined. + if (true) { + List citationMarkerEntries = new ArrayList<>(); + CitationMarkerEntry cm1 = + makeCitationMarkerEntry(entry1, database, "a", null, true); + citationMarkerEntries.add(cm1); + CitationMarkerEntry cm2 = + makeCitationMarkerEntry(entry2, database, "b", null, true); + citationMarkerEntries.add(cm2); + CitationMarkerEntry cm3 = + makeCitationMarkerEntry(entry3, database, "c", null, true); + citationMarkerEntries.add(cm3); + + assertEquals("[Boström, Wäyrynen, Bodén, Beznosov & Kruchten, 2006a,b" + +"; Boström, Wäyrynen, Bodén, Beznosov & NotKruchten, 2006c]", + style.createCitationMarker(citationMarkerEntries, + true, + NonUniqueCitationMarker.THROWS).asString()); + + assertEquals("Boström, Wäyrynen, Bodén, Beznosov & Kruchten [2006a,b]" + + "; Boström, Wäyrynen, Bodén, Beznosov & NotKruchten [2006c]", + style.createCitationMarker(citationMarkerEntries, + false, + NonUniqueCitationMarker.THROWS).asString()); + } + + // Without pageInfo, only the first is isFirstAppearanceOfSource. + // The second may be joined, based on expanded normalizedMarkers. + // The third is NotKruchten, should not be joined. + if (true) { + List citationMarkerEntries = new ArrayList<>(); + CitationMarkerEntry cm1 = + makeCitationMarkerEntry(entry1, database, "a", null, true); + citationMarkerEntries.add(cm1); + CitationMarkerEntry cm2 = + makeCitationMarkerEntry(entry2, database, "b", null, false); + citationMarkerEntries.add(cm2); + CitationMarkerEntry cm3 = + makeCitationMarkerEntry(entry3, database, "c", null, false); + citationMarkerEntries.add(cm3); + + assertEquals("[Boström, Wäyrynen, Bodén, Beznosov & Kruchten, 2006a,b" + +"; Boström et al., 2006c]", + style.createCitationMarker(citationMarkerEntries, + true, + NonUniqueCitationMarker.THROWS).asString()); + + } + // Without pageInfo, only the second is isFirstAppearanceOfSource. + // The second is not joined, because it is a first appearance, thus + // requires more names to be shown. + // The third is NotKruchten, should not be joined. + if (true) { + List citationMarkerEntries = new ArrayList<>(); + CitationMarkerEntry cm1 = + makeCitationMarkerEntry(entry1, database, "a", null, false); + citationMarkerEntries.add(cm1); + CitationMarkerEntry cm2 = + makeCitationMarkerEntry(entry2, database, "b", null, true); + citationMarkerEntries.add(cm2); + CitationMarkerEntry cm3 = + makeCitationMarkerEntry(entry3, database, "c", null, false); + citationMarkerEntries.add(cm3); + + assertEquals("[Boström et al., 2006a" + + "; Boström, Wäyrynen, Bodén, Beznosov & Kruchten, 2006b" + + "; Boström et al., 2006c]", + style.createCitationMarker(citationMarkerEntries, + true, + NonUniqueCitationMarker.THROWS).asString()); + } + + // Without pageInfo, neither is isFirstAppearanceOfSource. + // The second is joined. + // The third is NotKruchten, but is joined because NotKruchten is not among the names shown. + // Is this the correct behaviour? + if (true) { + List citationMarkerEntries = new ArrayList<>(); + CitationMarkerEntry cm1 = + makeCitationMarkerEntry(entry1, database, "a", null, false); + citationMarkerEntries.add(cm1); + CitationMarkerEntry cm2 = + makeCitationMarkerEntry(entry2, database, "b", null, false); + citationMarkerEntries.add(cm2); + CitationMarkerEntry cm3 = + makeCitationMarkerEntry(entry3, database, "c", null, false); + citationMarkerEntries.add(cm3); + + assertEquals("[Boström et al., 2006a,b,c]", + style.createCitationMarker(citationMarkerEntries, + true, + NonUniqueCitationMarker.THROWS).asString()); + } + + // With pageInfo: different entries with identical non-null pageInfo: not joined. + // XY [2000a,b,c; p1] whould be confusing. + if (true) { + List citationMarkerEntries = new ArrayList<>(); + CitationMarkerEntry cm1 = + makeCitationMarkerEntry(entry1, database, "a", "p1", false); + citationMarkerEntries.add(cm1); + CitationMarkerEntry cm2 = + makeCitationMarkerEntry(entry2, database, "b", "p1", false); + citationMarkerEntries.add(cm2); + CitationMarkerEntry cm3 = + makeCitationMarkerEntry(entry3, database, "c", "p1", false); + citationMarkerEntries.add(cm3); + + assertEquals("[Boström et al., 2006a; p1" + + "; Boström et al., 2006b; p1" + + "; Boström et al., 2006c; p1]", + style.createCitationMarker(citationMarkerEntries, + true, + NonUniqueCitationMarker.THROWS).asString()); + } + + // With pageInfo: same entries with identical non-null pageInfo: collapsed. + // Note: "same" here looks at the visible parts and citation key only, + // but ignores the rest. Normally the citation key should distinguish. + if (true) { + List citationMarkerEntries = new ArrayList<>(); + CitationMarkerEntry cm1 = + makeCitationMarkerEntry(entry1, database, "a", "p1", false); + citationMarkerEntries.add(cm1); + CitationMarkerEntry cm2 = + makeCitationMarkerEntry(entry1, database, "a", "p1", false); + citationMarkerEntries.add(cm2); + CitationMarkerEntry cm3 = + makeCitationMarkerEntry(entry1, database, "a", "p1", false); + citationMarkerEntries.add(cm3); + + assertEquals("[Boström et al., 2006a; p1]", + style.createCitationMarker(citationMarkerEntries, + true, + NonUniqueCitationMarker.THROWS).asString()); + } + // With pageInfo: same entries with different pageInfo: kept separate. + // Empty ("") and missing pageInfos considered equal, thus collapsed. + if (true) { + List citationMarkerEntries = new ArrayList<>(); + CitationMarkerEntry cm1 = + makeCitationMarkerEntry(entry1, database, "a", "p1", false); + citationMarkerEntries.add(cm1); + CitationMarkerEntry cm2 = + makeCitationMarkerEntry(entry1, database, "a", "p2", false); + citationMarkerEntries.add(cm2); + CitationMarkerEntry cm3 = + makeCitationMarkerEntry(entry1, database, "a", "", false); + citationMarkerEntries.add(cm3); + CitationMarkerEntry cm4 = + makeCitationMarkerEntry(entry1, database, "a", null, false); + citationMarkerEntries.add(cm4); + + assertEquals("[Boström et al., 2006a; p1" + + "; Boström et al., 2006a; p2" + + "; Boström et al., 2006a]", + style.createCitationMarker(citationMarkerEntries, + true, + NonUniqueCitationMarker.THROWS).asString()); + } + } } diff --git a/src/test/java/org/jabref/logic/openoffice/style/OOBibStyleTestHelper.java b/src/test/java/org/jabref/logic/openoffice/style/OOBibStyleTestHelper.java new file mode 100644 index 00000000000..c8302030fc6 --- /dev/null +++ b/src/test/java/org/jabref/logic/openoffice/style/OOBibStyleTestHelper.java @@ -0,0 +1,359 @@ +package org.jabref.logic.openoffice.style; + +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.jabref.logic.layout.Layout; +import org.jabref.logic.layout.LayoutFormatterPreferences; +import org.jabref.model.database.BibDatabase; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.field.StandardField; +import org.jabref.model.entry.types.StandardEntryType; +import org.jabref.model.entry.types.UnknownEntryType; +import org.jabref.model.openoffice.ootext.OOText; +import org.jabref.model.openoffice.style.Citation; +import org.jabref.model.openoffice.style.CitationLookupResult; +import org.jabref.model.openoffice.style.CitationMarkerEntry; +import org.jabref.model.openoffice.style.CitationMarkerNumericBibEntry; +import org.jabref.model.openoffice.style.CitationMarkerNumericEntry; +import org.jabref.model.openoffice.style.NonUniqueCitationMarker; +import org.jabref.model.openoffice.style.PageInfo; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Answers; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; + +class OOBibStyleTestHelper { + /* + * begin Helpers for testing style.getNumCitationMarker2 + */ + + /* + * Minimal implementation for CitationMarkerNumericEntry + */ + static class CitationMarkerNumericEntryImpl implements CitationMarkerNumericEntry { + + /* + * The number encoding "this entry is unresolved" for the constructor. + */ + public final static int UNRESOLVED_ENTRY_NUMBER = 0; + + private String citationKey; + private Optional num; + private Optional pageInfo; + + public CitationMarkerNumericEntryImpl(String citationKey, int num, Optional pageInfo) { + this.citationKey = citationKey; + this.num = (num == UNRESOLVED_ENTRY_NUMBER + ? Optional.empty() + : Optional.of(num)); + this.pageInfo = PageInfo.normalizePageInfo(pageInfo); + } + + @Override + public String getCitationKey() { + return citationKey; + } + + @Override + public Optional getNumber() { + return num; + } + + @Override + public Optional getPageInfo() { + return pageInfo; + } + } + + static class CitationMarkerNumericBibEntryImpl implements CitationMarkerNumericBibEntry { + String key; + Optional number; + + public CitationMarkerNumericBibEntryImpl(String key, Optional number) { + this.key = key; + this.number = number; + } + + @Override + public String getCitationKey() { + return key; + } + + @Override + public Optional getNumber() { + return number; + } + } + + static CitationMarkerNumericBibEntry numBibEntry(String key, Optional number) { + return new CitationMarkerNumericBibEntryImpl(key, number); + } + + /** + * Reproduce old method + * + * @param inList true means label for the bibliography + */ + static String runGetNumCitationMarker2a(OOBibStyle style, + List num, int minGroupingCount, boolean inList ) { + if (inList) { + if (num.size() != 1) { + throw new RuntimeException("Numeric label for the bibliography with " + + String.valueOf(num.size()) + " numbers?"); + } + int n = num.get(0); + CitationMarkerNumericBibEntryImpl x = + new CitationMarkerNumericBibEntryImpl("key", + (n == 0) ? Optional.empty() : Optional.of(n)); + return style.getNumCitationMarkerForBibliography(x).asString(); + } else { + List input = + num.stream() + .map(n -> + new CitationMarkerNumericEntryImpl("key" + String.valueOf(n), + n, + Optional.empty())) + .collect(Collectors.toList()); + return style.getNumCitationMarker2(input, minGroupingCount).asString(); + } + } + + /* + * Unlike getNumCitationMarker, getNumCitationMarker2 can handle pageInfo. + */ + static CitationMarkerNumericEntry numEntry(String key, int num, String pageInfoOrNull) { + Optional pageInfo = Optional.ofNullable(OOText.fromString(pageInfoOrNull)); + return new CitationMarkerNumericEntryImpl(key, num, pageInfo); + } + + static String runGetNumCitationMarker2b(OOBibStyle style, + int minGroupingCount, + CitationMarkerNumericEntry... s) { + List input = Stream.of(s).collect(Collectors.toList()); + OOText res = style.getNumCitationMarker2(input, minGroupingCount); + return res.asString(); + } + + /* + * end Helpers for testing style.getNumCitationMarker2 + */ + + + /* + * begin helper + */ + static CitationMarkerEntry makeCitationMarkerEntry(BibEntry entry, + BibDatabase database, + String uniqueLetterQ, + String pageInfoQ, + boolean isFirstAppearanceOfSource) { + if (!entry.getCitationKey().isPresent()) { + throw new RuntimeException("!entry.getCitationKey().isPresent()"); + } + String citationKey = entry.getCitationKey().get(); + Citation result = new Citation(citationKey); + result.setLookupResult(Optional.of(new CitationLookupResult(entry, database))); + result.setUniqueLetter(Optional.ofNullable(uniqueLetterQ)); + Optional pageInfo = Optional.ofNullable(OOText.fromString(pageInfoQ)); + result.setPageInfo(PageInfo.normalizePageInfo(pageInfo)); + result.setIsFirstAppearanceOfSource(isFirstAppearanceOfSource); + return result; + } + + /* + * Similar to old API. pageInfo is new, and unlimAuthors is + * replaced with isFirstAppearanceOfSource + */ + static String getCitationMarker2(OOBibStyle style, + List entries, + Map entryDBMap, + boolean inParenthesis, + String[] uniquefiers, + Boolean[] isFirstAppearanceOfSource, + String[] pageInfo) { + if (uniquefiers == null) { + uniquefiers = new String[entries.size()]; + Arrays.fill(uniquefiers, null); + } + if (pageInfo == null) { + pageInfo = new String[entries.size()]; + Arrays.fill(pageInfo, null); + } + if (isFirstAppearanceOfSource == null) { + isFirstAppearanceOfSource = new Boolean[entries.size()]; + Arrays.fill(isFirstAppearanceOfSource, false); + } + List citationMarkerEntries = new ArrayList<>(); + for (int i = 0; i < entries.size(); i++) { + BibEntry entry = entries.get(i); + CitationMarkerEntry e = makeCitationMarkerEntry(entry, + entryDBMap.get(entry), + uniquefiers[i], + pageInfo[i], + isFirstAppearanceOfSource[i]); + citationMarkerEntries.add(e); + } + return style.createCitationMarker(citationMarkerEntries, + inParenthesis, + NonUniqueCitationMarker.THROWS).asString(); + } + + /* + * end helper + */ + + + static void testGetNumCitationMarkerExtra(OOBibStyle style) throws IOException { + // Identical numeric entries are joined. + assertEquals("[1; 2]", runGetNumCitationMarker2b(style, 3, + numEntry("x1",1,null), + numEntry("x2",2,null), + numEntry("x1",2,null), + numEntry("x2",1,null))); + + // ... unless minGroupingCount <= 0 + assertEquals("[1; 1; 2; 2]", runGetNumCitationMarker2b(style, 0, + numEntry("x1",1,null), + numEntry("x2",2,null), + numEntry("x1",2,null), + numEntry("x2",1,null))); + + // ... or have different pageInfos + assertEquals("[1; p1a; 1; p1b; 2; p2; 3]", runGetNumCitationMarker2b(style, 1, + numEntry("x1", 1, "p1a"), + numEntry("x1", 1, "p1b"), + numEntry("x2", 2, "p2"), + numEntry("x2", 2, "p2"), + numEntry("x3", 3, null), + numEntry("x3", 3, null))); + + // Consecutive numbers can become a range ... + assertEquals("[1-3]", runGetNumCitationMarker2b(style, 1, + numEntry("x1", 1, null), + numEntry("x2", 2, null), + numEntry("x3", 3, null))); + + // ... unless minGroupingCount is too high + assertEquals("[1; 2; 3]", runGetNumCitationMarker2b(style, 4, + numEntry("x1", 1, null), + numEntry("x2", 2, null), + numEntry("x3", 3, null))); + + // ... or if minGroupingCount <= 0 + assertEquals("[1; 2; 3]", runGetNumCitationMarker2b(style, 0, + numEntry("x1", 1, null), + numEntry("x2", 2, null), + numEntry("x3", 3, null))); + + // ... a pageInfo needs to be emitted + assertEquals("[1; p1; 2-3]", runGetNumCitationMarker2b(style, 1, + numEntry("x1", 1, "p1"), + numEntry("x2", 2, null), + numEntry("x3", 3, null))); + + // null and "" pageInfos are taken as equal. + // Due to trimming, " " is the same as well. + assertEquals("[1]", runGetNumCitationMarker2b(style, 1, + numEntry("x1", 1, ""), + numEntry("x1", 1, null), + numEntry("x1", 1, " "))); + + // pageInfos are trimmed + assertEquals("[1; p1]", runGetNumCitationMarker2b(style, 1, + numEntry("x1", 1, "p1"), + numEntry("x1", 1, " p1"), + numEntry("x1", 1, "p1 "))); + + // The citation numbers come out sorted + assertEquals("[3-5; 7; 10-12]", runGetNumCitationMarker2b(style, 1, + numEntry("x12", 12, null), + numEntry("x7", 7, null), + numEntry("x3", 3, null), + numEntry("x4", 4, null), + numEntry("x11", 11, null), + numEntry("x10", 10, null), + numEntry("x5", 5, null))); + + // pageInfos are sorted together with the numbers + // (but they inhibit ranges where they are, even if they are identical, + // but not empty-or-null) + assertEquals("[3; p3; 4; p4; 5; p5; 7; p7; 10; px; 11; px; 12; px]", + runGetNumCitationMarker2b(style, 1, + numEntry("x12", 12, "px"), + numEntry("x7", 7, "p7"), + numEntry("x3", 3, "p3"), + numEntry("x4", 4, "p4"), + numEntry("x11", 11, "px"), + numEntry("x10", 10, "px"), + numEntry("x5", 5, "p5"))); + + // pageInfo sorting (for the same number) + assertEquals("[1; 1; a; 1; b]", + runGetNumCitationMarker2b(style, 1, + numEntry("x1", 1, ""), + numEntry("x1", 1, "b"), + numEntry("x1", 1, "a"))); + + // pageInfo sorting (for the same number) is not numeric. + assertEquals("[1; p100; 1; p20; 1; p9]", + runGetNumCitationMarker2b(style, 1, + numEntry("x1", 1, "p20"), + numEntry("x1", 1, "p9"), + numEntry("x1", 1, "p100"))); + + assertEquals("[1-3]", + runGetNumCitationMarker2b(style, 1, + numEntry("x1", 1, null), + numEntry("x2", 2, null), + numEntry("x3", 3, null))); + + assertEquals("[1; 2; 3]", + runGetNumCitationMarker2b(style, 5, + numEntry("x1", 1, null), + numEntry("x2", 2, null), + numEntry("x3", 3, null))); + + assertEquals("[1; 2; 3]", + runGetNumCitationMarker2b(style, -1, + numEntry("x1", 1, null), + numEntry("x2", 2, null), + numEntry("x3", 3, null))); + + assertEquals("[1; 3; 12]", + runGetNumCitationMarker2b(style, 1, + numEntry("x1", 1, null), + numEntry("x12", 12, null), + numEntry("x3", 3, null))); + + assertEquals("[3-5; 7; 10-12]", + runGetNumCitationMarker2b(style, 1, + numEntry("x12", 12, ""), + numEntry("x7", 7, ""), + numEntry("x3", 3, ""), + numEntry("x4", 4, ""), + numEntry("x11", 11, ""), + numEntry("x10", 10, ""), + numEntry("x5", 5, ""))); + } +} From d2a53c7061f59a623c1b86cb873e4db16210ed83 Mon Sep 17 00:00:00 2001 From: Antal K Date: Thu, 3 Jun 2021 11:39:40 +0200 Subject: [PATCH 16/51] add backend --- .../logic/openoffice/backend/Backend52.java | 465 ++++++++++++++++ .../logic/openoffice/backend/Codec52.java | 148 ++++++ .../logic/openoffice/backend/GetContext.java | 87 +++ .../NamedRangeManagerReferenceMark.java | 50 ++ .../backend/NamedRangeReferenceMark.java | 500 ++++++++++++++++++ .../model/openoffice/backend/NamedRange.java | 65 +++ .../openoffice/backend/NamedRangeManager.java | 31 ++ src/main/resources/l10n/JabRef_en.properties | 1 + 8 files changed, 1347 insertions(+) create mode 100644 src/main/java/org/jabref/logic/openoffice/backend/Backend52.java create mode 100644 src/main/java/org/jabref/logic/openoffice/backend/Codec52.java create mode 100644 src/main/java/org/jabref/logic/openoffice/backend/GetContext.java create mode 100644 src/main/java/org/jabref/logic/openoffice/backend/NamedRangeManagerReferenceMark.java create mode 100644 src/main/java/org/jabref/logic/openoffice/backend/NamedRangeReferenceMark.java create mode 100644 src/main/java/org/jabref/model/openoffice/backend/NamedRange.java create mode 100644 src/main/java/org/jabref/model/openoffice/backend/NamedRangeManager.java diff --git a/src/main/java/org/jabref/logic/openoffice/backend/Backend52.java b/src/main/java/org/jabref/logic/openoffice/backend/Backend52.java new file mode 100644 index 00000000000..d7c1b62086a --- /dev/null +++ b/src/main/java/org/jabref/logic/openoffice/backend/Backend52.java @@ -0,0 +1,465 @@ +package org.jabref.logic.openoffice.backend; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +import org.jabref.model.openoffice.CitationEntry; +import org.jabref.model.openoffice.backend.NamedRange; +import org.jabref.model.openoffice.backend.NamedRangeManager; +import org.jabref.model.openoffice.ootext.OOText; +import org.jabref.model.openoffice.style.Citation; +import org.jabref.model.openoffice.style.CitationGroup; +import org.jabref.model.openoffice.style.CitationGroupId; +import org.jabref.model.openoffice.style.CitationGroups; +import org.jabref.model.openoffice.style.CitationType; +import org.jabref.model.openoffice.style.OODataModel; +import org.jabref.model.openoffice.style.PageInfo; +import org.jabref.model.openoffice.uno.CreationException; +import org.jabref.model.openoffice.uno.NoDocumentException; +import org.jabref.model.openoffice.uno.UnoUserDefinedProperty; +import org.jabref.model.openoffice.util.OOListUtil; + +import com.sun.star.beans.IllegalTypeException; +import com.sun.star.beans.NotRemoveableException; +import com.sun.star.beans.PropertyExistException; +import com.sun.star.beans.PropertyVetoException; +import com.sun.star.beans.UnknownPropertyException; +import com.sun.star.container.NoSuchElementException; +import com.sun.star.lang.WrappedTargetException; +import com.sun.star.text.XTextCursor; +import com.sun.star.text.XTextDocument; +import com.sun.star.text.XTextRange; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class Backend52 { + private static final Logger LOGGER = LoggerFactory.getLogger(Backend52.class); + public final OODataModel dataModel; + private final NamedRangeManager citationStorageManager; + private final Map cgidToNamedRange; + + // uses: Codec52 + public Backend52() { + this.dataModel = OODataModel.JabRef52; + this.citationStorageManager = new NamedRangeManagerReferenceMark(); + this.cgidToNamedRange = new HashMap<>(); + } + + /** + * Get reference mark names from the document matching the pattern + * used for JabRef reference mark names. + * + * Note: the names returned are in arbitrary order. + * + */ + public List getJabRefReferenceMarkNames(XTextDocument doc) + throws + NoDocumentException { + List allNames = this.citationStorageManager.nrmGetUsedNames(doc); + return Codec52.filterIsJabRefReferenceMarkName(allNames); + } + + /** + * Names of custom properties belonging to us, but without a + * corresponding reference mark. These can be deleted. + * + * @param citationGroupNames These are the names that are used. + * + */ + private List findUnusedJabrefPropertyNames(XTextDocument doc, + List citationGroupNames) { + + // Collect unused jabrefPropertyNames + Set citationGroupNamesSet = citationGroupNames.stream().collect(Collectors.toSet()); + + List pageInfoThrash = new ArrayList<>(); + List jabrefPropertyNames = + UnoUserDefinedProperty.getListOfNames(doc) + .stream() + .filter(Codec52::isJabRefReferenceMarkName) + .collect(Collectors.toList()); + for (String pn : jabrefPropertyNames) { + if (!citationGroupNamesSet.contains(pn)) { + pageInfoThrash.add(pn); + } + } + return pageInfoThrash; + } + + /** + * @return Optional.empty if all is OK, message text otherwise. + */ + public Optional healthReport(XTextDocument doc) + throws + NoDocumentException { + List pageInfoThrash = + this.findUnusedJabrefPropertyNames(doc, this.getJabRefReferenceMarkNames(doc)); + if (pageInfoThrash.isEmpty()) { + return Optional.empty(); // "Backend52: found no unused pageInfo data"; + } + String msg = + "Backend52: found unused pageInfo data, with names listed below.\n" + + "In LibreOffice you may remove these in [File]/[Properties]/[Custom Properties]\n"; + msg += "" + String.join("\n", pageInfoThrash) + ""; + return Optional.of(msg); + } + + private static void setPageInfoInDataInitial(List citations, Optional pageInfo) { + // attribute to last citation (initially localOrder == storageOrder) + if (citations.size() > 0) { + citations.get(citations.size() - 1).setPageInfo(pageInfo); + } + } + + private static void setPageInfoInData(CitationGroup cg, Optional pageInfo) { + List citations = cg.getCitationsInLocalOrder(); + if (citations.size() > 0) { + citations.get(citations.size() - 1).setPageInfo(pageInfo); + } + } + + private static Optional getPageInfoFromData(CitationGroup cg) { + List citations = cg.getCitationsInLocalOrder(); + if (citations.size() > 0) { + return citations.get(citations.size() - 1).getPageInfo(); + } else { + return Optional.empty(); + } + } + + /** + * We have circular dependency here: backend uses + * class from ... + */ + public CitationGroup readCitationGroupFromDocumentOrThrow(XTextDocument doc, String refMarkName) + throws + WrappedTargetException, + NoDocumentException { + + Optional op = Codec52.parseMarkName(refMarkName); + if (op.isEmpty()) { + throw new IllegalArgumentException("readCitationGroupFromDocumentOrThrow:" + + " found unparsable referenceMarkName"); + } + Codec52.ParsedMarkName ov = op.get(); + CitationGroupId cgid = new CitationGroupId(refMarkName); + List citations = (ov.citationKeys.stream() + .map(Citation::new) + .collect(Collectors.toList())); + + Optional pageInfo = + (UnoUserDefinedProperty.getStringValue(doc, refMarkName) + .map(OOText::fromString)); + pageInfo = PageInfo.normalizePageInfo(pageInfo); + + setPageInfoInDataInitial(citations, pageInfo); + + Optional namedRange = citationStorageManager.nrmGetFromDocument(doc, refMarkName); + + if (namedRange.isEmpty()) { + throw new IllegalArgumentException("readCitationGroupFromDocumentOrThrow:" + + " referenceMarkName is not in the document"); + } + + CitationGroup cg = new CitationGroup(OODataModel.JabRef52, + cgid, + ov.citationType, + citations, + Optional.of(refMarkName)); + this.cgidToNamedRange.put(cgid, namedRange.get()); + return cg; + } + + /** + * Create a reference mark with the given name, at the + * end of position. + * + * On return {@code position} is collapsed, and is after the + * inserted space, or at the end of the reference mark. + * + * @param position Collapsed to its end. + * @param insertSpaceAfter We insert a space after the mark, that + * carries on format of characters from + * the original position. + */ + public CitationGroup createCitationGroup(XTextDocument doc, + List citationKeys, + List> pageInfos, + CitationType citationType, + XTextCursor position, + boolean insertSpaceAfter) + throws + CreationException, + NoDocumentException, + WrappedTargetException, + NotRemoveableException, + PropertyExistException, + PropertyVetoException, + IllegalTypeException { + + Objects.requireNonNull(pageInfos); + if (pageInfos.size() != citationKeys.size()) { + throw new RuntimeException("pageInfos.size != citationKeys.size"); + } + + // Get a new refMarkName + Set usedNames = new HashSet<>(this.citationStorageManager.nrmGetUsedNames(doc)); + String xkey = (citationKeys.stream().collect(Collectors.joining(","))); + String refMarkName = Codec52.getUniqueMarkName(usedNames, xkey, citationType); + + CitationGroupId cgid = new CitationGroupId(refMarkName); + + final int nCitations = citationKeys.size(); + final int last = nCitations - 1; + + // Build citations, add pageInfo to each citation + List citations = new ArrayList<>(nCitations); + for (int i = 0; i < nCitations; i++) { + Citation cit = new Citation(citationKeys.get(i)); + citations.add(cit); + + Optional pageInfo = PageInfo.normalizePageInfo(pageInfos.get(i)); + switch (dataModel) { + case JabRef52: + if (i == last) { + cit.setPageInfo(pageInfo); + } else { + if (pageInfo.isPresent()) { + LOGGER.warn("dataModel JabRef52" + + " only supports pageInfo for the last citation of a group"); + } + } + break; + case JabRef53: + cit.setPageInfo(pageInfo); + break; + } + } + + /* + * Apply to document + */ + boolean withoutBrackets = (citationType == CitationType.INVISIBLE_CIT); + NamedRange namedRange = + this.citationStorageManager.nrmCreate(doc, refMarkName, position, insertSpaceAfter, + withoutBrackets); + + switch (dataModel) { + case JabRef52: + Optional pageInfo = PageInfo.normalizePageInfo(pageInfos.get(last)); + + if (pageInfo.isPresent()) { + String pageInfoString = OOText.toString(pageInfo.get()); + UnoUserDefinedProperty.createStringProperty(doc, refMarkName, pageInfoString); + } else { + // do not inherit from trash + UnoUserDefinedProperty.removeIfExists(doc, refMarkName); + } + CitationGroup cg = new CitationGroup(OODataModel.JabRef52, + cgid, + citationType, citations, + Optional.of(refMarkName)); + this.cgidToNamedRange.put(cgid, namedRange); + return cg; + default: + throw new RuntimeException("Backend52 requires JabRef52 dataModel"); + } + } + + /** + * @return A list with a nullable pageInfo entry for each citation in + * joinableGroups. + * + * TODO: JabRef52 combinePageInfos is not reversible. Should warn + * user to check the result. Or ask what to do. + */ + public static List> + combinePageInfosCommon(OODataModel dataModel, List joinableGroup) { + switch (dataModel) { + case JabRef52: + // collect to cgPageInfos + List> cgPageInfos = OOListUtil.map(joinableGroup, + Backend52::getPageInfoFromData); + + // Try to do something of the cgPageInfos. + String cgPageInfo = (cgPageInfos.stream() + .filter(pi -> pi.isPresent()) + .map(pi -> OOText.toString(pi.get())) + .distinct() + .collect(Collectors.joining("; "))); + + int nCitations = (joinableGroup.stream() + .map(cg -> cg.numberOfCitations()) + .mapToInt(Integer::intValue).sum()); + if ("".equals(cgPageInfo)) { + cgPageInfo = null; + } + return OODataModel.fakePageInfos(cgPageInfo, nCitations); + + case JabRef53: + return (joinableGroup.stream() + .flatMap(cg -> (cg.citationsInStorageOrder.stream() + .map(Citation::getPageInfo))) + .collect(Collectors.toList())); + default: + throw new RuntimeException("unhandled dataModel here"); + } + } + + /** + * + */ + public List> combinePageInfos(List joinableGroup) { + return combinePageInfosCommon(this.dataModel, joinableGroup); + } + + private NamedRange getNamedRangeOrThrow(CitationGroup cg) { + NamedRange namedRange = this.cgidToNamedRange.get(cg.cgid); + if (namedRange == null) { + LOGGER.warn("getNamedRange: could not lookup namedRange"); + throw new RuntimeException("getNamedRange: could not lookup namedRange"); + } + return namedRange; + } + + public void removeCitationGroup(CitationGroup cg, XTextDocument doc) + throws + WrappedTargetException, + NoDocumentException, + NoSuchElementException, + NotRemoveableException, + IllegalTypeException, + PropertyExistException { + + NamedRange namedRange = getNamedRangeOrThrow(cg); + String refMarkName = namedRange.nrGetRangeName(); + namedRange.nrRemoveFromDocument(doc); + UnoUserDefinedProperty.removeIfExists(doc, refMarkName); + this.cgidToNamedRange.remove(cg.cgid); + } + + /** + * + * @return Optional.empty if the reference mark is missing. + * + */ + public Optional getMarkRange(CitationGroup cg, XTextDocument doc) + throws + NoDocumentException, + WrappedTargetException { + + NamedRange namedRange = getNamedRangeOrThrow(cg); + return namedRange.nrGetMarkRange(doc); + } + + /** + * Cursor for the reference marks as is, not prepared for filling, + * but does not need cleanFillCursorForCitationGroup either. + */ + public Optional getRawCursorForCitationGroup(CitationGroup cg, XTextDocument doc) + throws + NoDocumentException, + WrappedTargetException { + NamedRange namedRange = getNamedRangeOrThrow(cg); + return namedRange.nrGetRawCursor(doc); + } + + /** + * Must be followed by call to cleanFillCursorForCitationGroup + */ + public XTextCursor getFillCursorForCitationGroup(CitationGroup cg, XTextDocument doc) + throws + NoDocumentException, + WrappedTargetException, + CreationException { + + NamedRange namedRange = getNamedRangeOrThrow(cg); + return namedRange.nrGetFillCursor(doc); + } + + /** To be called after getFillCursorForCitationGroup */ + public void cleanFillCursorForCitationGroup(CitationGroup cg, XTextDocument doc) + throws + NoDocumentException, + WrappedTargetException, + CreationException { + NamedRange namedRange = getNamedRangeOrThrow(cg); + namedRange.nrCleanFillCursor(doc); + } + + public List getCitationEntries(XTextDocument doc, CitationGroups cgs) + throws + UnknownPropertyException, + WrappedTargetException, + NoDocumentException { + + switch (dataModel) { + case JabRef52: + // One context per CitationGroup: Backend52 (DataModel.JabRef52) + // For DataModel.JabRef53 (Backend53) we need one context per Citation + int n = cgs.numberOfCitationGroups(); + List citations = new ArrayList<>(n); + for (CitationGroup cg : cgs.getCitationGroupsUnordered()) { + String name = cg.cgid.citationGroupIdAsString(); + XTextCursor cursor = (this + .getRawCursorForCitationGroup(cg, doc) + .orElseThrow(RuntimeException::new)); + String context = GetContext.getCursorStringWithContext(cursor, 30, 30, true); + Optional pageInfo = (cg.numberOfCitations() > 0 + ? (getPageInfoFromData(cg) + .map(e -> OOText.toString(e))) + : Optional.empty()); + CitationEntry entry = new CitationEntry(name, context, pageInfo); + citations.add(entry); + } + return citations; + case JabRef53: + // xx + throw new RuntimeException("getCitationEntries for JabRef53 is not implemented yet"); + default: + throw new RuntimeException("getCitationEntries: unhandled dataModel "); + } + } + + /* + * Only applies to storage. Citation markers are not changed. + */ + public void applyCitationEntries(XTextDocument doc, List citationEntries) + throws + UnknownPropertyException, + NotRemoveableException, + PropertyExistException, + PropertyVetoException, + IllegalTypeException, + IllegalArgumentException, + NoDocumentException, + WrappedTargetException { + + switch (dataModel) { + case JabRef52: + for (CitationEntry entry : citationEntries) { + Optional pageInfo = entry.getPageInfo().map(OOText::fromString); + pageInfo = PageInfo.normalizePageInfo(pageInfo); + if (pageInfo.isPresent()) { + String name = entry.getRefMarkName(); + UnoUserDefinedProperty.createStringProperty(doc, name, pageInfo.get().asString()); + } + } + break; + case JabRef53: + // xx + throw new RuntimeException("applyCitationEntries for JabRef53 is not implemented yet"); + default: + throw new RuntimeException("applyCitationEntries: unhandled dataModel "); + } + } + +} + diff --git a/src/main/java/org/jabref/logic/openoffice/backend/Codec52.java b/src/main/java/org/jabref/logic/openoffice/backend/Codec52.java new file mode 100644 index 00000000000..352df27cdeb --- /dev/null +++ b/src/main/java/org/jabref/logic/openoffice/backend/Codec52.java @@ -0,0 +1,148 @@ +package org.jabref.logic.openoffice.backend; + +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import org.jabref.model.openoffice.style.CitationType; +import org.jabref.model.openoffice.uno.NoDocumentException; + +/** + * How and what is encoded in a mark names. + * + * - pageInfo does not appear here. It is not encoded in the mark name. + * - Does not depend on the type of marks (reference mark of bookmark) used. + */ +class Codec52 { + private static final String BIB_CITATION = "JR_cite"; + private static final Pattern CITE_PATTERN = + // Pattern.compile(BIB_CITATION + "(\\d*)_(\\d*)_(.*)"); + // citationType is always "1" "2" or "3" + Pattern.compile(BIB_CITATION + "(\\d*)_([123])_(.*)"); + + /** + * This is what we get back from parsing a refMarkName. + * + */ + public static class ParsedMarkName { + /** "", "0", "1" ... */ + public final String i; + /** in-text-citation type */ + public final CitationType citationType; + /** Citation keys embedded in the reference mark. */ + public final List citationKeys; + + ParsedMarkName(String i, CitationType citationType, List citationKeys) { + Objects.requireNonNull(i); + Objects.requireNonNull(citationKeys); + this.i = i; + this.citationType = citationType; + this.citationKeys = citationKeys; + } + } + + /* + * Integer representation was written into the document in + * JabRef52, keep it for compatibility. + */ + public static CitationType CitationTypeFromInt(int i) { + switch (i) { + case 1: + return CitationType.AUTHORYEAR_PAR; + case 2: + return CitationType.AUTHORYEAR_INTEXT; + case 3: + return CitationType.INVISIBLE_CIT; + default: + throw new RuntimeException("Invalid CitationType code"); + } + } + + public static int CitationTypeToInt(CitationType i) { + switch (i) { + case AUTHORYEAR_PAR: + return 1; + case AUTHORYEAR_INTEXT: + return 2; + case INVISIBLE_CIT: + return 3; + default: + throw new RuntimeException("Invalid CitationType"); + } + } + + /** + * Produce a reference mark name for JabRef for the given citation + * key and citationType that does not yet appear among the reference + * marks of the document. + * + * @param bibtexKey The citation key. + * @param citationType Encodes the effect of withText and + * inParenthesis options. + * + * The first occurrence of bibtexKey gets no serial number, the + * second gets 0, the third 1 ... + * + * Or the first unused in this series, after removals. + */ + public static String getUniqueMarkName(Set usedNames, + String bibtexKey, + CitationType citationType) + throws + NoDocumentException { + + int i = 0; + int j = CitationTypeToInt(citationType); + String name = BIB_CITATION + '_' + j + '_' + bibtexKey; + while (usedNames.contains(name)) { + name = BIB_CITATION + i + '_' + j + '_' + bibtexKey; + i++; + } + return name; + } + + /** + * Parse a JabRef (reference) mark name. + * + * @return Optional.empty() on failure. + * + */ + public static Optional parseMarkName(String refMarkName) { + + Matcher citeMatcher = CITE_PATTERN.matcher(refMarkName); + if (!citeMatcher.find()) { + return Optional.empty(); + } + + List keys = Arrays.asList(citeMatcher.group(3).split(",")); + String i = citeMatcher.group(1); + int j = Integer.parseInt(citeMatcher.group(2)); + CitationType citationType = CitationTypeFromInt(j); + return (Optional.of(new Codec52.ParsedMarkName(i, citationType, keys))); + } + + /** + * @return true if name matches the pattern used for JabRef + * reference mark names. + */ + public static boolean isJabRefReferenceMarkName(String name) { + return (CITE_PATTERN.matcher(name).find()); + } + + /** + * Filter a list of reference mark names by `isJabRefReferenceMarkName` + * + * @param names The list to be filtered. + */ + public static List filterIsJabRefReferenceMarkName(List names) { + return (names + .stream() + .filter(Codec52::isJabRefReferenceMarkName) + .collect(Collectors.toList())); + } +} diff --git a/src/main/java/org/jabref/logic/openoffice/backend/GetContext.java b/src/main/java/org/jabref/logic/openoffice/backend/GetContext.java new file mode 100644 index 00000000000..468d630e756 --- /dev/null +++ b/src/main/java/org/jabref/logic/openoffice/backend/GetContext.java @@ -0,0 +1,87 @@ +package org.jabref.logic.openoffice.backend; + +import org.jabref.model.openoffice.uno.NoDocumentException; + +import com.sun.star.lang.WrappedTargetException; +import com.sun.star.text.XTextCursor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Utility methods for processing OO Writer documents. + */ +public class GetContext { + + private static final Logger LOGGER = LoggerFactory.getLogger(GetContext.class); + + private GetContext() { + // Just to hide the public constructor + } + + /** + * Get the text belonging to cursor with up to + * charBefore and charAfter characters of context. + * + * The actual context may be smaller than requested. + * + * @param cursor + * @param charBefore Number of characters requested. + * @param charAfter Number of characters requested. + * @param htmlMarkup If true, the text belonging to the + * reference mark is surrounded by bold html tag. + * + */ + public static String getCursorStringWithContext(XTextCursor cursor, + int charBefore, + int charAfter, + boolean htmlMarkup) + throws + WrappedTargetException, + NoDocumentException { + + String citPart = cursor.getString(); + + // extend cursor range left + int flex = 8; + for (int i = 0; i < charBefore; i++) { + try { + cursor.goLeft((short) 1, true); + // If we are close to charBefore and see a space, + // then cut here. Might avoid cutting a word in half. + if ((i >= (charBefore - flex)) + && Character.isWhitespace(cursor.getString().charAt(0))) { + break; + } + } catch (IndexOutOfBoundsException ex) { + LOGGER.warn("Problem going left", ex); + } + } + + int lengthWithBefore = cursor.getString().length(); + int addedBefore = lengthWithBefore - citPart.length(); + + cursor.collapseToStart(); + for (int i = 0; i < (charAfter + lengthWithBefore); i++) { + try { + cursor.goRight((short) 1, true); + if (i >= ((charAfter + lengthWithBefore) - flex)) { + String strNow = cursor.getString(); + if (Character.isWhitespace(strNow.charAt(strNow.length() - 1))) { + break; + } + } + } catch (IndexOutOfBoundsException ex) { + LOGGER.warn("Problem going right", ex); + } + } + + String result = cursor.getString(); + if (htmlMarkup) { + result = (result.substring(0, addedBefore) + + "" + citPart + "" + + result.substring(lengthWithBefore)); + } + return result.trim(); + } + +} diff --git a/src/main/java/org/jabref/logic/openoffice/backend/NamedRangeManagerReferenceMark.java b/src/main/java/org/jabref/logic/openoffice/backend/NamedRangeManagerReferenceMark.java new file mode 100644 index 00000000000..157dd87cf0c --- /dev/null +++ b/src/main/java/org/jabref/logic/openoffice/backend/NamedRangeManagerReferenceMark.java @@ -0,0 +1,50 @@ +package org.jabref.logic.openoffice.backend; + +import java.util.List; +import java.util.Optional; + +import org.jabref.model.openoffice.backend.NamedRange; +import org.jabref.model.openoffice.backend.NamedRangeManager; +import org.jabref.model.openoffice.uno.CreationException; +import org.jabref.model.openoffice.uno.NoDocumentException; +import org.jabref.model.openoffice.uno.UnoReferenceMark; + +import com.sun.star.lang.WrappedTargetException; +import com.sun.star.text.XTextCursor; +import com.sun.star.text.XTextDocument; + +public class NamedRangeManagerReferenceMark implements NamedRangeManager { + + @Override + public NamedRange nrmCreate(XTextDocument doc, + String refMarkName, + XTextCursor position, + boolean insertSpaceAfter, + boolean withoutBrackets) + throws + CreationException { + return NamedRangeReferenceMark.create(doc, + refMarkName, + position, + insertSpaceAfter, + withoutBrackets); + } + + @Override + public List nrmGetUsedNames(XTextDocument doc) + throws + NoDocumentException { + return UnoReferenceMark.getListOfNames(doc); + } + + @Override + public Optional nrmGetFromDocument(XTextDocument doc, String refMarkName) + throws + NoDocumentException, + WrappedTargetException { + return (NamedRangeReferenceMark + .getFromDocument(doc, refMarkName) + .map(x -> x)); + } +} + diff --git a/src/main/java/org/jabref/logic/openoffice/backend/NamedRangeReferenceMark.java b/src/main/java/org/jabref/logic/openoffice/backend/NamedRangeReferenceMark.java new file mode 100644 index 00000000000..38ac716d9ac --- /dev/null +++ b/src/main/java/org/jabref/logic/openoffice/backend/NamedRangeReferenceMark.java @@ -0,0 +1,500 @@ +package org.jabref.logic.openoffice.backend; + +import java.util.Optional; + +import org.jabref.model.openoffice.backend.NamedRange; +import org.jabref.model.openoffice.uno.CreationException; +import org.jabref.model.openoffice.uno.NoDocumentException; +import org.jabref.model.openoffice.uno.UnoCursor; +import org.jabref.model.openoffice.uno.UnoReferenceMark; + +import com.sun.star.container.NoSuchElementException; +import com.sun.star.lang.WrappedTargetException; +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 org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +// was StorageBaseRefMark + +class NamedRangeReferenceMark implements NamedRange { + + private static final String ZERO_WIDTH_SPACE = "\u200b"; + + // for debugging we may want visible bracket + private static final boolean + REFERENCE_MARK_USE_INVISIBLE_BRACKETS = true; // !debug; + + public static final String + REFERENCE_MARK_LEFT_BRACKET = REFERENCE_MARK_USE_INVISIBLE_BRACKETS ? ZERO_WIDTH_SPACE : "<"; + + public static final String + REFERENCE_MARK_RIGHT_BRACKET = REFERENCE_MARK_USE_INVISIBLE_BRACKETS ? ZERO_WIDTH_SPACE : ">"; + + private static final Logger LOGGER = LoggerFactory.getLogger(NamedRangeReferenceMark.class); + + private String id; /* reference mark name */ + + private NamedRangeReferenceMark(String id) { + this.id = id; + } + + String getId() { + return id; + } + + /** + * Insert {@code n} spaces in a way that reference + * marks just before or just after the cursor are not affected. + * + * This is based on the observation, that starting two + * new paragraphs separates us from a reference mark on either side. + * + * The pattern used is: + * {@code safeInsertSpaces(n): para, para, left, space(n), right-delete, left(n), left-delete} + * + * @param position Where to insert (at position.getStart()) + * @param n Number of spaces to insert. + * + * @return a new cursor, covering the just-inserted spaces. + * + * This could be generalized to insert arbitrary text safely + * between two reference marks. But we do not need that now. + */ + private static XTextCursor safeInsertSpacesBetweenReferenceMarks(XTextRange position, int n) { + // Start with an empty cursor at position.getStart(); + XText text = position.getText(); + XTextCursor cursor = text.createTextCursorByRange(position.getStart()); + text.insertString(cursor, "\r\r", false); // para, para + cursor.goLeft((short) 1, false); // left + text.insertString(cursor, " ".repeat(n), false); // space(n) + cursor.goRight((short) 1, true); + cursor.setString(""); // right-delete + cursor.goLeft((short) n, false); // left(n) + cursor.goLeft((short) 1, true); + cursor.setString(""); // left-delete + cursor.goRight((short) n, true); // select the newly inserted spaces + return cursor; + } + + private static void createReprInDocument(XTextDocument doc, + String refMarkName, + XTextCursor position, + boolean insertSpaceAfter, + boolean withoutBrackets) + throws + CreationException { + + // The cursor we received: we push it before us. + position.collapseToEnd(); + + XTextCursor cursor = safeInsertSpacesBetweenReferenceMarks(position.getEnd(), 2); + + // cursors before the first and after the last space + XTextCursor cursorBefore = cursor.getText().createTextCursorByRange(cursor.getStart()); + XTextCursor cursorAfter = cursor.getText().createTextCursorByRange(cursor.getEnd()); + + cursor.collapseToStart(); + cursor.goRight((short) 1, false); + // now we are between two spaces + + final String left = NamedRangeReferenceMark.REFERENCE_MARK_LEFT_BRACKET; + final String right = NamedRangeReferenceMark.REFERENCE_MARK_RIGHT_BRACKET; + final short leftLength = (short) left.length(); + final short rightLength = (short) right.length(); + String bracketedContent = (withoutBrackets + ? "" + : left + right); + + cursor.getText().insertString(cursor, bracketedContent, true); + + UnoReferenceMark.create(doc, refMarkName, cursor, true /* absorb */); + + cursorBefore.goRight((short) 1, true); + cursorBefore.setString(""); + if (!insertSpaceAfter) { + cursorAfter.goLeft((short) 1, true); + cursorAfter.setString(""); + } + } + + static NamedRangeReferenceMark create(XTextDocument doc, + String refMarkName, + XTextCursor position, + boolean insertSpaceAfter, + boolean withoutBrackets) + throws + CreationException { + + createReprInDocument(doc, + refMarkName, + position, + insertSpaceAfter, + withoutBrackets); + return new NamedRangeReferenceMark(refMarkName); + } + + /** + * @return Optional.empty if there is no corresponding range. + */ + static Optional getFromDocument(XTextDocument doc, + String refMarkName) + throws + NoDocumentException, + WrappedTargetException { + return (UnoReferenceMark.getAnchor(doc, refMarkName) + .map(e -> new NamedRangeReferenceMark(refMarkName))); + } + + /* + * Remove it from the document. + * + * See: removeCitationGroups + */ + @Override + public void nrRemoveFromDocument(XTextDocument doc) + throws + WrappedTargetException, + NoDocumentException, + NoSuchElementException { + UnoReferenceMark.remove(doc, this.nrGetRangeName()); + } + + @Override + public String nrGetRangeName() { + return id; + } + + /* + * ranges controlled by citation groups should not overlap with each other. + * + * @return Optional.empty if the reference mark is missing. + * + * See: UnoReferenceMark.getAnchor + */ + @Override + public Optional nrGetMarkRange(XTextDocument doc) + throws + NoDocumentException, + WrappedTargetException { + String name = this.nrGetRangeName(); + return UnoReferenceMark.getAnchor(doc, name); + } + + /** + * Cursor for the reference marks as is, not prepared for filling, + * but does not need nrCleanFillCursor either. + * + * @return Optional.empty() if reference mark is missing from the document, + * otherwise an XTextCursor for getMarkRange + * + * See: getRawCursorForCitationGroup + */ + @Override + public Optional nrGetRawCursor(XTextDocument doc) + throws + NoDocumentException, + WrappedTargetException { + + String name = this.nrGetRangeName(); + Optional full = Optional.empty(); + + Optional markAsTextContent = UnoReferenceMark.getAsTextContent(doc, name); + + if (markAsTextContent.isEmpty()) { + String msg = String.format("nrGetRawCursor: markAsTextContent(%s).isEmpty()", name); + LOGGER.warn(msg); + } + + full = UnoCursor.getTextCursorOfTextContentAnchor(markAsTextContent.get()); + if (full.isEmpty()) { + String msg = "nrGetRawCursor: full.isEmpty()"; + LOGGER.warn(msg); + return Optional.empty(); + } + return full; + } + + /** + * See: getFillCursorForCitationGroup + */ + @Override + public XTextCursor nrGetFillCursor(XTextDocument doc) + throws + NoDocumentException, + WrappedTargetException, + CreationException { + + String name = this.nrGetRangeName(); + + final boolean debugThisFun = false; + final String left = NamedRangeReferenceMark.REFERENCE_MARK_LEFT_BRACKET; + final String right = NamedRangeReferenceMark.REFERENCE_MARK_RIGHT_BRACKET; + final short leftLength = (short) left.length(); + final short rightLength = (short) right.length(); + + XTextCursor full = null; + String fullText = null; + for (int i = 1; i <= 2; i++) { + Optional markAsTextContent = UnoReferenceMark.getAsTextContent(doc, name); + + if (markAsTextContent.isEmpty()) { + String msg = String.format("nrGetFillCursor:" + + " markAsTextContent(%s).isEmpty (attempt %d)", + name, + i); + throw new RuntimeException(msg); + } + + full = UnoCursor.getTextCursorOfTextContentAnchor(markAsTextContent.get()).orElse(null); + if (full == null) { + String msg = String.format("nrGetFillCursor: full == null (attempt %d)", i); + throw new RuntimeException(msg); + } + + fullText = full.getString(); + + if (debugThisFun) { + System.out.printf("nrGetFillCursor: fulltext = '%s'%n", fullText); + } + + if (fullText.length() >= 2) { + if (debugThisFun) { + System.out.printf("nrGetFillCursor: (attempt: %d) fulltext.length() >= 2," + + " break loop%n", i); + } + break; + } else { + // (fullText.length() < 2) + if (i == 2) { + String msg = String.format("nrGetFillCursor:" + + " (fullText.length() < 2) (attempt %d)", + i); + throw new RuntimeException(msg); + } + // too short, recreate + if (true || debugThisFun) { + System.out.println("nrGetFillCursor: too short, recreate"); + } + full.setString(""); + try { + UnoReferenceMark.remove(doc, name); + } catch (NoSuchElementException ex) { + String msg = String.format("nrGetFillCursor got NoSuchElementException" + + " for '%s'", + name); + LOGGER.warn(msg); + } + createReprInDocument(doc, + name, + full, + false, /* insertSpaceAfter */ + false /* withoutBrackets */); + } + } + + if (full == null) { + throw new RuntimeException("nrGetFillCursorFor: full == null (after loop)"); + } + if (fullText == null) { + throw new RuntimeException("nrGetFillCursor: fullText == null (after loop)"); + } + + fullText = full.getString(); + if (fullText.length() < 2) { + throw new RuntimeException("nrGetFillCursor: fullText.length() < 2 (after loop)'%n"); + } + XTextCursor beta = full.getText().createTextCursorByRange(full); + beta.collapseToStart(); + beta.goRight((short) 1, false); + beta.goRight((short) (fullText.length() - 2), true); + if (debugThisFun) { + System.out.printf("nrGetFillCursor: beta(1) covers '%s'%n", beta.getString()); + } + if (fullText.startsWith(left) && fullText.endsWith(right)) { + beta.setString(""); + } else { + if (debugThisFun) { + String msg = String.format("nrGetFillCursor: recreating brackets for '%s'", fullText); + // LOGGER.warn(msg); + System.out.println(msg); + } + + // we have at least two characters inside + XTextCursor alpha = full.getText().createTextCursorByRange(full); + alpha.collapseToStart(); + + XTextCursor omega = full.getText().createTextCursorByRange(full); + omega.collapseToEnd(); + + // beta now covers everything except first and last character + // Replace its content with brackets + String paddingx = "x"; + String paddingy = "y"; + String paddingz = "z"; + beta.setString(paddingx + left + paddingy + right + paddingz); + if (debugThisFun) { + System.out.printf("nrGetFillCursor: beta(2) covers '%s'%n", beta.getString()); + } + // move beta to before the right bracket + beta.collapseToEnd(); + beta.goLeft((short) (rightLength + 1), false); + // remove middle padding + beta.goLeft((short) 1, true); + if (debugThisFun) { + System.out.printf("nrGetFillCursor: beta(3) covers '%s'%n", beta.getString()); + } + // only drop paddingy later: beta.setString(""); + + // drop the initial character and paddingx + alpha.collapseToStart(); + alpha.goRight((short) (1 + 1), true); + if (debugThisFun) { + System.out.printf("nrGetFillCursor: alpha(4) covers '%s'%n", alpha.getString()); + } + alpha.setString(""); + // drop the last character and paddingz + omega.collapseToEnd(); + omega.goLeft((short) (1 + 1), true); + if (debugThisFun) { + System.out.printf("nrGetFillCursor: omega(5) covers '%s'%n", omega.getString()); + } + omega.setString(""); + + // drop paddingy now + if (debugThisFun) { + System.out.printf("nrGetFillCursor: beta(6) covers '%s'%n", beta.getString()); + } + beta.setString(""); + // should be OK now. + if (debugThisFun) { + alpha.goRight(leftLength, true); + System.out.printf("nrGetFillCursor: alpha(7) covers '%s', should be '%s'%n", + alpha.getString(), left); + omega.goLeft(rightLength, true); + System.out.printf("nrGetFillCursor: omega(8) covers '%s', should be '%s'%n", + omega.getString(), right); + } + } + + // NamedRangeReferenceMark.checkFillCursor(beta); + return beta; + } + + /* + * Throw RuntimeException if the brackets are not there. + */ + public static void checkFillCursor(XTextCursor cursor) { + final String left = REFERENCE_MARK_LEFT_BRACKET; + final String right = REFERENCE_MARK_RIGHT_BRACKET; + final short leftLength = (short) left.length(); + final short rightLength = (short) right.length(); + + XTextCursor alpha = cursor.getText().createTextCursorByRange(cursor); + alpha.collapseToStart(); + + XTextCursor omega = cursor.getText().createTextCursorByRange(cursor); + omega.collapseToEnd(); + + if (leftLength > 0) { + alpha.goLeft(leftLength, true); + if (!left.equals(alpha.getString())) { + String msg = String.format("checkFillCursor:" + + " ('%s') is not prefixed with" + + " REFERENCE_MARK_LEFT_BRACKET, has '%s'", + cursor.getString(), alpha.getString()); + throw new RuntimeException(msg); + } + } + + if (rightLength > 0) { + omega.goRight(rightLength, true); + if (!right.equals(omega.getString())) { + String msg = String.format("checkFillCursor:" + + " ('%s') is not followed by" + + " REFERENCE_MARK_RIGHT_BRACKET, has '%s'", + cursor.getString(), omega.getString()); + throw new RuntimeException(msg); + } + } + } + + /** + * Remove brackets, but if the result would become empty, leave + * them; if the result would be a single characer, leave the left bracket. + * + * See: cleanFillCursorForCitationGroup + */ + @Override + public void nrCleanFillCursor(XTextDocument doc) + throws + NoDocumentException, + WrappedTargetException, + CreationException { + + // alwaysRemoveBrackets : full compatibility with JabRef 5.2: + // brackets are temporary, only exist between nrGetFillCursor + // and nrCleanFillCursor. + final boolean alwaysRemoveBrackets = false; + + // removeBracketsFromEmpty is intended to force removal if we + // are working on an "Empty citation" (INVISIBLE_CIT). + final boolean removeBracketsFromEmpty = false; + + final String left = REFERENCE_MARK_LEFT_BRACKET; + final String right = REFERENCE_MARK_RIGHT_BRACKET; + final short leftLength = (short) left.length(); + final short rightLength = (short) right.length(); + + String name = this.nrGetRangeName(); + + XTextCursor full = this.nrGetRawCursor(doc).orElseThrow(RuntimeException::new); + final String fullText = full.getString(); + final int fullTextLength = fullText.length(); + + if (!fullText.startsWith(left)) { + String msg = String.format("nrCleanFillCursor:" + + " (%s) does not start with REFERENCE_MARK_LEFT_BRACKET", + name); + throw new RuntimeException(msg); + } + + if (!fullText.endsWith(right)) { + String msg = String.format("nrCleanFillCursor:" + + " (%s) does not end with REFERENCE_MARK_RIGHT_BRACKET", + name); + throw new RuntimeException(msg); + } + + final int contentLength = (fullTextLength - (leftLength + rightLength)); + if (contentLength < 0) { + String msg = String.format("nrCleanFillCursor: length(%s) < 0", name); + throw new RuntimeException(msg); + } + + boolean removeRight = ((contentLength >= 1) + || ((contentLength == 0) && removeBracketsFromEmpty) + || alwaysRemoveBrackets); + + boolean removeLeft = ((contentLength >= 2) + || ((contentLength == 0) && removeBracketsFromEmpty) + || alwaysRemoveBrackets); + + if (removeRight) { + XTextCursor omega = full.getText().createTextCursorByRange(full); + omega.collapseToEnd(); + omega.goLeft(rightLength, true); + omega.setString(""); + } + + if (removeLeft) { + XTextCursor alpha = full.getText().createTextCursorByRange(full); + alpha.collapseToStart(); + alpha.goRight(leftLength, true); + alpha.setString(""); + } + } +} diff --git a/src/main/java/org/jabref/model/openoffice/backend/NamedRange.java b/src/main/java/org/jabref/model/openoffice/backend/NamedRange.java new file mode 100644 index 00000000000..e5c54773b07 --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/backend/NamedRange.java @@ -0,0 +1,65 @@ +package org.jabref.model.openoffice.backend; + +import java.util.Optional; + +import org.jabref.model.openoffice.uno.CreationException; +import org.jabref.model.openoffice.uno.NoDocumentException; + +import com.sun.star.container.NoSuchElementException; +import com.sun.star.lang.WrappedTargetException; +import com.sun.star.text.XTextCursor; +import com.sun.star.text.XTextDocument; +import com.sun.star.text.XTextRange; + +public interface NamedRange { + + public String nrGetRangeName(); + + /** + * @return Optional.empty if the mark is missing from the document. + */ + public Optional nrGetMarkRange(XTextDocument doc) + throws + NoDocumentException, + WrappedTargetException; + + /** + * Cursor for the reference marks as is, not prepared for filling, + * but does not need nrCleanFillCursor either. + */ + public Optional nrGetRawCursor(XTextDocument doc) + throws + NoDocumentException, + WrappedTargetException; + + /** + * Get a cursor for filling in text. + * + * Must be followed by nrCleanFillCursor() + */ + public XTextCursor nrGetFillCursor(XTextDocument doc) + throws + NoDocumentException, + WrappedTargetException, + CreationException; + + /** + * Remove brackets, but if the result would become empty, leave + * them; if the result would be a single characer, leave the left bracket. + * + */ + public void nrCleanFillCursor(XTextDocument doc) + throws + NoDocumentException, + WrappedTargetException, + CreationException; + + /** + * Note: create is in NamedRangeManager + */ + public void nrRemoveFromDocument(XTextDocument doc) + throws + WrappedTargetException, + NoDocumentException, + NoSuchElementException; +} diff --git a/src/main/java/org/jabref/model/openoffice/backend/NamedRangeManager.java b/src/main/java/org/jabref/model/openoffice/backend/NamedRangeManager.java new file mode 100644 index 00000000000..f53fcc72527 --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/backend/NamedRangeManager.java @@ -0,0 +1,31 @@ +package org.jabref.model.openoffice.backend; + +import java.util.List; +import java.util.Optional; + +import org.jabref.model.openoffice.uno.CreationException; +import org.jabref.model.openoffice.uno.NoDocumentException; + +import com.sun.star.lang.WrappedTargetException; +import com.sun.star.text.XTextCursor; +import com.sun.star.text.XTextDocument; + +public interface NamedRangeManager { + + public NamedRange nrmCreate(XTextDocument doc, + String markName, + XTextCursor position, + boolean insertSpaceAfter, + boolean withoutBrackets) + throws + CreationException; + + public List nrmGetUsedNames(XTextDocument doc) + throws + NoDocumentException; + + public Optional nrmGetFromDocument(XTextDocument doc, String markName) + throws + NoDocumentException, + WrappedTargetException; +} diff --git a/src/main/resources/l10n/JabRef_en.properties b/src/main/resources/l10n/JabRef_en.properties index 0d5281b27a2..c2d0ea46ee7 100644 --- a/src/main/resources/l10n/JabRef_en.properties +++ b/src/main/resources/l10n/JabRef_en.properties @@ -2295,6 +2295,7 @@ Custom\ DOI\ URI=Custom DOI URI Customization=Customization Use\ custom\ DOI\ base\ URI\ for\ article\ access=Use custom DOI base URI for article access +Cited\ on\ pages=Cited on pages Unable\ to\ find\ valid\ certification\ path\ to\ requested\ target(%0),\ download\ anyway?=Unable to find valid certification path to requested target(%0), download anyway? Download\ operation\ canceled.=Download operation canceled. From 0fed61823160b35be7d7b0e9be2e8d80863c584e Mon Sep 17 00:00:00 2001 From: Antal K Date: Thu, 3 Jun 2021 11:52:31 +0200 Subject: [PATCH 17/51] add frontend --- .../logic/openoffice/frontend/OOFrontend.java | 615 ++++++++++++++++++ .../frontend/RangeForOverlapCheck.java | 49 ++ .../frontend/UpdateBibliography.java | 153 +++++ .../frontend/UpdateCitationMarkers.java | 174 +++++ src/main/resources/l10n/JabRef_en.properties | 4 + 5 files changed, 995 insertions(+) create mode 100644 src/main/java/org/jabref/logic/openoffice/frontend/OOFrontend.java create mode 100644 src/main/java/org/jabref/logic/openoffice/frontend/RangeForOverlapCheck.java create mode 100644 src/main/java/org/jabref/logic/openoffice/frontend/UpdateBibliography.java create mode 100644 src/main/java/org/jabref/logic/openoffice/frontend/UpdateCitationMarkers.java diff --git a/src/main/java/org/jabref/logic/openoffice/frontend/OOFrontend.java b/src/main/java/org/jabref/logic/openoffice/frontend/OOFrontend.java new file mode 100644 index 00000000000..fd0523a4bf8 --- /dev/null +++ b/src/main/java/org/jabref/logic/openoffice/frontend/OOFrontend.java @@ -0,0 +1,615 @@ +package org.jabref.logic.openoffice.frontend; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; + +import org.jabref.logic.JabRefException; +import org.jabref.logic.l10n.Localization; +import org.jabref.logic.openoffice.backend.Backend52; +import org.jabref.model.openoffice.CitationEntry; +import org.jabref.model.openoffice.ootext.OOText; +import org.jabref.model.openoffice.rangesort.FunctionalTextViewCursor; +import org.jabref.model.openoffice.rangesort.RangeOverlap; +import org.jabref.model.openoffice.rangesort.RangeOverlapBetween; +import org.jabref.model.openoffice.rangesort.RangeOverlapWithin; +import org.jabref.model.openoffice.rangesort.RangeSet; +import org.jabref.model.openoffice.rangesort.RangeSort; +import org.jabref.model.openoffice.rangesort.RangeSortEntry; +import org.jabref.model.openoffice.rangesort.RangeSortVisual; +import org.jabref.model.openoffice.rangesort.RangeSortable; +import org.jabref.model.openoffice.style.CitationGroup; +import org.jabref.model.openoffice.style.CitationGroupId; +import org.jabref.model.openoffice.style.CitationGroups; +import org.jabref.model.openoffice.style.CitationType; +import org.jabref.model.openoffice.style.OODataModel; +import org.jabref.model.openoffice.uno.CreationException; +import org.jabref.model.openoffice.uno.NoDocumentException; +import org.jabref.model.openoffice.uno.UnoCursor; +import org.jabref.model.openoffice.uno.UnoTextRange; +import org.jabref.model.openoffice.util.OOListUtil; +import org.jabref.model.openoffice.util.OOVoidResult; + +import com.sun.star.beans.IllegalTypeException; +import com.sun.star.beans.NotRemoveableException; +import com.sun.star.beans.PropertyExistException; +import com.sun.star.beans.PropertyVetoException; +import com.sun.star.beans.UnknownPropertyException; +import com.sun.star.container.NoSuchElementException; +import com.sun.star.lang.WrappedTargetException; +import com.sun.star.text.XTextCursor; +import com.sun.star.text.XTextDocument; +import com.sun.star.text.XTextRange; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class OOFrontend { + private static final Logger LOGGER = LoggerFactory.getLogger(OOFrontend.class); + public final Backend52 backend; + public final CitationGroups citationGroups; + + public OOFrontend(XTextDocument doc) + throws + NoDocumentException, + WrappedTargetException { + + // TODO: dataModel should come from looking at the + // document and preferences. + // + this.backend = new Backend52(); + + // Get the citationGroupNames + List citationGroupNames = this.backend.getJabRefReferenceMarkNames(doc); + + Map citationGroups = + readCitationGroupsFromDocument(this.backend, doc, citationGroupNames); + this.citationGroups = new CitationGroups(citationGroups); + } + + public OODataModel getDataModel() { + return backend.dataModel; + } + + public Optional healthReport(XTextDocument doc) + throws + NoDocumentException { + return backend.healthReport(doc); + } + + private static Map + readCitationGroupsFromDocument(Backend52 backend, + XTextDocument doc, + List citationGroupNames) + throws + WrappedTargetException, + NoDocumentException { + + Map citationGroups = new HashMap<>(); + for (int i = 0; i < citationGroupNames.size(); i++) { + final String name = citationGroupNames.get(i); + CitationGroup cg = backend.readCitationGroupFromDocumentOrThrow(doc, name); + citationGroups.put(cg.cgid, cg); + } + return citationGroups; + } + + /** + * Creates a list of {@code RangeSortable} values for + * our {@code CitationGroup} values. Originally designed to be + * passed to {@code visualSort}. + * + * The elements of the returned list are actually of type + * {@code RangeSortEntry}. + * + * The result is sorted within {@code XTextRange.getText()} + * partitions of the citation groups according to their {@code XTextRange} + * (before mapping to footnote marks). + * + * In the result, RangeSortable.getIndexInPosition() contains + * unique indexes within the original partition (not after + * mapFootnotesToFootnoteMarks). + * + * @param mapFootnotesToFootnoteMarks If true, replace ranges in + * footnotes with the range of the corresponding footnote + * mark. This is used for numbering the citations. + * + */ + private List> + createVisualSortInput(XTextDocument doc, boolean mapFootnotesToFootnoteMarks) + throws + NoDocumentException, + WrappedTargetException { + + List> sortables = new ArrayList<>(); + for (CitationGroup cg : citationGroups.getCitationGroupsUnordered()) { + XTextRange range = (this + .getMarkRange(doc, cg) + .orElseThrow(RuntimeException::new)); + sortables.add(new RangeSortEntry<>(range, 0, cg)); + } + + /* + * At this point we are almost ready to return sortables. + * + * But we may want to number citations in a footnote + * as if it appeared where the footnote mark is. + * + * The following code replaces ranges within footnotes with + * the range for the corresponding footnote mark. + * + * This brings further ambiguity if we have multiple + * citation groups within the same footnote: for the comparison + * they become indistinguishable. Numbering between them is + * not controlled. Also combineCiteMarkers will see them in + * the wrong order (if we use this comparison), and will not + * be able to merge. To avoid these, we sort textually within + * each .getText() partition and add indexInPosition + * accordingly. + * + */ + + // Sort within partitions + RangeSort.RangePartitions> partitions = + RangeSort.partitionAndSortRanges(sortables); + + // build final list + List> result = new ArrayList<>(); + for (List> partition : partitions.getPartitions()) { + + int indexInPartition = 0; + for (int i = 0; i < partition.size(); i++) { + RangeSortEntry sortable = partition.get(i); + XTextRange aRange = sortable.getRange(); + sortable.setIndexInPosition(indexInPartition++); + if (mapFootnotesToFootnoteMarks) { + Optional footnoteMarkRange = + UnoTextRange.getFootnoteMarkRange(sortable.getRange()); + // Adjust range if we are inside a footnote: + if (footnoteMarkRange.isPresent()) { + sortable.setRange(footnoteMarkRange.get()); + } + } + result.add(sortable); + } + } + return result.stream().map(e -> e).collect(Collectors.toList()); + } + + /** + * @param mapFootnotesToFootnoteMarks If true, sort reference + * marks in footnotes as if they appeared at the + * corresponding footnote mark. + * + * @return citation groups sorted by their visual positions. + * + * Limitation: for two column layout visual (top-down, + * left-right) order does not match the expected (textual) + * order. + * + */ + private List + getVisuallySortedCitationGroups(XTextDocument doc, + boolean mapFootnotesToFootnoteMarks, + FunctionalTextViewCursor fcursor) + throws + WrappedTargetException, + NoDocumentException, + JabRefException { + + List> sortables = + createVisualSortInput(doc, mapFootnotesToFootnoteMarks); + + List> sorted = + RangeSortVisual.visualSort(sortables, + doc, + fcursor); + + List result = + (sorted.stream().map(e -> e.getContent()).collect(Collectors.toList())); + + return result; + } + + /** + * Return citation groups in visual order within (but not across) + * XText partitions. + * + * This is (1) sufficient for combineCiteMarkers which looks for + * consecutive XTextRanges within each XText, (2) not confused by + * multicolumn layout or multipage display. + */ + public List + getCitationGroupsSortedWithinPartitions(XTextDocument doc, boolean mapFootnotesToFootnoteMarks) + throws + NoDocumentException, + WrappedTargetException { + // This is like getVisuallySortedCitationGroups, + // but we skip the visualSort part. + List> sortables = + createVisualSortInput(doc, mapFootnotesToFootnoteMarks); + + return (sortables.stream().map(e -> e.getContent()).collect(Collectors.toList())); + } + + /** + * Create a citation group for the given citation keys, at the + * end of position. + * + * On return {@code position} is collapsed, and is after the + * inserted space, or at the end of the reference mark. + * + * @param citationKeys In storage order + * @param pageInfos In storage order + * @param citationType + * @param position Collapsed to its end. + * @param insertSpaceAfter If true, we insert a space after the mark, that + * carries on format of characters from + * the original position. + */ + public CitationGroup createCitationGroup(XTextDocument doc, + List citationKeys, + List> pageInfos, + CitationType citationType, + XTextCursor position, + boolean insertSpaceAfter) + throws + CreationException, + NoDocumentException, + WrappedTargetException, + NotRemoveableException, + PropertyExistException, + PropertyVetoException, + IllegalTypeException { + + Objects.requireNonNull(pageInfos); + if (pageInfos.size() != citationKeys.size()) { + throw new RuntimeException("pageInfos.size != citationKeys.size"); + } + CitationGroup cg = backend.createCitationGroup(doc, + citationKeys, + pageInfos, + citationType, + position, + insertSpaceAfter); + + this.citationGroups.afterCreateCitationGroup(cg); + return cg; + } + + /** + * Remove {@code cg} both from the document and notify {@code citationGroups} + */ + public void removeCitationGroup(CitationGroup cg, XTextDocument doc) + throws + WrappedTargetException, + NoDocumentException, + NoSuchElementException, + NotRemoveableException, + PropertyExistException, + IllegalTypeException { + + backend.removeCitationGroup(cg, doc); + this.citationGroups.afterRemoveCitationGroup(cg); + } + + public void removeCitationGroups(List cgs, XTextDocument doc) + throws + WrappedTargetException, + NoDocumentException, + NoSuchElementException, + NotRemoveableException, + PropertyExistException, + IllegalTypeException { + + for (CitationGroup cg : cgs) { + removeCitationGroup(cg, doc); + } + } + + /** + * ranges controlled by citation groups should not overlap with each other. + * + * @return Optional.empty() if the reference mark is missing. + * + */ + public Optional getMarkRange(XTextDocument doc, CitationGroup cg) + throws + NoDocumentException, + WrappedTargetException { + return backend.getMarkRange(cg, doc); + } + + public XTextCursor getFillCursorForCitationGroup(XTextDocument doc, CitationGroup cg) + throws + NoDocumentException, + WrappedTargetException, + CreationException { + return backend.getFillCursorForCitationGroup(cg, doc); + } + + /** + * Remove brackets added by getFillCursorForCitationGroup. + */ + public void cleanFillCursorForCitationGroup(XTextDocument doc, CitationGroup cg) + throws + NoDocumentException, + WrappedTargetException, + CreationException { + + backend.cleanFillCursorForCitationGroup(cg, doc); + } + + /** + * @return A RangeForOverlapCheck for each citation group. + * + * result.size() == nRefMarks + */ + public List> citationRanges(XTextDocument doc) + throws + NoDocumentException, + WrappedTargetException { + + List> result = + new ArrayList<>(citationGroups.numberOfCitationGroups()); + + for (CitationGroup cg : citationGroups.getCitationGroupsUnordered()) { + XTextRange range = this.getMarkRange(doc, cg).orElseThrow(RuntimeException::new); + String description = cg.cgid.citationGroupIdAsString(); // cg.cgRangeStorage.nrGetRangeName(); + result.add(new RangeForOverlapCheck<>(range, + cg.cgid, + RangeForOverlapCheck.REFERENCE_MARK_KIND, + description)); + } + return result; + } + + public List> bibliographyRanges(XTextDocument doc) + throws + NoDocumentException, + WrappedTargetException { + + List> result = new ArrayList<>(); + + Optional range = UpdateBibliography.getBibliographyRange(doc); + if (range.isPresent()) { + String description = "bibliography"; + result.add(new RangeForOverlapCheck<>(range.get(), + new CitationGroupId("bibliography"), + RangeForOverlapCheck.BIBLIOGRAPHY_MARK_KIND, + description)); + } + return result; + } + + public List> viewCursorRanges(XTextDocument doc) + throws + NoDocumentException, + WrappedTargetException { + + List> result = new ArrayList<>(); + + Optional range = UnoCursor.getViewCursor(doc).map(e -> e); + if (range.isPresent()) { + String description = "cursor"; + result.add(new RangeForOverlapCheck<>(range.get(), + new CitationGroupId("cursor"), + RangeForOverlapCheck.CURSOR_MARK_KIND, + description)); + } + return result; + } + + /** + * @return A range for each footnote mark where the footnote + * contains at least one citation group. + * + * Purpose: We do not want markers of footnotes containing + * reference marks to overlap with reference + * marks. Overwriting these footnote marks might kill our + * reference marks in the footnote. + * + * Note: Here we directly communicate to the document, not + * through the backend. This is because mapping ranges to + * footnote marks does not depend on how do we mark or + * structure those ranges. + */ + public List> + footnoteMarkRanges(XTextDocument doc, + List> citationRanges) + throws + NoDocumentException, + WrappedTargetException { + + // Avoid inserting the same mark twice. + // Could use RangeSet if we had that. + RangeSet seen = new RangeSet(); + + List> result = new ArrayList<>(); + + for (RangeForOverlapCheck citationRange : citationRanges) { + + Optional footnoteMarkRange = + UnoTextRange.getFootnoteMarkRange(citationRange.range); + + if (footnoteMarkRange.isEmpty()) { + // not in footnote + continue; + } + + boolean seenContains = seen.contains(footnoteMarkRange.get()); + if (!seenContains) { + seen.add(footnoteMarkRange.get()); + result.add(new RangeForOverlapCheck<>(footnoteMarkRange.get(), + citationRange.idWithinKind, + RangeForOverlapCheck.FOOTNOTE_MARK_KIND, + "FootnoteMark for " + citationRange.format())); + } + } + return result; + } + + static String + rangeOverlapsToMessage(List>> overlaps) { + + if (overlaps.size() == 0) { + return "(*no overlaps*)"; + } + + StringBuilder msg = new StringBuilder(); + for (RangeOverlap> overlap : overlaps) { + String listOfRanges = (overlap.valuesForOverlappingRanges.stream() + .map(v -> String.format("'%s'", v.format())) + .collect(Collectors.joining(", "))); + msg.append( + switch (overlap.kind) { + case EQUAL_RANGE -> Localization.lang("Found identical ranges"); + case OVERLAP -> Localization.lang("Found overlapping ranges"); + case TOUCH -> Localization.lang("Found touching ranges"); + }); + msg.append(": "); + msg.append(listOfRanges); + msg.append("\n"); + } + return msg.toString(); + } + + /** + * Check for any overlap between userRanges and protected ranges. + * + * Assume userRanges is small (usually 1 elements for checking the cursor) + * Returns on first problem found. + */ + public OOVoidResult + checkRangeOverlapsWithCursor(XTextDocument doc, + List> userRanges, + boolean requireSeparation) + throws + NoDocumentException, + WrappedTargetException { + + List> citationRanges = citationRanges(doc); + List> ranges = new ArrayList<>(); + + // ranges.addAll(userRanges); + ranges.addAll(bibliographyRanges(doc)); + ranges.addAll(citationRanges); + ranges.addAll(footnoteMarkRanges(doc, citationRanges)); + + List>> overlaps = + RangeOverlapBetween.findFirst( + doc, + userRanges, + ranges, + requireSeparation); + + if (overlaps.size() == 0) { + return OOVoidResult.ok(); + } + return OOVoidResult.error(new JabRefException("Found overlapping or touching ranges", + rangeOverlapsToMessage(overlaps))); + } + + /** + * @param requireSeparation Report range pairs that only share a boundary. + * @param reportAtMost Limit number of overlaps reported (0 for no limit) + * + */ + public OOVoidResult + checkRangeOverlaps(XTextDocument doc, + List> userRanges, + boolean requireSeparation, + int reportAtMost) + throws + NoDocumentException, + WrappedTargetException { + + List> citationRanges = citationRanges(doc); + List> ranges = new ArrayList<>(); + ranges.addAll(userRanges); + ranges.addAll(bibliographyRanges(doc)); + ranges.addAll(citationRanges); + ranges.addAll(footnoteMarkRanges(doc, citationRanges)); + + List>> overlaps = + RangeOverlapWithin.findOverlappingRanges(doc, ranges, requireSeparation, reportAtMost); + + if (overlaps.size() == 0) { + return OOVoidResult.ok(); + } + return OOVoidResult.error(new JabRefException("Found overlapping or touching ranges", + rangeOverlapsToMessage(overlaps))); + } + + /** + * GUI: Get a list of CitationEntry objects corresponding to citations + * in the document. + * + * Called from: ManageCitationsDialogViewModel constructor. + * + * @return A list with entries corresponding to citations in the + * text, in arbitrary order (same order as from + * getJabRefReferenceMarkNames). + * + * Note: visual or alphabetic order could be more + * manageable for the user. We could provide these + * here, but switching between them needs change on + * GUI (adding a toggle or selector). + * + * Note: CitationEntry implements Comparable, where + * compareTo() and equals() are based on refMarkName. + * The order used in the "Manage citations" dialog + * does not seem to use that. + * + * The 1st is labeled "Citation" (show citation in bold, + * and some context around it). + * + * The columns can be sorted by clicking on the column title. + * For the "Citation" column, the sorting is based on the content, + * (the context before the citation), not on the citation itself. + * + * In the "Extra information ..." column some visual indication + * of the editable part could be helpful. + * + * Wish: selecting an entry (or a button in the line) in + * the GUI could move the cursor in the document to + * the entry. + */ + public List getCitationEntries(XTextDocument doc) + throws + UnknownPropertyException, + WrappedTargetException, + NoDocumentException { + return this.backend.getCitationEntries(doc, citationGroups); + } + + public void applyCitationEntries(XTextDocument doc, List citationEntries) + throws + UnknownPropertyException, + NotRemoveableException, + PropertyExistException, + PropertyVetoException, + IllegalTypeException, + IllegalArgumentException, + NoDocumentException, + WrappedTargetException { + this.backend.applyCitationEntries(doc, citationEntries); + } + + public void imposeGlobalOrder(XTextDocument doc, FunctionalTextViewCursor fcursor) + throws + WrappedTargetException, + NoDocumentException, + JabRefException { + + boolean mapFootnotesToFootnoteMarks = true; + List sortedCitationGroups = + getVisuallySortedCitationGroups(doc, mapFootnotesToFootnoteMarks, fcursor); + List sortedCitationGroupIds = + OOListUtil.map(sortedCitationGroups, cg -> cg.cgid); + citationGroups.setGlobalOrder(sortedCitationGroupIds); + } +} diff --git a/src/main/java/org/jabref/logic/openoffice/frontend/RangeForOverlapCheck.java b/src/main/java/org/jabref/logic/openoffice/frontend/RangeForOverlapCheck.java new file mode 100644 index 00000000000..6cbf560ec62 --- /dev/null +++ b/src/main/java/org/jabref/logic/openoffice/frontend/RangeForOverlapCheck.java @@ -0,0 +1,49 @@ +package org.jabref.logic.openoffice.frontend; + +import org.jabref.model.openoffice.rangesort.RangeHolder; + +import com.sun.star.text.XTextRange; + +/** + * Describe a protected range for overlap checking and reporting. + * + * To check that our protected ranges do not overlap, we collect + * these ranges. To check for overlaps between these, we need the + * {@code range} itself. To report the results of overlap + * checking, we need a {@code description} that can be understood + * by the user. + * + * To be able to refer back to more extended data, we might need to + * identify its {@code kind}, and its index in the corresponding + * tables or other identifier within its kind ({@code idWithinKind}) + * + */ +public class RangeForOverlapCheck implements RangeHolder { + + public final static int REFERENCE_MARK_KIND = 0; + public final static int FOOTNOTE_MARK_KIND = 1; + public final static int CURSOR_MARK_KIND = 2; + public final static int BIBLIOGRAPHY_MARK_KIND = 3; + + public final XTextRange range; + + public final int kind; + public final T idWithinKind; + private final String description; + + public RangeForOverlapCheck(XTextRange range, T idWithinKind, int kind, String description) { + this.range = range; + this.kind = kind; + this.idWithinKind = idWithinKind; + this.description = description; + } + + public String format() { + return description; + } + + @Override + public XTextRange getRange() { + return range; + } +} diff --git a/src/main/java/org/jabref/logic/openoffice/frontend/UpdateBibliography.java b/src/main/java/org/jabref/logic/openoffice/frontend/UpdateBibliography.java new file mode 100644 index 00000000000..ef1a4c42dc5 --- /dev/null +++ b/src/main/java/org/jabref/logic/openoffice/frontend/UpdateBibliography.java @@ -0,0 +1,153 @@ +package org.jabref.logic.openoffice.frontend; + +import java.util.Optional; + +import org.jabref.logic.openoffice.style.OOBibStyle; +import org.jabref.logic.openoffice.style.OOFormatBibliography; +import org.jabref.model.openoffice.ootext.OOText; +import org.jabref.model.openoffice.ootext.OOTextIntoOO; +import org.jabref.model.openoffice.style.CitedKeys; +import org.jabref.model.openoffice.uno.CreationException; +import org.jabref.model.openoffice.uno.NoDocumentException; +import org.jabref.model.openoffice.uno.UnoBookmark; +import org.jabref.model.openoffice.uno.UnoTextSection; + +import com.sun.star.beans.PropertyVetoException; +import com.sun.star.beans.UnknownPropertyException; +import com.sun.star.container.NoSuchElementException; +import com.sun.star.lang.WrappedTargetException; +import com.sun.star.text.XTextCursor; +import com.sun.star.text.XTextDocument; +import com.sun.star.text.XTextRange; + +/* + * Update document: citation marks and bibliography + */ +public class UpdateBibliography { + + private static final String BIB_SECTION_NAME = "JR_bib"; + private static final String BIB_SECTION_END_NAME = "JR_bib_end"; + + public static Optional getBibliographyRange(XTextDocument doc) + throws + NoDocumentException, + WrappedTargetException { + Optional sectionRange = UnoTextSection.getAnchor(doc, BIB_SECTION_NAME); + return sectionRange; + } + + /** + * Rebuilds the bibliography. + */ + public static void rebuildBibTextSection(XTextDocument doc, + OOFrontend fr, + CitedKeys bibliography, + OOBibStyle style, + boolean alwaysAddCitedOnPages) + throws + NoSuchElementException, + WrappedTargetException, + IllegalArgumentException, + CreationException, + PropertyVetoException, + UnknownPropertyException, + NoDocumentException { + + clearBibTextSectionContent2(doc); + + populateBibTextSection(doc, + fr, + bibliography, + style, + alwaysAddCitedOnPages); + } + + /** + * Insert a paragraph break and create a text section for the bibliography. + * + * Only called from `clearBibTextSectionContent2` + */ + private static void createBibTextSection2(XTextDocument doc) + throws + CreationException, + IllegalArgumentException { + + // Always creating at the end of the document. + // Alternatively, we could receive a cursor. + XTextCursor textCursor = doc.getText().createTextCursor(); + textCursor.gotoEnd(false); + UnoTextSection.create(doc, BIB_SECTION_NAME, textCursor, false); + } + + /** + * Find and clear the text section BIB_SECTION_NAME to "", + * or create it. + * + * Only called from: `rebuildBibTextSection` + * + */ + private static void clearBibTextSectionContent2(XTextDocument doc) + throws + CreationException, + IllegalArgumentException, + NoDocumentException, + WrappedTargetException { + + // Optional sectionRange = UnoTextSection.getAnchor(doc, BIB_SECTION_NAME); + Optional sectionRange = getBibliographyRange(doc); + if (sectionRange.isEmpty()) { + createBibTextSection2(doc); + return; + } else { + // Clear it + XTextCursor cursor = doc.getText().createTextCursorByRange(sectionRange.get()); + cursor.setString(""); + } + } + + /** + * Only called from: `rebuildBibTextSection` + * + * Assumes the section named BIB_SECTION_NAME exists. + */ + private static void populateBibTextSection(XTextDocument doc, + OOFrontend fr, + CitedKeys bibliography, + OOBibStyle style, + boolean alwaysAddCitedOnPages) + throws + CreationException, + IllegalArgumentException, + NoDocumentException, + NoSuchElementException, + PropertyVetoException, + UnknownPropertyException, + WrappedTargetException { + + XTextRange sectionRange = getBibliographyRange(doc).orElseThrow(RuntimeException::new); + + XTextCursor cursor = doc.getText().createTextCursorByRange(sectionRange); + + // emit the title of the bibliography + OOTextIntoOO.removeDirectFormatting(cursor); + OOText bibliographyText = OOFormatBibliography.formatBibliography(fr.citationGroups, + bibliography, + style, + alwaysAddCitedOnPages); + OOTextIntoOO.write(doc, cursor, bibliographyText); + cursor.collapseToEnd(); + + // remove the inital empty paragraph from the section. + sectionRange = getBibliographyRange(doc).orElseThrow(RuntimeException::new); + XTextCursor initialParagraph = doc.getText().createTextCursorByRange(sectionRange); + initialParagraph.collapseToStart(); + initialParagraph.goRight((short) 1, true); + initialParagraph.setString(""); + + UnoBookmark.remove(doc, BIB_SECTION_END_NAME); + UnoBookmark.create(doc, BIB_SECTION_END_NAME, cursor, true); + + cursor.collapseToEnd(); + } + +} diff --git a/src/main/java/org/jabref/logic/openoffice/frontend/UpdateCitationMarkers.java b/src/main/java/org/jabref/logic/openoffice/frontend/UpdateCitationMarkers.java new file mode 100644 index 00000000000..7d864ab1063 --- /dev/null +++ b/src/main/java/org/jabref/logic/openoffice/frontend/UpdateCitationMarkers.java @@ -0,0 +1,174 @@ +package org.jabref.logic.openoffice.frontend; + +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +import org.jabref.logic.JabRefException; +import org.jabref.logic.openoffice.style.OOBibStyle; +import org.jabref.model.openoffice.ootext.OOText; +import org.jabref.model.openoffice.ootext.OOTextIntoOO; +import org.jabref.model.openoffice.style.CitationGroup; +import org.jabref.model.openoffice.style.CitationGroups; +import org.jabref.model.openoffice.style.CitationType; +import org.jabref.model.openoffice.uno.CreationException; +import org.jabref.model.openoffice.uno.NoDocumentException; + +import com.sun.star.beans.IllegalTypeException; +import com.sun.star.beans.NotRemoveableException; +import com.sun.star.beans.PropertyExistException; +import com.sun.star.beans.PropertyVetoException; +import com.sun.star.beans.UnknownPropertyException; +import com.sun.star.container.NoSuchElementException; +import com.sun.star.lang.WrappedTargetException; +import com.sun.star.text.XTextCursor; +import com.sun.star.text.XTextDocument; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/* + * Update document: citation marks and bibliography + */ +public class UpdateCitationMarkers { + + private static final Logger LOGGER = LoggerFactory.getLogger(UpdateCitationMarkers.class); + + /** + * Visit each reference mark in referenceMarkNames, overwrite its + * text content. + * + * After each fillCitationMarkInCursor call check if we lost the + * BIB_SECTION_NAME bookmark and recreate it if we did. + * + * @param fr + * + * @param style Bibliography style to use. + * + */ + public static void applyNewCitationMarkers(XTextDocument doc, OOFrontend fr, OOBibStyle style) + throws + NoDocumentException, + UnknownPropertyException, + CreationException, + WrappedTargetException, + PropertyVetoException, + NoSuchElementException, + JabRefException { + + CitationGroups citationGroups = fr.citationGroups; + + for (CitationGroup cg : citationGroups.getCitationGroupsUnordered()) { + + boolean withText = (cg.citationType != CitationType.INVISIBLE_CIT); + Optional marker = cg.getCitationMarker(); + + if (!marker.isPresent()) { + String msg = String.format("applyNewCitationMarkers: no marker for %s", + cg.cgid.citationGroupIdAsString()); + LOGGER.warn(msg); + continue; + } + + if (withText && marker.isPresent()) { + + XTextCursor cursor = fr.getFillCursorForCitationGroup(doc, cg); + + fillCitationMarkInCursor(doc, cursor, marker.get(), withText, style); + + fr.cleanFillCursorForCitationGroup(doc, cg); + } + + } + } + + public static void fillCitationMarkInCursor(XTextDocument doc, + XTextCursor cursor, + OOText citationText, + boolean withText, + OOBibStyle style) + throws + UnknownPropertyException, + PropertyVetoException, + WrappedTargetException, + NoSuchElementException, + CreationException, + IllegalArgumentException { + + Objects.requireNonNull(cursor); + Objects.requireNonNull(citationText); + Objects.requireNonNull(style); + + if (withText) { + OOText citationText2 = style.decorateCitationMarker(citationText); + // inject a ZERO_WIDTH_SPACE to hold the initial character format + final String ZERO_WIDTH_SPACE = "\u200b"; + citationText2 = OOText.fromString(ZERO_WIDTH_SPACE + citationText2.asString()); + OOTextIntoOO.write(doc, cursor, citationText2); + } else { + cursor.setString(""); + } + } + + /** + * Inserts a citation group in the document: creates and fills it. + * + * @param citationKeys BibTeX keys of + * @param pageInfos + * @param citationType + * + * @param citationText Text for the citation. A citation mark or + * placeholder if not yet available. + * + * @param position Location to insert at. + * @param style + * @param insertSpaceAfter A space inserted after the reference + * mark makes it easier to separate from the text + * coming after. But is not wanted when we recreate a + * reference mark. + */ + public static void createAndFillCitationGroup(OOFrontend fr, + XTextDocument doc, + List citationKeys, + List> pageInfos, + CitationType citationType, + OOText citationText, + XTextCursor position, + OOBibStyle style, + boolean insertSpaceAfter) + throws + UnknownPropertyException, + NotRemoveableException, + PropertyExistException, + PropertyVetoException, + WrappedTargetException, + PropertyVetoException, + IllegalArgumentException, + CreationException, + NoDocumentException, + IllegalTypeException, + NoSuchElementException { + + Objects.requireNonNull(pageInfos); + if (pageInfos.size() != citationKeys.size()) { + throw new RuntimeException("pageInfos.size != citationKeys.size"); + } + CitationGroup cg = fr.createCitationGroup(doc, + citationKeys, + pageInfos, + citationType, + position, + insertSpaceAfter); + + final boolean withText = citationType.withText(); + + if (withText) { + XTextCursor c2 = fr.getFillCursorForCitationGroup(doc, cg); + + UpdateCitationMarkers.fillCitationMarkInCursor(doc, c2, citationText, withText, style); + + fr.cleanFillCursorForCitationGroup(doc, cg); + } + position.collapseToEnd(); + } + +} diff --git a/src/main/resources/l10n/JabRef_en.properties b/src/main/resources/l10n/JabRef_en.properties index c2d0ea46ee7..dc2fd8df58a 100644 --- a/src/main/resources/l10n/JabRef_en.properties +++ b/src/main/resources/l10n/JabRef_en.properties @@ -1529,6 +1529,10 @@ Custom=Custom Export\ cited=Export cited Unable\ to\ generate\ new\ library=Unable to generate new library +Found\ identical\ ranges=Found identical ranges +Found\ overlapping\ ranges=Found overlapping ranges +Found\ touching\ ranges=Found touching ranges + Note\:\ Use\ the\ placeholder\ %DIR%\ for\ the\ location\ of\ the\ opened\ library\ file.=Note: Use the placeholder %DIR% for the location of the opened library file. Error\ occured\ while\ executing\ the\ command\ \"%0\".=Error occured while executing the command \"%0\". Reformat\ ISSN=Reformat ISSN From 3632d00fa02dd12a86819c96c73c6133c748cec9 Mon Sep 17 00:00:00 2001 From: Antal K Date: Thu, 3 Jun 2021 11:58:32 +0200 Subject: [PATCH 18/51] add actions --- .../logic/openoffice/action/EditInsert.java | 130 ++++++ .../logic/openoffice/action/EditMerge.java | 372 ++++++++++++++++++ .../logic/openoffice/action/EditSeparate.java | 106 +++++ .../logic/openoffice/action/ExportCited.java | 98 +++++ .../openoffice/action/ManageCitations.java | 41 ++ .../logic/openoffice/action/Update.java | 145 +++++++ 6 files changed, 892 insertions(+) create mode 100644 src/main/java/org/jabref/logic/openoffice/action/EditInsert.java create mode 100644 src/main/java/org/jabref/logic/openoffice/action/EditMerge.java create mode 100644 src/main/java/org/jabref/logic/openoffice/action/EditSeparate.java create mode 100644 src/main/java/org/jabref/logic/openoffice/action/ExportCited.java create mode 100644 src/main/java/org/jabref/logic/openoffice/action/ManageCitations.java create mode 100644 src/main/java/org/jabref/logic/openoffice/action/Update.java diff --git a/src/main/java/org/jabref/logic/openoffice/action/EditInsert.java b/src/main/java/org/jabref/logic/openoffice/action/EditInsert.java new file mode 100644 index 00000000000..277b92bec76 --- /dev/null +++ b/src/main/java/org/jabref/logic/openoffice/action/EditInsert.java @@ -0,0 +1,130 @@ +package org.jabref.logic.openoffice.action; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import org.jabref.logic.JabRefException; +import org.jabref.logic.openoffice.frontend.OOFrontend; +import org.jabref.logic.openoffice.frontend.UpdateCitationMarkers; +import org.jabref.logic.openoffice.style.OOBibStyle; +import org.jabref.model.database.BibDatabase; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.openoffice.ootext.OOText; +import org.jabref.model.openoffice.style.Citation; +import org.jabref.model.openoffice.style.CitationMarkerEntry; +import org.jabref.model.openoffice.style.CitationType; +import org.jabref.model.openoffice.style.NonUniqueCitationMarker; +import org.jabref.model.openoffice.style.OODataModel; +import org.jabref.model.openoffice.uno.CreationException; +import org.jabref.model.openoffice.uno.NoDocumentException; +import org.jabref.model.openoffice.uno.UnoScreenRefresh; +import org.jabref.model.openoffice.util.OOListUtil; + +import com.sun.star.beans.IllegalTypeException; +import com.sun.star.beans.NotRemoveableException; +import com.sun.star.beans.PropertyExistException; +import com.sun.star.beans.PropertyVetoException; +import com.sun.star.beans.UnknownPropertyException; +import com.sun.star.container.NoSuchElementException; +import com.sun.star.lang.WrappedTargetException; +import com.sun.star.text.XTextCursor; +import com.sun.star.text.XTextDocument; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class EditInsert { + + private static final Logger LOGGER = LoggerFactory.getLogger(EditInsert.class); + + /** + * In insertEntry we receive BibEntry values from the GUI. + * + * In the document we store citations by their citation key. + * + * If the citation key is missing, the best we can do is to notify + * the user. Or the programmer, that we cannot accept such input. + * + */ + private static String insertEntryGetCitationKey(BibEntry entry) { + Optional key = entry.getCitationKey(); + if (key.isEmpty()) { + throw new RuntimeException("insertEntryGetCitationKey:" + + " cannot cite entries without citation key"); + } + return key.get(); + } + + /* + * @param cursor Where to insert. + * + * @param sync If not empty, update citation markers and, + * depending on the embedded options, the + * bibliography. + * + * @param fcursor If sync.isPresent(), it must provide a + * FunctionalTextViewCursor. Otherwise not used. + */ + public static void insertCitationGroup(XTextDocument doc, + OOFrontend fr, + XTextCursor cursor, + List entries, + BibDatabase database, + OOBibStyle style, + CitationType citationType, + String pageInfo) + throws + UnknownPropertyException, + NoDocumentException, + NotRemoveableException, + WrappedTargetException, + PropertyVetoException, + PropertyExistException, + NoSuchElementException, + CreationException, + IllegalTypeException, + JabRefException { + + List citationKeys = OOListUtil.map(entries, EditInsert::insertEntryGetCitationKey); + + final int nEntries = entries.size(); + List> pageInfos = OODataModel.fakePageInfos(pageInfo, nEntries); + + List citations = new ArrayList<>(nEntries); + for (int i = 0; i < nEntries; i++) { + Citation cit = new Citation(citationKeys.get(i)); + cit.lookupInDatabases(Collections.singletonList(database)); + cit.setPageInfo(pageInfos.get(i)); + citations.add(cit); + } + + // The text we insert + OOText citeText = + (style.isNumberEntries() + ? OOText.fromString("[-]") // A dash only. Only refresh later. + : style.createCitationMarker(citations, + citationType.inParenthesis(), + NonUniqueCitationMarker.FORGIVEN)); + + if ("".equals(OOText.toString(citeText))) { + citeText = OOText.fromString("[?]"); + } + + try { + UnoScreenRefresh.lockControllers(doc); + UpdateCitationMarkers.createAndFillCitationGroup(fr, + doc, + citationKeys, + pageInfos, + citationType, + citeText, + cursor, + style, + true /* insertSpaceAfter */); + } finally { + UnoScreenRefresh.unlockControllers(doc); + } + + } +} diff --git a/src/main/java/org/jabref/logic/openoffice/action/EditMerge.java b/src/main/java/org/jabref/logic/openoffice/action/EditMerge.java new file mode 100644 index 00000000000..823610c51cf --- /dev/null +++ b/src/main/java/org/jabref/logic/openoffice/action/EditMerge.java @@ -0,0 +1,372 @@ +package org.jabref.logic.openoffice.action; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; + +import org.jabref.logic.JabRefException; +import org.jabref.logic.openoffice.frontend.OOFrontend; +import org.jabref.logic.openoffice.frontend.UpdateCitationMarkers; +import org.jabref.logic.openoffice.style.OOBibStyle; +import org.jabref.model.openoffice.ootext.OOText; +import org.jabref.model.openoffice.style.Citation; +import org.jabref.model.openoffice.style.CitationGroup; +import org.jabref.model.openoffice.style.CitationType; +import org.jabref.model.openoffice.uno.CreationException; +import org.jabref.model.openoffice.uno.NoDocumentException; +import org.jabref.model.openoffice.uno.UnoScreenRefresh; +import org.jabref.model.openoffice.uno.UnoTextRange; +import org.jabref.model.openoffice.util.OOListUtil; + +import com.sun.star.beans.IllegalTypeException; +import com.sun.star.beans.NotRemoveableException; +import com.sun.star.beans.PropertyExistException; +import com.sun.star.beans.PropertyVetoException; +import com.sun.star.beans.UnknownPropertyException; +import com.sun.star.container.NoSuchElementException; +import com.sun.star.lang.WrappedTargetException; +import com.sun.star.text.XTextCursor; +import com.sun.star.text.XTextDocument; +import com.sun.star.text.XTextRange; +import com.sun.star.util.InvalidStateException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class EditMerge { + + private static final Logger LOGGER = LoggerFactory.getLogger(EditMerge.class); + + private EditMerge() { + // hide constructor + } + + /* + * @return true if modified document + */ + public static boolean mergeCitationGroups(XTextDocument doc, OOFrontend fr, OOBibStyle style) + throws + CreationException, + IllegalArgumentException, + IllegalTypeException, + InvalidStateException, + JabRefException, + NoDocumentException, + NoSuchElementException, + NotRemoveableException, + PropertyExistException, + PropertyVetoException, + UnknownPropertyException, + WrappedTargetException { + + boolean madeModifications = false; + + try { + UnoScreenRefresh.lockControllers(doc); + + List joinableGroups = EditMerge.scan(doc, fr); + + for (JoinableGroupData joinableGroupData : joinableGroups) { + + List cgs = joinableGroupData.group; + + List newCitations = (cgs.stream() + .flatMap(cg -> cg.citationsInStorageOrder.stream()) + .collect(Collectors.toList())); + + CitationType citationType = cgs.get(0).citationType; + List> pageInfos = fr.backend.combinePageInfos(cgs); + + fr.removeCitationGroups(cgs, doc); + XTextCursor textCursor = joinableGroupData.groupCursor; + textCursor.setString(""); // Also remove the spaces between. + + List citationKeys = OOListUtil.map(newCitations, Citation::getCitationKey); + + /* insertSpaceAfter: no, it is already there (or could be) */ + boolean insertSpaceAfter = false; + UpdateCitationMarkers.createAndFillCitationGroup(fr, + doc, + citationKeys, + pageInfos, + citationType, + OOText.fromString("tmp"), + textCursor, + style, + insertSpaceAfter); + } + + madeModifications = !joinableGroups.isEmpty(); + + } finally { + UnoScreenRefresh.unlockControllers(doc); + } + + return madeModifications; + } + + private static class JoinableGroupData { + /* + * A list of consecutive citation groups only separated by spaces. + */ + List group; + /* + * A cursor covering the XTextRange of each entry in group + * (and the spaces between them) + */ + XTextCursor groupCursor; + + JoinableGroupData(List group, XTextCursor groupCursor) { + this.group = group; + this.groupCursor = groupCursor; + } + } + + private static class ScanState { + + // Citation groups in the current group + List currentGroup; + + // A cursor that covers the Citation groups in currentGroup, + // including the space between them. + // Null if currentGroup.isEmpty() + XTextCursor currentGroupCursor; + + // A cursor starting at the end of the last CitationGroup in + // currentGroup. Null if currentGroup.isEmpty() + XTextCursor cursorBetween; + + // The last element of currentGroup. + // Null if currentGroup.isEmpty() + CitationGroup prev; + + // The XTextRange for prev. + // Null if currentGroup.isEmpty() + XTextRange prevRange; + + ScanState() { + reset(); + } + + void reset() { + currentGroup = new ArrayList<>(); + currentGroupCursor = null; + cursorBetween = null; + prev = null; + prevRange = null; + } + } + + /** + * Decide if cg could be added to state.currentGroup + * + * @param cg The CitationGroup to test + * @param currentRange The XTextRange corresponding to cg. + * + * @return false if cannot add, true if can. If returned true, + * then state.cursorBetween and state.currentGroupCursor are + * expanded to end at the start of currentRange. + */ + private static boolean checkAddToGroup(ScanState state, CitationGroup cg, XTextRange currentRange) { + + if (state.currentGroup.isEmpty()) { + return false; + } + + Objects.requireNonNull(state.currentGroupCursor); + Objects.requireNonNull(state.cursorBetween); + Objects.requireNonNull(state.prev); + Objects.requireNonNull(state.prevRange); + + // Only combine (Author 2000) type citations + if (cg.citationType != CitationType.AUTHORYEAR_PAR) { + return false; + } + + if (state.prev != null) { + + // Even if we combine AUTHORYEAR_INTEXT citations, we + // would not mix them with AUTHORYEAR_PAR + if (cg.citationType != state.prev.citationType) { + return false; + } + + if (!UnoTextRange.comparables(state.prevRange, currentRange)) { + return false; + } + + // Sanity check: the current range should start later than + // the previous. + int textOrder = UnoTextRange.compareStarts(state.prevRange, currentRange); + if (textOrder != (-1)) { + String msg = + String.format("MergeCitationGroups:" + + " \"%s\" supposed to be followed by \"%s\"," + + " but %s", + state.prevRange.getString(), + currentRange.getString(), + ((textOrder == 0) + ? "they start at the same position" + : ("the start of the latter precedes" + + " the start of the first"))); + LOGGER.warn(msg); + return false; + } + } + + if (state.cursorBetween == null) { + return false; + } + + Objects.requireNonNull(state.cursorBetween); + Objects.requireNonNull(state.currentGroupCursor); + + // assume: currentGroupCursor.getEnd() == cursorBetween.getEnd() + if (UnoTextRange.compareEnds(state.cursorBetween, state.currentGroupCursor) != 0) { + String msg = ("MergeCitationGroups:" + + " cursorBetween.end != currentGroupCursor.end"); + throw new RuntimeException(msg); + } + + /* + * Try to expand state.currentGroupCursor and state.cursorBetween by going right + * to reach rangeStart. + */ + XTextRange rangeStart = currentRange.getStart(); + boolean couldExpand = true; + XTextCursor thisCharCursor = + (currentRange.getText().createTextCursorByRange(state.cursorBetween.getEnd())); + + while (couldExpand && (UnoTextRange.compareEnds(state.cursorBetween, rangeStart) < 0)) { + // + // Check that we only walk through inline whitespace. + // + couldExpand = thisCharCursor.goRight((short) 1, true); + String thisChar = thisCharCursor.getString(); + thisCharCursor.collapseToEnd(); + if (thisChar.isEmpty() || thisChar.equals("\n") || !thisChar.trim().isEmpty()) { + couldExpand = false; + if (!thisChar.isEmpty()) { + thisCharCursor.goLeft((short) 1, false); + } + break; + } + state.cursorBetween.goRight((short) 1, true); + state.currentGroupCursor.goRight((short) 1, true); + + // These two should move in sync: + if (UnoTextRange.compareEnds(state.cursorBetween, state.currentGroupCursor) != 0) { + String msg = ("MergeCitationGroups:" + + " cursorBetween.end != currentGroupCursor.end" + + " (during expand)"); + throw new RuntimeException(msg); + } + } // while + + if (!couldExpand) { + return false; + } + + return true; + } + + /** + * Add cg to state.currentGroup + * Set state.cursorBetween to start at currentRange.getEnd() + * Expand state.currentGroupCursor to also cover currentRange + * Set state.prev to cg, state.prevRange to currentRange + */ + private static void addToCurrentGroup(ScanState state, CitationGroup cg, XTextRange currentRange) { + final boolean isNewGroup = state.currentGroup.isEmpty(); + if (!isNewGroup) { + Objects.requireNonNull(state.currentGroupCursor); + Objects.requireNonNull(state.cursorBetween); + Objects.requireNonNull(state.prev); + Objects.requireNonNull(state.prevRange); + } + + // Add the current entry to a group. + state.currentGroup.add(cg); + + // Set up cursorBetween to start at currentRange.getEnd() + XTextRange rangeEnd = currentRange.getEnd(); + state.cursorBetween = currentRange.getText().createTextCursorByRange(rangeEnd); + + // If new group, create currentGroupCursor + if (isNewGroup) { + state.currentGroupCursor = (currentRange.getText() + .createTextCursorByRange(currentRange.getStart())); + } + + // include currentRange in currentGroupCursor + state.currentGroupCursor.goRight((short) (currentRange.getString().length()), true); + + if (UnoTextRange.compareEnds(state.cursorBetween, state.currentGroupCursor) != 0) { + String msg = ("MergeCitationGroups: cursorBetween.end != currentGroupCursor.end"); + throw new RuntimeException(msg); + } + + /* Store data about last entry in currentGroup */ + state.prev = cg; + state.prevRange = currentRange; + } + + /** + * Scan the document for joinable groups. Return those found. + */ + private static List scan(XTextDocument doc, OOFrontend fr) + throws + NoDocumentException, + WrappedTargetException { + List result = new ArrayList<>(); + + List cgs = + fr.getCitationGroupsSortedWithinPartitions(doc, + false /* mapFootnotesToFootnoteMarks */); + if (cgs.isEmpty()) { + return result; + } + + ScanState state = new ScanState(); + + for (CitationGroup cg : cgs) { + + XTextRange currentRange = (fr.getMarkRange(doc, cg) + .orElseThrow(RuntimeException::new)); + + /* + * Decide if we add cg to the group. False when the group is empty. + */ + boolean addToGroup = checkAddToGroup(state, cg, currentRange); + + /* + * Even if we do not add it to an existing group, + * we might use it to start a new group. + * + * Can it start a new group? + */ + boolean canStartGroup = (cg.citationType == CitationType.AUTHORYEAR_PAR); + + if (!addToGroup) { + // close currentGroup + if (state.currentGroup.size() > 1) { + result.add(new JoinableGroupData(state.currentGroup, state.currentGroupCursor)); + } + // Start a new, empty group + state.reset(); + } + + if (addToGroup || canStartGroup) { + addToCurrentGroup(state, cg, currentRange); + } + } + + // close currentGroup + if (state.currentGroup.size() > 1) { + result.add(new JoinableGroupData(state.currentGroup, state.currentGroupCursor)); + } + return result; + } + +} diff --git a/src/main/java/org/jabref/logic/openoffice/action/EditSeparate.java b/src/main/java/org/jabref/logic/openoffice/action/EditSeparate.java new file mode 100644 index 00000000000..31c23ed52d0 --- /dev/null +++ b/src/main/java/org/jabref/logic/openoffice/action/EditSeparate.java @@ -0,0 +1,106 @@ +package org.jabref.logic.openoffice.action; + +import java.util.List; + +import org.jabref.logic.JabRefException; +import org.jabref.logic.openoffice.frontend.OOFrontend; +import org.jabref.logic.openoffice.frontend.UpdateCitationMarkers; +import org.jabref.logic.openoffice.style.OOBibStyle; +import org.jabref.logic.openoffice.style.OOProcess; +import org.jabref.model.database.BibDatabase; +import org.jabref.model.openoffice.ootext.OOText; +import org.jabref.model.openoffice.style.Citation; +import org.jabref.model.openoffice.style.CitationGroup; +import org.jabref.model.openoffice.uno.CreationException; +import org.jabref.model.openoffice.uno.NoDocumentException; +import org.jabref.model.openoffice.uno.UnoScreenRefresh; + +import com.sun.star.beans.IllegalTypeException; +import com.sun.star.beans.NotRemoveableException; +import com.sun.star.beans.PropertyExistException; +import com.sun.star.beans.PropertyVetoException; +import com.sun.star.beans.UnknownPropertyException; +import com.sun.star.container.NoSuchElementException; +import com.sun.star.lang.WrappedTargetException; +import com.sun.star.text.XTextCursor; +import com.sun.star.text.XTextDocument; +import com.sun.star.text.XTextRange; +import com.sun.star.util.InvalidStateException; + +public class EditSeparate { + + public static boolean separateCitations(XTextDocument doc, + OOFrontend fr, + List databases, + OOBibStyle style) + throws + CreationException, + IllegalTypeException, + InvalidStateException, + JabRefException, + NoDocumentException, + NoSuchElementException, + NotRemoveableException, + PropertyExistException, + PropertyVetoException, + UnknownPropertyException, + WrappedTargetException, + com.sun.star.lang.IllegalArgumentException { + + boolean madeModifications = false; + + // To reduce surprises in JabRef52 mode, impose localOrder to + // decide the visually last Citation in the group. Unless the + // style changed since refresh this is the last on the screen + // as well. + fr.citationGroups.lookupCitations(databases); + fr.citationGroups.imposeLocalOrder(OOProcess.comparatorForMulticite(style)); + + List cgs = fr.citationGroups.getCitationGroupsUnordered(); + + try { + UnoScreenRefresh.lockControllers(doc); + + for (CitationGroup cg : cgs) { + + XTextRange range1 = (fr + .getMarkRange(doc, cg) + .orElseThrow(RuntimeException::new)); + XTextCursor textCursor = range1.getText().createTextCursorByRange(range1); + + List cits = cg.citationsInStorageOrder; + if (cits.size() <= 1) { + continue; + } + + fr.removeCitationGroup(cg, doc); + // Now we own the content of cits + + // Create a citation group for each citation. + final int last = cits.size() - 1; + for (int i = 0; i < cits.size(); i++) { + boolean insertSpaceAfter = (i != last); + Citation cit = cits.get(i); + + UpdateCitationMarkers.createAndFillCitationGroup( + fr, + doc, + List.of(cit.citationKey), + List.of(cit.getPageInfo()), + cg.citationType, + OOText.fromString(cit.citationKey), + textCursor, + style, + insertSpaceAfter); + + textCursor.collapseToEnd(); + } + + madeModifications = true; + } + } finally { + UnoScreenRefresh.unlockControllers(doc); + } + return madeModifications; + } +} diff --git a/src/main/java/org/jabref/logic/openoffice/action/ExportCited.java b/src/main/java/org/jabref/logic/openoffice/action/ExportCited.java new file mode 100644 index 00000000000..8d344688c68 --- /dev/null +++ b/src/main/java/org/jabref/logic/openoffice/action/ExportCited.java @@ -0,0 +1,98 @@ +package org.jabref.logic.openoffice.action; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.jabref.logic.openoffice.frontend.OOFrontend; +import org.jabref.model.database.BibDatabase; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.field.StandardField; +import org.jabref.model.openoffice.style.CitedKey; +import org.jabref.model.openoffice.style.CitedKeys; +import org.jabref.model.openoffice.uno.NoDocumentException; + +import com.sun.star.beans.UnknownPropertyException; +import com.sun.star.container.NoSuchElementException; +import com.sun.star.lang.WrappedTargetException; +import com.sun.star.text.XTextDocument; + +public class ExportCited { + + public static class GenerateDatabaseResult { + /** + * null: not done; isEmpty: no unresolved + */ + public final List unresolvedKeys; + public final BibDatabase newDatabase; + + GenerateDatabaseResult(List unresolvedKeys, BibDatabase newDatabase) { + this.unresolvedKeys = unresolvedKeys; + this.newDatabase = newDatabase; + } + } + + /** + * + * @param databases The databases to look up the citation keys in the document from. + * @return A new database, with cloned entries. + * + * If a key is not found, it is added to result.unresolvedKeys + * + * Cross references (in StandardField.CROSSREF) are followed (not recursively): + * if the referenced entry is found, it is included in the result. + * If it is not found, it is silently ignored. + */ + public static GenerateDatabaseResult generateDatabase(XTextDocument doc, List databases) + throws + NoDocumentException, + NoSuchElementException, + UnknownPropertyException, + WrappedTargetException { + + OOFrontend fr = new OOFrontend(doc); + CitedKeys cks = fr.citationGroups.getCitedKeysUnordered(); + cks.lookupInDatabases(databases); + + List unresolvedKeys = new ArrayList<>(); + BibDatabase resultDatabase = new BibDatabase(); + + List entriesToInsert = new ArrayList<>(); + Set seen = new HashSet<>(); // Only add crossReference once. + + for (CitedKey ck : cks.values()) { + if (ck.getLookupResult().isEmpty()) { + unresolvedKeys.add(ck.citationKey); + continue; + } else { + BibEntry entry = ck.getLookupResult().get().entry; + BibDatabase loopDatabase = ck.getLookupResult().get().database; + + // If entry found + BibEntry clonedEntry = (BibEntry) entry.clone(); + + // Insert a copy of the entry + entriesToInsert.add(clonedEntry); + + // Check if the cloned entry has a cross-reference field + clonedEntry + .getField(StandardField.CROSSREF) + .ifPresent(crossReference -> { + boolean isNew = !seen.contains(crossReference); + if (isNew) { + // Add it if it is in the current library + loopDatabase + .getEntryByCitationKey(crossReference) + .ifPresent(entriesToInsert::add); + seen.add(crossReference); + } + }); + } + } + + resultDatabase.insertEntries(entriesToInsert); + return new GenerateDatabaseResult(unresolvedKeys, resultDatabase); + } + +} diff --git a/src/main/java/org/jabref/logic/openoffice/action/ManageCitations.java b/src/main/java/org/jabref/logic/openoffice/action/ManageCitations.java new file mode 100644 index 00000000000..4046cbbe11b --- /dev/null +++ b/src/main/java/org/jabref/logic/openoffice/action/ManageCitations.java @@ -0,0 +1,41 @@ +package org.jabref.logic.openoffice.action; + +import java.util.List; + +import org.jabref.logic.openoffice.frontend.OOFrontend; +import org.jabref.model.openoffice.CitationEntry; +import org.jabref.model.openoffice.uno.NoDocumentException; + +import com.sun.star.beans.IllegalTypeException; +import com.sun.star.beans.NotRemoveableException; +import com.sun.star.beans.PropertyExistException; +import com.sun.star.beans.PropertyVetoException; +import com.sun.star.beans.UnknownPropertyException; +import com.sun.star.lang.WrappedTargetException; +import com.sun.star.text.XTextDocument; + +public class ManageCitations { + + public static List getCitationEntries(XTextDocument doc) + throws + NoDocumentException, + UnknownPropertyException, + WrappedTargetException { + OOFrontend fr = new OOFrontend(doc); + return fr.getCitationEntries(doc); + } + + public static void applyCitationEntries(XTextDocument doc, List citationEntries) + throws + NoDocumentException, + UnknownPropertyException, + NotRemoveableException, + PropertyExistException, + PropertyVetoException, + IllegalTypeException, + WrappedTargetException, + IllegalArgumentException { + OOFrontend fr = new OOFrontend(doc); + fr.applyCitationEntries(doc, citationEntries); + } +} diff --git a/src/main/java/org/jabref/logic/openoffice/action/Update.java b/src/main/java/org/jabref/logic/openoffice/action/Update.java new file mode 100644 index 00000000000..151e4b6575b --- /dev/null +++ b/src/main/java/org/jabref/logic/openoffice/action/Update.java @@ -0,0 +1,145 @@ +package org.jabref.logic.openoffice.action; + +import java.util.List; + +import org.jabref.logic.JabRefException; +import org.jabref.logic.openoffice.frontend.OOFrontend; +import org.jabref.logic.openoffice.frontend.UpdateBibliography; +import org.jabref.logic.openoffice.frontend.UpdateCitationMarkers; +import org.jabref.logic.openoffice.style.OOBibStyle; +import org.jabref.logic.openoffice.style.OOProcess; +import org.jabref.model.database.BibDatabase; +import org.jabref.model.openoffice.rangesort.FunctionalTextViewCursor; +import org.jabref.model.openoffice.uno.CreationException; +import org.jabref.model.openoffice.uno.NoDocumentException; +import org.jabref.model.openoffice.uno.UnoScreenRefresh; + +import com.sun.star.beans.PropertyVetoException; +import com.sun.star.beans.UnknownPropertyException; +import com.sun.star.container.NoSuchElementException; +import com.sun.star.lang.WrappedTargetException; +import com.sun.star.text.XTextDocument; + +/* + * Update document: citation marks and bibliography + */ +public class Update { + + /* + * @return unresolvedKeys + */ + private static List updateDocument(XTextDocument doc, + OOFrontend fr, + List databases, + OOBibStyle style, + FunctionalTextViewCursor fcursor, + boolean doUpdateBibliography, + boolean alwaysAddCitedOnPages) + throws + CreationException, + JabRefException, + NoDocumentException, + NoSuchElementException, + PropertyVetoException, + UnknownPropertyException, + WrappedTargetException, + com.sun.star.lang.IllegalArgumentException { + + final boolean useLockControllers = true; + + fr.imposeGlobalOrder(doc, fcursor); + OOProcess.produceCitationMarkers(fr.citationGroups, databases, style); + + try { + if (useLockControllers) { + UnoScreenRefresh.lockControllers(doc); + } + + UpdateCitationMarkers.applyNewCitationMarkers(doc, fr, style); + + if (doUpdateBibliography) { + UpdateBibliography.rebuildBibTextSection(doc, + fr, + fr.citationGroups.getBibliography().get(), + style, + alwaysAddCitedOnPages); + } + List result = fr.citationGroups.getUnresolvedKeys(); + return result; + } finally { + if (useLockControllers && UnoScreenRefresh.hasControllersLocked(doc)) { + UnoScreenRefresh.unlockControllers(doc); + } + } + } + + public static class SyncOptions { + + public final List databases; + boolean updateBibliography; + boolean alwaysAddCitedOnPages; + + public SyncOptions(List databases) { + this.databases = databases; + this.updateBibliography = false; + this.alwaysAddCitedOnPages = false; + } + + public SyncOptions setUpdateBibliography(boolean value) { + this.updateBibliography = value; + return this; + } + + public SyncOptions setAlwaysAddCitedOnPages(boolean value) { + this.alwaysAddCitedOnPages = value; + return this; + } + } + + public static List synchronizeDocument(XTextDocument doc, + OOFrontend fr, + OOBibStyle style, + FunctionalTextViewCursor fcursor, + SyncOptions syncOptions) + throws + CreationException, + JabRefException, + NoDocumentException, + NoSuchElementException, + PropertyVetoException, + UnknownPropertyException, + WrappedTargetException, + com.sun.star.lang.IllegalArgumentException { + + return Update.updateDocument(doc, + fr, + syncOptions.databases, + style, + fcursor, + syncOptions.updateBibliography, + syncOptions.alwaysAddCitedOnPages); + } + + /* + * Reread document before sync + */ + public static List resyncDocument(XTextDocument doc, + OOBibStyle style, + FunctionalTextViewCursor fcursor, + SyncOptions syncOptions) + throws + CreationException, + JabRefException, + NoDocumentException, + NoSuchElementException, + PropertyVetoException, + UnknownPropertyException, + WrappedTargetException, + com.sun.star.lang.IllegalArgumentException { + + OOFrontend fr = new OOFrontend(doc); + + return Update.synchronizeDocument(doc, fr, style, fcursor, syncOptions); + } + +} From 88cab9cfad57b084b332f2d1faef4bedac825125 Mon Sep 17 00:00:00 2001 From: Antal K Date: Thu, 3 Jun 2021 15:33:58 +0200 Subject: [PATCH 19/51] checkstyle on tests --- .../openoffice/style/OOBibStyleTest.java | 51 ++++++++----------- .../style/OOBibStyleTestHelper.java | 41 ++++----------- 2 files changed, 30 insertions(+), 62 deletions(-) diff --git a/src/test/java/org/jabref/logic/openoffice/style/OOBibStyleTest.java b/src/test/java/org/jabref/logic/openoffice/style/OOBibStyleTest.java index bf0bdf945a4..03a0b535e34 100644 --- a/src/test/java/org/jabref/logic/openoffice/style/OOBibStyleTest.java +++ b/src/test/java/org/jabref/logic/openoffice/style/OOBibStyleTest.java @@ -11,7 +11,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; @@ -25,13 +24,10 @@ import org.jabref.model.entry.types.StandardEntryType; import org.jabref.model.entry.types.UnknownEntryType; import org.jabref.model.openoffice.ootext.OOText; -import org.jabref.model.openoffice.style.Citation; -import org.jabref.model.openoffice.style.CitationLookupResult; import org.jabref.model.openoffice.style.CitationMarkerEntry; import org.jabref.model.openoffice.style.CitationMarkerNumericBibEntry; import org.jabref.model.openoffice.style.CitationMarkerNumericEntry; import org.jabref.model.openoffice.style.NonUniqueCitationMarker; -import org.jabref.model.openoffice.style.PageInfo; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -96,7 +92,7 @@ void testNumerical() throws IOException { * begin helpers */ static String runGetNumCitationMarker2a(OOBibStyle style, - List num, int minGroupingCount, boolean inList ) { + List num, int minGroupingCount, boolean inList) { return OOBibStyleTestHelper.runGetNumCitationMarker2a(style, num, minGroupingCount, inList); } @@ -125,7 +121,7 @@ static CitationMarkerEntry makeCitationMarkerEntry(BibEntry entry, database, uniqueLetterQ, pageInfoQ, - isFirstAppearanceOfSource ); + isFirstAppearanceOfSource); } /* @@ -145,14 +141,13 @@ static String getCitationMarker2(OOBibStyle style, inParenthesis, uniquefiers, isFirstAppearanceOfSource, - pageInfo ); + pageInfo); } /* * end helpers */ - @Test void testGetNumCitationMarker() throws IOException { OOBibStyle style = new OOBibStyle(StyleLoader.DEFAULT_NUMERICAL_STYLE_PATH, @@ -218,27 +213,27 @@ void testGetNumCitationMarkerUndefined() throws IOException { // unresolved citations look like [??key] assertEquals("[" + OOBibStyle.UNDEFINED_CITATION_MARKER + "key" + "]", runGetNumCitationMarker2b(style, 1, - numEntry("key",0,null))); + numEntry("key", 0, null))); // pageInfo is shown for unresolved citations assertEquals("[" + OOBibStyle.UNDEFINED_CITATION_MARKER + "key" + "; p1]", runGetNumCitationMarker2b(style, 1, - numEntry("key",0,"p1"))); + numEntry("key", 0, "p1"))); // unresolved citations sorted to the front assertEquals("[" + OOBibStyle.UNDEFINED_CITATION_MARKER + "key" + "; 2-4]", runGetNumCitationMarker2b(style, 1, - numEntry("x4",4,""), - numEntry("x2",2,""), - numEntry("x3",3,""), - numEntry("key",0,""))); + numEntry("x4", 4, ""), + numEntry("x2", 2, ""), + numEntry("x3", 3, ""), + numEntry("key", 0, ""))); assertEquals("[" + OOBibStyle.UNDEFINED_CITATION_MARKER + "key" + "; 1-3]", runGetNumCitationMarker2b(style, 1, - numEntry("x1",1,""), - numEntry("x2",2,""), - numEntry("y3",3,""), - numEntry("key",0,""))); + numEntry("x1", 1, ""), + numEntry("x2", 2, ""), + numEntry("y3", 3, ""), + numEntry("key", 0, ""))); // multiple unresolved citations are not collapsed assertEquals("[" @@ -246,19 +241,16 @@ void testGetNumCitationMarkerUndefined() throws IOException { + OOBibStyle.UNDEFINED_CITATION_MARKER + "x2" + "; " + OOBibStyle.UNDEFINED_CITATION_MARKER + "x3" + "]", runGetNumCitationMarker2b(style, 1, - numEntry("x1",0,""), - numEntry("x2",0,""), - numEntry("x3",0,""))); + numEntry("x1", 0, ""), + numEntry("x2", 0, ""), + numEntry("x3", 0, ""))); /* * BIBLIOGRAPHY */ - { - CitationMarkerNumericBibEntry x = numBibEntry("key", Optional.empty()); - assertEquals("[" + OOBibStyle.UNDEFINED_CITATION_MARKER + "key" + "] ", - style.getNumCitationMarkerForBibliography(x).asString()); - } - + CitationMarkerNumericBibEntry x = numBibEntry("key", Optional.empty()); + assertEquals("[" + OOBibStyle.UNDEFINED_CITATION_MARKER + "key" + "] ", + style.getNumCitationMarkerForBibliography(x).asString()); } @Test @@ -280,7 +272,6 @@ void testGetCitProperty() throws IOException { assertTrue(journals.contains("Journal name 1")); } - @Test void testGetCitationMarker() throws IOException { OOBibStyle style = new OOBibStyle(StyleLoader.DEFAULT_NUMERICAL_STYLE_PATH, layoutFormatterPreferences); @@ -767,7 +758,7 @@ void testGetCitationMarkerJoinFirst() throws IOException { citationMarkerEntries.add(cm3); assertEquals("[Boström, Wäyrynen, Bodén, Beznosov & Kruchten, 2006a,b" - +"; Boström, Wäyrynen, Bodén, Beznosov & NotKruchten, 2006c]", + + "; Boström, Wäyrynen, Bodén, Beznosov & NotKruchten, 2006c]", style.createCitationMarker(citationMarkerEntries, true, NonUniqueCitationMarker.THROWS).asString()); @@ -795,7 +786,7 @@ void testGetCitationMarkerJoinFirst() throws IOException { citationMarkerEntries.add(cm3); assertEquals("[Boström, Wäyrynen, Bodén, Beznosov & Kruchten, 2006a,b" - +"; Boström et al., 2006c]", + + "; Boström et al., 2006c]", style.createCitationMarker(citationMarkerEntries, true, NonUniqueCitationMarker.THROWS).asString()); diff --git a/src/test/java/org/jabref/logic/openoffice/style/OOBibStyleTestHelper.java b/src/test/java/org/jabref/logic/openoffice/style/OOBibStyleTestHelper.java index c8302030fc6..9fbe9579ccb 100644 --- a/src/test/java/org/jabref/logic/openoffice/style/OOBibStyleTestHelper.java +++ b/src/test/java/org/jabref/logic/openoffice/style/OOBibStyleTestHelper.java @@ -1,29 +1,16 @@ package org.jabref.logic.openoffice.style; -import java.io.File; import java.io.IOException; -import java.net.URISyntaxException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Optional; -import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; -import org.jabref.logic.layout.Layout; -import org.jabref.logic.layout.LayoutFormatterPreferences; import org.jabref.model.database.BibDatabase; import org.jabref.model.entry.BibEntry; -import org.jabref.model.entry.field.StandardField; -import org.jabref.model.entry.types.StandardEntryType; -import org.jabref.model.entry.types.UnknownEntryType; import org.jabref.model.openoffice.ootext.OOText; import org.jabref.model.openoffice.style.Citation; import org.jabref.model.openoffice.style.CitationLookupResult; @@ -33,15 +20,7 @@ import org.jabref.model.openoffice.style.NonUniqueCitationMarker; import org.jabref.model.openoffice.style.PageInfo; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.Answers; - import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.Mockito.mock; class OOBibStyleTestHelper { /* @@ -116,7 +95,7 @@ static CitationMarkerNumericBibEntry numBibEntry(String key, Optional n * @param inList true means label for the bibliography */ static String runGetNumCitationMarker2a(OOBibStyle style, - List num, int minGroupingCount, boolean inList ) { + List num, int minGroupingCount, boolean inList) { if (inList) { if (num.size() != 1) { throw new RuntimeException("Numeric label for the bibliography with " @@ -159,7 +138,6 @@ static String runGetNumCitationMarker2b(OOBibStyle style, * end Helpers for testing style.getNumCitationMarker2 */ - /* * begin helper */ @@ -223,21 +201,20 @@ static String getCitationMarker2(OOBibStyle style, * end helper */ - static void testGetNumCitationMarkerExtra(OOBibStyle style) throws IOException { // Identical numeric entries are joined. assertEquals("[1; 2]", runGetNumCitationMarker2b(style, 3, - numEntry("x1",1,null), - numEntry("x2",2,null), - numEntry("x1",2,null), - numEntry("x2",1,null))); + numEntry("x1", 1, null), + numEntry("x2", 2, null), + numEntry("x1", 2, null), + numEntry("x2", 1, null))); // ... unless minGroupingCount <= 0 assertEquals("[1; 1; 2; 2]", runGetNumCitationMarker2b(style, 0, - numEntry("x1",1,null), - numEntry("x2",2,null), - numEntry("x1",2,null), - numEntry("x2",1,null))); + numEntry("x1", 1, null), + numEntry("x2", 2, null), + numEntry("x1", 2, null), + numEntry("x2", 1, null))); // ... or have different pageInfos assertEquals("[1; p1a; 1; p1b; 2; p2; 3]", runGetNumCitationMarker2b(style, 1, From b7aaf2af9be835fac592645787a091338dad3c34 Mon Sep 17 00:00:00 2001 From: Antal K Date: Thu, 3 Jun 2021 15:39:04 +0200 Subject: [PATCH 20/51] add missing message --- src/main/resources/l10n/JabRef_en.properties | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/resources/l10n/JabRef_en.properties b/src/main/resources/l10n/JabRef_en.properties index 0d5281b27a2..c2d0ea46ee7 100644 --- a/src/main/resources/l10n/JabRef_en.properties +++ b/src/main/resources/l10n/JabRef_en.properties @@ -2295,6 +2295,7 @@ Custom\ DOI\ URI=Custom DOI URI Customization=Customization Use\ custom\ DOI\ base\ URI\ for\ article\ access=Use custom DOI base URI for article access +Cited\ on\ pages=Cited on pages Unable\ to\ find\ valid\ certification\ path\ to\ requested\ target(%0),\ download\ anyway?=Unable to find valid certification path to requested target(%0), download anyway? Download\ operation\ canceled.=Download operation canceled. From 0fa9a4c03365a15126b79d9cf45e45affd99ce7c Mon Sep 17 00:00:00 2001 From: Antal K Date: Thu, 3 Jun 2021 11:39:40 +0200 Subject: [PATCH 21/51] add backend --- .../logic/openoffice/backend/Backend52.java | 465 ++++++++++++++++ .../logic/openoffice/backend/Codec52.java | 148 ++++++ .../logic/openoffice/backend/GetContext.java | 87 +++ .../NamedRangeManagerReferenceMark.java | 50 ++ .../backend/NamedRangeReferenceMark.java | 500 ++++++++++++++++++ .../model/openoffice/backend/NamedRange.java | 65 +++ .../openoffice/backend/NamedRangeManager.java | 31 ++ 7 files changed, 1346 insertions(+) create mode 100644 src/main/java/org/jabref/logic/openoffice/backend/Backend52.java create mode 100644 src/main/java/org/jabref/logic/openoffice/backend/Codec52.java create mode 100644 src/main/java/org/jabref/logic/openoffice/backend/GetContext.java create mode 100644 src/main/java/org/jabref/logic/openoffice/backend/NamedRangeManagerReferenceMark.java create mode 100644 src/main/java/org/jabref/logic/openoffice/backend/NamedRangeReferenceMark.java create mode 100644 src/main/java/org/jabref/model/openoffice/backend/NamedRange.java create mode 100644 src/main/java/org/jabref/model/openoffice/backend/NamedRangeManager.java diff --git a/src/main/java/org/jabref/logic/openoffice/backend/Backend52.java b/src/main/java/org/jabref/logic/openoffice/backend/Backend52.java new file mode 100644 index 00000000000..d7c1b62086a --- /dev/null +++ b/src/main/java/org/jabref/logic/openoffice/backend/Backend52.java @@ -0,0 +1,465 @@ +package org.jabref.logic.openoffice.backend; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +import org.jabref.model.openoffice.CitationEntry; +import org.jabref.model.openoffice.backend.NamedRange; +import org.jabref.model.openoffice.backend.NamedRangeManager; +import org.jabref.model.openoffice.ootext.OOText; +import org.jabref.model.openoffice.style.Citation; +import org.jabref.model.openoffice.style.CitationGroup; +import org.jabref.model.openoffice.style.CitationGroupId; +import org.jabref.model.openoffice.style.CitationGroups; +import org.jabref.model.openoffice.style.CitationType; +import org.jabref.model.openoffice.style.OODataModel; +import org.jabref.model.openoffice.style.PageInfo; +import org.jabref.model.openoffice.uno.CreationException; +import org.jabref.model.openoffice.uno.NoDocumentException; +import org.jabref.model.openoffice.uno.UnoUserDefinedProperty; +import org.jabref.model.openoffice.util.OOListUtil; + +import com.sun.star.beans.IllegalTypeException; +import com.sun.star.beans.NotRemoveableException; +import com.sun.star.beans.PropertyExistException; +import com.sun.star.beans.PropertyVetoException; +import com.sun.star.beans.UnknownPropertyException; +import com.sun.star.container.NoSuchElementException; +import com.sun.star.lang.WrappedTargetException; +import com.sun.star.text.XTextCursor; +import com.sun.star.text.XTextDocument; +import com.sun.star.text.XTextRange; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class Backend52 { + private static final Logger LOGGER = LoggerFactory.getLogger(Backend52.class); + public final OODataModel dataModel; + private final NamedRangeManager citationStorageManager; + private final Map cgidToNamedRange; + + // uses: Codec52 + public Backend52() { + this.dataModel = OODataModel.JabRef52; + this.citationStorageManager = new NamedRangeManagerReferenceMark(); + this.cgidToNamedRange = new HashMap<>(); + } + + /** + * Get reference mark names from the document matching the pattern + * used for JabRef reference mark names. + * + * Note: the names returned are in arbitrary order. + * + */ + public List getJabRefReferenceMarkNames(XTextDocument doc) + throws + NoDocumentException { + List allNames = this.citationStorageManager.nrmGetUsedNames(doc); + return Codec52.filterIsJabRefReferenceMarkName(allNames); + } + + /** + * Names of custom properties belonging to us, but without a + * corresponding reference mark. These can be deleted. + * + * @param citationGroupNames These are the names that are used. + * + */ + private List findUnusedJabrefPropertyNames(XTextDocument doc, + List citationGroupNames) { + + // Collect unused jabrefPropertyNames + Set citationGroupNamesSet = citationGroupNames.stream().collect(Collectors.toSet()); + + List pageInfoThrash = new ArrayList<>(); + List jabrefPropertyNames = + UnoUserDefinedProperty.getListOfNames(doc) + .stream() + .filter(Codec52::isJabRefReferenceMarkName) + .collect(Collectors.toList()); + for (String pn : jabrefPropertyNames) { + if (!citationGroupNamesSet.contains(pn)) { + pageInfoThrash.add(pn); + } + } + return pageInfoThrash; + } + + /** + * @return Optional.empty if all is OK, message text otherwise. + */ + public Optional healthReport(XTextDocument doc) + throws + NoDocumentException { + List pageInfoThrash = + this.findUnusedJabrefPropertyNames(doc, this.getJabRefReferenceMarkNames(doc)); + if (pageInfoThrash.isEmpty()) { + return Optional.empty(); // "Backend52: found no unused pageInfo data"; + } + String msg = + "Backend52: found unused pageInfo data, with names listed below.\n" + + "In LibreOffice you may remove these in [File]/[Properties]/[Custom Properties]\n"; + msg += "" + String.join("\n", pageInfoThrash) + ""; + return Optional.of(msg); + } + + private static void setPageInfoInDataInitial(List citations, Optional pageInfo) { + // attribute to last citation (initially localOrder == storageOrder) + if (citations.size() > 0) { + citations.get(citations.size() - 1).setPageInfo(pageInfo); + } + } + + private static void setPageInfoInData(CitationGroup cg, Optional pageInfo) { + List citations = cg.getCitationsInLocalOrder(); + if (citations.size() > 0) { + citations.get(citations.size() - 1).setPageInfo(pageInfo); + } + } + + private static Optional getPageInfoFromData(CitationGroup cg) { + List citations = cg.getCitationsInLocalOrder(); + if (citations.size() > 0) { + return citations.get(citations.size() - 1).getPageInfo(); + } else { + return Optional.empty(); + } + } + + /** + * We have circular dependency here: backend uses + * class from ... + */ + public CitationGroup readCitationGroupFromDocumentOrThrow(XTextDocument doc, String refMarkName) + throws + WrappedTargetException, + NoDocumentException { + + Optional op = Codec52.parseMarkName(refMarkName); + if (op.isEmpty()) { + throw new IllegalArgumentException("readCitationGroupFromDocumentOrThrow:" + + " found unparsable referenceMarkName"); + } + Codec52.ParsedMarkName ov = op.get(); + CitationGroupId cgid = new CitationGroupId(refMarkName); + List citations = (ov.citationKeys.stream() + .map(Citation::new) + .collect(Collectors.toList())); + + Optional pageInfo = + (UnoUserDefinedProperty.getStringValue(doc, refMarkName) + .map(OOText::fromString)); + pageInfo = PageInfo.normalizePageInfo(pageInfo); + + setPageInfoInDataInitial(citations, pageInfo); + + Optional namedRange = citationStorageManager.nrmGetFromDocument(doc, refMarkName); + + if (namedRange.isEmpty()) { + throw new IllegalArgumentException("readCitationGroupFromDocumentOrThrow:" + + " referenceMarkName is not in the document"); + } + + CitationGroup cg = new CitationGroup(OODataModel.JabRef52, + cgid, + ov.citationType, + citations, + Optional.of(refMarkName)); + this.cgidToNamedRange.put(cgid, namedRange.get()); + return cg; + } + + /** + * Create a reference mark with the given name, at the + * end of position. + * + * On return {@code position} is collapsed, and is after the + * inserted space, or at the end of the reference mark. + * + * @param position Collapsed to its end. + * @param insertSpaceAfter We insert a space after the mark, that + * carries on format of characters from + * the original position. + */ + public CitationGroup createCitationGroup(XTextDocument doc, + List citationKeys, + List> pageInfos, + CitationType citationType, + XTextCursor position, + boolean insertSpaceAfter) + throws + CreationException, + NoDocumentException, + WrappedTargetException, + NotRemoveableException, + PropertyExistException, + PropertyVetoException, + IllegalTypeException { + + Objects.requireNonNull(pageInfos); + if (pageInfos.size() != citationKeys.size()) { + throw new RuntimeException("pageInfos.size != citationKeys.size"); + } + + // Get a new refMarkName + Set usedNames = new HashSet<>(this.citationStorageManager.nrmGetUsedNames(doc)); + String xkey = (citationKeys.stream().collect(Collectors.joining(","))); + String refMarkName = Codec52.getUniqueMarkName(usedNames, xkey, citationType); + + CitationGroupId cgid = new CitationGroupId(refMarkName); + + final int nCitations = citationKeys.size(); + final int last = nCitations - 1; + + // Build citations, add pageInfo to each citation + List citations = new ArrayList<>(nCitations); + for (int i = 0; i < nCitations; i++) { + Citation cit = new Citation(citationKeys.get(i)); + citations.add(cit); + + Optional pageInfo = PageInfo.normalizePageInfo(pageInfos.get(i)); + switch (dataModel) { + case JabRef52: + if (i == last) { + cit.setPageInfo(pageInfo); + } else { + if (pageInfo.isPresent()) { + LOGGER.warn("dataModel JabRef52" + + " only supports pageInfo for the last citation of a group"); + } + } + break; + case JabRef53: + cit.setPageInfo(pageInfo); + break; + } + } + + /* + * Apply to document + */ + boolean withoutBrackets = (citationType == CitationType.INVISIBLE_CIT); + NamedRange namedRange = + this.citationStorageManager.nrmCreate(doc, refMarkName, position, insertSpaceAfter, + withoutBrackets); + + switch (dataModel) { + case JabRef52: + Optional pageInfo = PageInfo.normalizePageInfo(pageInfos.get(last)); + + if (pageInfo.isPresent()) { + String pageInfoString = OOText.toString(pageInfo.get()); + UnoUserDefinedProperty.createStringProperty(doc, refMarkName, pageInfoString); + } else { + // do not inherit from trash + UnoUserDefinedProperty.removeIfExists(doc, refMarkName); + } + CitationGroup cg = new CitationGroup(OODataModel.JabRef52, + cgid, + citationType, citations, + Optional.of(refMarkName)); + this.cgidToNamedRange.put(cgid, namedRange); + return cg; + default: + throw new RuntimeException("Backend52 requires JabRef52 dataModel"); + } + } + + /** + * @return A list with a nullable pageInfo entry for each citation in + * joinableGroups. + * + * TODO: JabRef52 combinePageInfos is not reversible. Should warn + * user to check the result. Or ask what to do. + */ + public static List> + combinePageInfosCommon(OODataModel dataModel, List joinableGroup) { + switch (dataModel) { + case JabRef52: + // collect to cgPageInfos + List> cgPageInfos = OOListUtil.map(joinableGroup, + Backend52::getPageInfoFromData); + + // Try to do something of the cgPageInfos. + String cgPageInfo = (cgPageInfos.stream() + .filter(pi -> pi.isPresent()) + .map(pi -> OOText.toString(pi.get())) + .distinct() + .collect(Collectors.joining("; "))); + + int nCitations = (joinableGroup.stream() + .map(cg -> cg.numberOfCitations()) + .mapToInt(Integer::intValue).sum()); + if ("".equals(cgPageInfo)) { + cgPageInfo = null; + } + return OODataModel.fakePageInfos(cgPageInfo, nCitations); + + case JabRef53: + return (joinableGroup.stream() + .flatMap(cg -> (cg.citationsInStorageOrder.stream() + .map(Citation::getPageInfo))) + .collect(Collectors.toList())); + default: + throw new RuntimeException("unhandled dataModel here"); + } + } + + /** + * + */ + public List> combinePageInfos(List joinableGroup) { + return combinePageInfosCommon(this.dataModel, joinableGroup); + } + + private NamedRange getNamedRangeOrThrow(CitationGroup cg) { + NamedRange namedRange = this.cgidToNamedRange.get(cg.cgid); + if (namedRange == null) { + LOGGER.warn("getNamedRange: could not lookup namedRange"); + throw new RuntimeException("getNamedRange: could not lookup namedRange"); + } + return namedRange; + } + + public void removeCitationGroup(CitationGroup cg, XTextDocument doc) + throws + WrappedTargetException, + NoDocumentException, + NoSuchElementException, + NotRemoveableException, + IllegalTypeException, + PropertyExistException { + + NamedRange namedRange = getNamedRangeOrThrow(cg); + String refMarkName = namedRange.nrGetRangeName(); + namedRange.nrRemoveFromDocument(doc); + UnoUserDefinedProperty.removeIfExists(doc, refMarkName); + this.cgidToNamedRange.remove(cg.cgid); + } + + /** + * + * @return Optional.empty if the reference mark is missing. + * + */ + public Optional getMarkRange(CitationGroup cg, XTextDocument doc) + throws + NoDocumentException, + WrappedTargetException { + + NamedRange namedRange = getNamedRangeOrThrow(cg); + return namedRange.nrGetMarkRange(doc); + } + + /** + * Cursor for the reference marks as is, not prepared for filling, + * but does not need cleanFillCursorForCitationGroup either. + */ + public Optional getRawCursorForCitationGroup(CitationGroup cg, XTextDocument doc) + throws + NoDocumentException, + WrappedTargetException { + NamedRange namedRange = getNamedRangeOrThrow(cg); + return namedRange.nrGetRawCursor(doc); + } + + /** + * Must be followed by call to cleanFillCursorForCitationGroup + */ + public XTextCursor getFillCursorForCitationGroup(CitationGroup cg, XTextDocument doc) + throws + NoDocumentException, + WrappedTargetException, + CreationException { + + NamedRange namedRange = getNamedRangeOrThrow(cg); + return namedRange.nrGetFillCursor(doc); + } + + /** To be called after getFillCursorForCitationGroup */ + public void cleanFillCursorForCitationGroup(CitationGroup cg, XTextDocument doc) + throws + NoDocumentException, + WrappedTargetException, + CreationException { + NamedRange namedRange = getNamedRangeOrThrow(cg); + namedRange.nrCleanFillCursor(doc); + } + + public List getCitationEntries(XTextDocument doc, CitationGroups cgs) + throws + UnknownPropertyException, + WrappedTargetException, + NoDocumentException { + + switch (dataModel) { + case JabRef52: + // One context per CitationGroup: Backend52 (DataModel.JabRef52) + // For DataModel.JabRef53 (Backend53) we need one context per Citation + int n = cgs.numberOfCitationGroups(); + List citations = new ArrayList<>(n); + for (CitationGroup cg : cgs.getCitationGroupsUnordered()) { + String name = cg.cgid.citationGroupIdAsString(); + XTextCursor cursor = (this + .getRawCursorForCitationGroup(cg, doc) + .orElseThrow(RuntimeException::new)); + String context = GetContext.getCursorStringWithContext(cursor, 30, 30, true); + Optional pageInfo = (cg.numberOfCitations() > 0 + ? (getPageInfoFromData(cg) + .map(e -> OOText.toString(e))) + : Optional.empty()); + CitationEntry entry = new CitationEntry(name, context, pageInfo); + citations.add(entry); + } + return citations; + case JabRef53: + // xx + throw new RuntimeException("getCitationEntries for JabRef53 is not implemented yet"); + default: + throw new RuntimeException("getCitationEntries: unhandled dataModel "); + } + } + + /* + * Only applies to storage. Citation markers are not changed. + */ + public void applyCitationEntries(XTextDocument doc, List citationEntries) + throws + UnknownPropertyException, + NotRemoveableException, + PropertyExistException, + PropertyVetoException, + IllegalTypeException, + IllegalArgumentException, + NoDocumentException, + WrappedTargetException { + + switch (dataModel) { + case JabRef52: + for (CitationEntry entry : citationEntries) { + Optional pageInfo = entry.getPageInfo().map(OOText::fromString); + pageInfo = PageInfo.normalizePageInfo(pageInfo); + if (pageInfo.isPresent()) { + String name = entry.getRefMarkName(); + UnoUserDefinedProperty.createStringProperty(doc, name, pageInfo.get().asString()); + } + } + break; + case JabRef53: + // xx + throw new RuntimeException("applyCitationEntries for JabRef53 is not implemented yet"); + default: + throw new RuntimeException("applyCitationEntries: unhandled dataModel "); + } + } + +} + diff --git a/src/main/java/org/jabref/logic/openoffice/backend/Codec52.java b/src/main/java/org/jabref/logic/openoffice/backend/Codec52.java new file mode 100644 index 00000000000..352df27cdeb --- /dev/null +++ b/src/main/java/org/jabref/logic/openoffice/backend/Codec52.java @@ -0,0 +1,148 @@ +package org.jabref.logic.openoffice.backend; + +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import org.jabref.model.openoffice.style.CitationType; +import org.jabref.model.openoffice.uno.NoDocumentException; + +/** + * How and what is encoded in a mark names. + * + * - pageInfo does not appear here. It is not encoded in the mark name. + * - Does not depend on the type of marks (reference mark of bookmark) used. + */ +class Codec52 { + private static final String BIB_CITATION = "JR_cite"; + private static final Pattern CITE_PATTERN = + // Pattern.compile(BIB_CITATION + "(\\d*)_(\\d*)_(.*)"); + // citationType is always "1" "2" or "3" + Pattern.compile(BIB_CITATION + "(\\d*)_([123])_(.*)"); + + /** + * This is what we get back from parsing a refMarkName. + * + */ + public static class ParsedMarkName { + /** "", "0", "1" ... */ + public final String i; + /** in-text-citation type */ + public final CitationType citationType; + /** Citation keys embedded in the reference mark. */ + public final List citationKeys; + + ParsedMarkName(String i, CitationType citationType, List citationKeys) { + Objects.requireNonNull(i); + Objects.requireNonNull(citationKeys); + this.i = i; + this.citationType = citationType; + this.citationKeys = citationKeys; + } + } + + /* + * Integer representation was written into the document in + * JabRef52, keep it for compatibility. + */ + public static CitationType CitationTypeFromInt(int i) { + switch (i) { + case 1: + return CitationType.AUTHORYEAR_PAR; + case 2: + return CitationType.AUTHORYEAR_INTEXT; + case 3: + return CitationType.INVISIBLE_CIT; + default: + throw new RuntimeException("Invalid CitationType code"); + } + } + + public static int CitationTypeToInt(CitationType i) { + switch (i) { + case AUTHORYEAR_PAR: + return 1; + case AUTHORYEAR_INTEXT: + return 2; + case INVISIBLE_CIT: + return 3; + default: + throw new RuntimeException("Invalid CitationType"); + } + } + + /** + * Produce a reference mark name for JabRef for the given citation + * key and citationType that does not yet appear among the reference + * marks of the document. + * + * @param bibtexKey The citation key. + * @param citationType Encodes the effect of withText and + * inParenthesis options. + * + * The first occurrence of bibtexKey gets no serial number, the + * second gets 0, the third 1 ... + * + * Or the first unused in this series, after removals. + */ + public static String getUniqueMarkName(Set usedNames, + String bibtexKey, + CitationType citationType) + throws + NoDocumentException { + + int i = 0; + int j = CitationTypeToInt(citationType); + String name = BIB_CITATION + '_' + j + '_' + bibtexKey; + while (usedNames.contains(name)) { + name = BIB_CITATION + i + '_' + j + '_' + bibtexKey; + i++; + } + return name; + } + + /** + * Parse a JabRef (reference) mark name. + * + * @return Optional.empty() on failure. + * + */ + public static Optional parseMarkName(String refMarkName) { + + Matcher citeMatcher = CITE_PATTERN.matcher(refMarkName); + if (!citeMatcher.find()) { + return Optional.empty(); + } + + List keys = Arrays.asList(citeMatcher.group(3).split(",")); + String i = citeMatcher.group(1); + int j = Integer.parseInt(citeMatcher.group(2)); + CitationType citationType = CitationTypeFromInt(j); + return (Optional.of(new Codec52.ParsedMarkName(i, citationType, keys))); + } + + /** + * @return true if name matches the pattern used for JabRef + * reference mark names. + */ + public static boolean isJabRefReferenceMarkName(String name) { + return (CITE_PATTERN.matcher(name).find()); + } + + /** + * Filter a list of reference mark names by `isJabRefReferenceMarkName` + * + * @param names The list to be filtered. + */ + public static List filterIsJabRefReferenceMarkName(List names) { + return (names + .stream() + .filter(Codec52::isJabRefReferenceMarkName) + .collect(Collectors.toList())); + } +} diff --git a/src/main/java/org/jabref/logic/openoffice/backend/GetContext.java b/src/main/java/org/jabref/logic/openoffice/backend/GetContext.java new file mode 100644 index 00000000000..468d630e756 --- /dev/null +++ b/src/main/java/org/jabref/logic/openoffice/backend/GetContext.java @@ -0,0 +1,87 @@ +package org.jabref.logic.openoffice.backend; + +import org.jabref.model.openoffice.uno.NoDocumentException; + +import com.sun.star.lang.WrappedTargetException; +import com.sun.star.text.XTextCursor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Utility methods for processing OO Writer documents. + */ +public class GetContext { + + private static final Logger LOGGER = LoggerFactory.getLogger(GetContext.class); + + private GetContext() { + // Just to hide the public constructor + } + + /** + * Get the text belonging to cursor with up to + * charBefore and charAfter characters of context. + * + * The actual context may be smaller than requested. + * + * @param cursor + * @param charBefore Number of characters requested. + * @param charAfter Number of characters requested. + * @param htmlMarkup If true, the text belonging to the + * reference mark is surrounded by bold html tag. + * + */ + public static String getCursorStringWithContext(XTextCursor cursor, + int charBefore, + int charAfter, + boolean htmlMarkup) + throws + WrappedTargetException, + NoDocumentException { + + String citPart = cursor.getString(); + + // extend cursor range left + int flex = 8; + for (int i = 0; i < charBefore; i++) { + try { + cursor.goLeft((short) 1, true); + // If we are close to charBefore and see a space, + // then cut here. Might avoid cutting a word in half. + if ((i >= (charBefore - flex)) + && Character.isWhitespace(cursor.getString().charAt(0))) { + break; + } + } catch (IndexOutOfBoundsException ex) { + LOGGER.warn("Problem going left", ex); + } + } + + int lengthWithBefore = cursor.getString().length(); + int addedBefore = lengthWithBefore - citPart.length(); + + cursor.collapseToStart(); + for (int i = 0; i < (charAfter + lengthWithBefore); i++) { + try { + cursor.goRight((short) 1, true); + if (i >= ((charAfter + lengthWithBefore) - flex)) { + String strNow = cursor.getString(); + if (Character.isWhitespace(strNow.charAt(strNow.length() - 1))) { + break; + } + } + } catch (IndexOutOfBoundsException ex) { + LOGGER.warn("Problem going right", ex); + } + } + + String result = cursor.getString(); + if (htmlMarkup) { + result = (result.substring(0, addedBefore) + + "" + citPart + "" + + result.substring(lengthWithBefore)); + } + return result.trim(); + } + +} diff --git a/src/main/java/org/jabref/logic/openoffice/backend/NamedRangeManagerReferenceMark.java b/src/main/java/org/jabref/logic/openoffice/backend/NamedRangeManagerReferenceMark.java new file mode 100644 index 00000000000..157dd87cf0c --- /dev/null +++ b/src/main/java/org/jabref/logic/openoffice/backend/NamedRangeManagerReferenceMark.java @@ -0,0 +1,50 @@ +package org.jabref.logic.openoffice.backend; + +import java.util.List; +import java.util.Optional; + +import org.jabref.model.openoffice.backend.NamedRange; +import org.jabref.model.openoffice.backend.NamedRangeManager; +import org.jabref.model.openoffice.uno.CreationException; +import org.jabref.model.openoffice.uno.NoDocumentException; +import org.jabref.model.openoffice.uno.UnoReferenceMark; + +import com.sun.star.lang.WrappedTargetException; +import com.sun.star.text.XTextCursor; +import com.sun.star.text.XTextDocument; + +public class NamedRangeManagerReferenceMark implements NamedRangeManager { + + @Override + public NamedRange nrmCreate(XTextDocument doc, + String refMarkName, + XTextCursor position, + boolean insertSpaceAfter, + boolean withoutBrackets) + throws + CreationException { + return NamedRangeReferenceMark.create(doc, + refMarkName, + position, + insertSpaceAfter, + withoutBrackets); + } + + @Override + public List nrmGetUsedNames(XTextDocument doc) + throws + NoDocumentException { + return UnoReferenceMark.getListOfNames(doc); + } + + @Override + public Optional nrmGetFromDocument(XTextDocument doc, String refMarkName) + throws + NoDocumentException, + WrappedTargetException { + return (NamedRangeReferenceMark + .getFromDocument(doc, refMarkName) + .map(x -> x)); + } +} + diff --git a/src/main/java/org/jabref/logic/openoffice/backend/NamedRangeReferenceMark.java b/src/main/java/org/jabref/logic/openoffice/backend/NamedRangeReferenceMark.java new file mode 100644 index 00000000000..38ac716d9ac --- /dev/null +++ b/src/main/java/org/jabref/logic/openoffice/backend/NamedRangeReferenceMark.java @@ -0,0 +1,500 @@ +package org.jabref.logic.openoffice.backend; + +import java.util.Optional; + +import org.jabref.model.openoffice.backend.NamedRange; +import org.jabref.model.openoffice.uno.CreationException; +import org.jabref.model.openoffice.uno.NoDocumentException; +import org.jabref.model.openoffice.uno.UnoCursor; +import org.jabref.model.openoffice.uno.UnoReferenceMark; + +import com.sun.star.container.NoSuchElementException; +import com.sun.star.lang.WrappedTargetException; +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 org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +// was StorageBaseRefMark + +class NamedRangeReferenceMark implements NamedRange { + + private static final String ZERO_WIDTH_SPACE = "\u200b"; + + // for debugging we may want visible bracket + private static final boolean + REFERENCE_MARK_USE_INVISIBLE_BRACKETS = true; // !debug; + + public static final String + REFERENCE_MARK_LEFT_BRACKET = REFERENCE_MARK_USE_INVISIBLE_BRACKETS ? ZERO_WIDTH_SPACE : "<"; + + public static final String + REFERENCE_MARK_RIGHT_BRACKET = REFERENCE_MARK_USE_INVISIBLE_BRACKETS ? ZERO_WIDTH_SPACE : ">"; + + private static final Logger LOGGER = LoggerFactory.getLogger(NamedRangeReferenceMark.class); + + private String id; /* reference mark name */ + + private NamedRangeReferenceMark(String id) { + this.id = id; + } + + String getId() { + return id; + } + + /** + * Insert {@code n} spaces in a way that reference + * marks just before or just after the cursor are not affected. + * + * This is based on the observation, that starting two + * new paragraphs separates us from a reference mark on either side. + * + * The pattern used is: + * {@code safeInsertSpaces(n): para, para, left, space(n), right-delete, left(n), left-delete} + * + * @param position Where to insert (at position.getStart()) + * @param n Number of spaces to insert. + * + * @return a new cursor, covering the just-inserted spaces. + * + * This could be generalized to insert arbitrary text safely + * between two reference marks. But we do not need that now. + */ + private static XTextCursor safeInsertSpacesBetweenReferenceMarks(XTextRange position, int n) { + // Start with an empty cursor at position.getStart(); + XText text = position.getText(); + XTextCursor cursor = text.createTextCursorByRange(position.getStart()); + text.insertString(cursor, "\r\r", false); // para, para + cursor.goLeft((short) 1, false); // left + text.insertString(cursor, " ".repeat(n), false); // space(n) + cursor.goRight((short) 1, true); + cursor.setString(""); // right-delete + cursor.goLeft((short) n, false); // left(n) + cursor.goLeft((short) 1, true); + cursor.setString(""); // left-delete + cursor.goRight((short) n, true); // select the newly inserted spaces + return cursor; + } + + private static void createReprInDocument(XTextDocument doc, + String refMarkName, + XTextCursor position, + boolean insertSpaceAfter, + boolean withoutBrackets) + throws + CreationException { + + // The cursor we received: we push it before us. + position.collapseToEnd(); + + XTextCursor cursor = safeInsertSpacesBetweenReferenceMarks(position.getEnd(), 2); + + // cursors before the first and after the last space + XTextCursor cursorBefore = cursor.getText().createTextCursorByRange(cursor.getStart()); + XTextCursor cursorAfter = cursor.getText().createTextCursorByRange(cursor.getEnd()); + + cursor.collapseToStart(); + cursor.goRight((short) 1, false); + // now we are between two spaces + + final String left = NamedRangeReferenceMark.REFERENCE_MARK_LEFT_BRACKET; + final String right = NamedRangeReferenceMark.REFERENCE_MARK_RIGHT_BRACKET; + final short leftLength = (short) left.length(); + final short rightLength = (short) right.length(); + String bracketedContent = (withoutBrackets + ? "" + : left + right); + + cursor.getText().insertString(cursor, bracketedContent, true); + + UnoReferenceMark.create(doc, refMarkName, cursor, true /* absorb */); + + cursorBefore.goRight((short) 1, true); + cursorBefore.setString(""); + if (!insertSpaceAfter) { + cursorAfter.goLeft((short) 1, true); + cursorAfter.setString(""); + } + } + + static NamedRangeReferenceMark create(XTextDocument doc, + String refMarkName, + XTextCursor position, + boolean insertSpaceAfter, + boolean withoutBrackets) + throws + CreationException { + + createReprInDocument(doc, + refMarkName, + position, + insertSpaceAfter, + withoutBrackets); + return new NamedRangeReferenceMark(refMarkName); + } + + /** + * @return Optional.empty if there is no corresponding range. + */ + static Optional getFromDocument(XTextDocument doc, + String refMarkName) + throws + NoDocumentException, + WrappedTargetException { + return (UnoReferenceMark.getAnchor(doc, refMarkName) + .map(e -> new NamedRangeReferenceMark(refMarkName))); + } + + /* + * Remove it from the document. + * + * See: removeCitationGroups + */ + @Override + public void nrRemoveFromDocument(XTextDocument doc) + throws + WrappedTargetException, + NoDocumentException, + NoSuchElementException { + UnoReferenceMark.remove(doc, this.nrGetRangeName()); + } + + @Override + public String nrGetRangeName() { + return id; + } + + /* + * ranges controlled by citation groups should not overlap with each other. + * + * @return Optional.empty if the reference mark is missing. + * + * See: UnoReferenceMark.getAnchor + */ + @Override + public Optional nrGetMarkRange(XTextDocument doc) + throws + NoDocumentException, + WrappedTargetException { + String name = this.nrGetRangeName(); + return UnoReferenceMark.getAnchor(doc, name); + } + + /** + * Cursor for the reference marks as is, not prepared for filling, + * but does not need nrCleanFillCursor either. + * + * @return Optional.empty() if reference mark is missing from the document, + * otherwise an XTextCursor for getMarkRange + * + * See: getRawCursorForCitationGroup + */ + @Override + public Optional nrGetRawCursor(XTextDocument doc) + throws + NoDocumentException, + WrappedTargetException { + + String name = this.nrGetRangeName(); + Optional full = Optional.empty(); + + Optional markAsTextContent = UnoReferenceMark.getAsTextContent(doc, name); + + if (markAsTextContent.isEmpty()) { + String msg = String.format("nrGetRawCursor: markAsTextContent(%s).isEmpty()", name); + LOGGER.warn(msg); + } + + full = UnoCursor.getTextCursorOfTextContentAnchor(markAsTextContent.get()); + if (full.isEmpty()) { + String msg = "nrGetRawCursor: full.isEmpty()"; + LOGGER.warn(msg); + return Optional.empty(); + } + return full; + } + + /** + * See: getFillCursorForCitationGroup + */ + @Override + public XTextCursor nrGetFillCursor(XTextDocument doc) + throws + NoDocumentException, + WrappedTargetException, + CreationException { + + String name = this.nrGetRangeName(); + + final boolean debugThisFun = false; + final String left = NamedRangeReferenceMark.REFERENCE_MARK_LEFT_BRACKET; + final String right = NamedRangeReferenceMark.REFERENCE_MARK_RIGHT_BRACKET; + final short leftLength = (short) left.length(); + final short rightLength = (short) right.length(); + + XTextCursor full = null; + String fullText = null; + for (int i = 1; i <= 2; i++) { + Optional markAsTextContent = UnoReferenceMark.getAsTextContent(doc, name); + + if (markAsTextContent.isEmpty()) { + String msg = String.format("nrGetFillCursor:" + + " markAsTextContent(%s).isEmpty (attempt %d)", + name, + i); + throw new RuntimeException(msg); + } + + full = UnoCursor.getTextCursorOfTextContentAnchor(markAsTextContent.get()).orElse(null); + if (full == null) { + String msg = String.format("nrGetFillCursor: full == null (attempt %d)", i); + throw new RuntimeException(msg); + } + + fullText = full.getString(); + + if (debugThisFun) { + System.out.printf("nrGetFillCursor: fulltext = '%s'%n", fullText); + } + + if (fullText.length() >= 2) { + if (debugThisFun) { + System.out.printf("nrGetFillCursor: (attempt: %d) fulltext.length() >= 2," + + " break loop%n", i); + } + break; + } else { + // (fullText.length() < 2) + if (i == 2) { + String msg = String.format("nrGetFillCursor:" + + " (fullText.length() < 2) (attempt %d)", + i); + throw new RuntimeException(msg); + } + // too short, recreate + if (true || debugThisFun) { + System.out.println("nrGetFillCursor: too short, recreate"); + } + full.setString(""); + try { + UnoReferenceMark.remove(doc, name); + } catch (NoSuchElementException ex) { + String msg = String.format("nrGetFillCursor got NoSuchElementException" + + " for '%s'", + name); + LOGGER.warn(msg); + } + createReprInDocument(doc, + name, + full, + false, /* insertSpaceAfter */ + false /* withoutBrackets */); + } + } + + if (full == null) { + throw new RuntimeException("nrGetFillCursorFor: full == null (after loop)"); + } + if (fullText == null) { + throw new RuntimeException("nrGetFillCursor: fullText == null (after loop)"); + } + + fullText = full.getString(); + if (fullText.length() < 2) { + throw new RuntimeException("nrGetFillCursor: fullText.length() < 2 (after loop)'%n"); + } + XTextCursor beta = full.getText().createTextCursorByRange(full); + beta.collapseToStart(); + beta.goRight((short) 1, false); + beta.goRight((short) (fullText.length() - 2), true); + if (debugThisFun) { + System.out.printf("nrGetFillCursor: beta(1) covers '%s'%n", beta.getString()); + } + if (fullText.startsWith(left) && fullText.endsWith(right)) { + beta.setString(""); + } else { + if (debugThisFun) { + String msg = String.format("nrGetFillCursor: recreating brackets for '%s'", fullText); + // LOGGER.warn(msg); + System.out.println(msg); + } + + // we have at least two characters inside + XTextCursor alpha = full.getText().createTextCursorByRange(full); + alpha.collapseToStart(); + + XTextCursor omega = full.getText().createTextCursorByRange(full); + omega.collapseToEnd(); + + // beta now covers everything except first and last character + // Replace its content with brackets + String paddingx = "x"; + String paddingy = "y"; + String paddingz = "z"; + beta.setString(paddingx + left + paddingy + right + paddingz); + if (debugThisFun) { + System.out.printf("nrGetFillCursor: beta(2) covers '%s'%n", beta.getString()); + } + // move beta to before the right bracket + beta.collapseToEnd(); + beta.goLeft((short) (rightLength + 1), false); + // remove middle padding + beta.goLeft((short) 1, true); + if (debugThisFun) { + System.out.printf("nrGetFillCursor: beta(3) covers '%s'%n", beta.getString()); + } + // only drop paddingy later: beta.setString(""); + + // drop the initial character and paddingx + alpha.collapseToStart(); + alpha.goRight((short) (1 + 1), true); + if (debugThisFun) { + System.out.printf("nrGetFillCursor: alpha(4) covers '%s'%n", alpha.getString()); + } + alpha.setString(""); + // drop the last character and paddingz + omega.collapseToEnd(); + omega.goLeft((short) (1 + 1), true); + if (debugThisFun) { + System.out.printf("nrGetFillCursor: omega(5) covers '%s'%n", omega.getString()); + } + omega.setString(""); + + // drop paddingy now + if (debugThisFun) { + System.out.printf("nrGetFillCursor: beta(6) covers '%s'%n", beta.getString()); + } + beta.setString(""); + // should be OK now. + if (debugThisFun) { + alpha.goRight(leftLength, true); + System.out.printf("nrGetFillCursor: alpha(7) covers '%s', should be '%s'%n", + alpha.getString(), left); + omega.goLeft(rightLength, true); + System.out.printf("nrGetFillCursor: omega(8) covers '%s', should be '%s'%n", + omega.getString(), right); + } + } + + // NamedRangeReferenceMark.checkFillCursor(beta); + return beta; + } + + /* + * Throw RuntimeException if the brackets are not there. + */ + public static void checkFillCursor(XTextCursor cursor) { + final String left = REFERENCE_MARK_LEFT_BRACKET; + final String right = REFERENCE_MARK_RIGHT_BRACKET; + final short leftLength = (short) left.length(); + final short rightLength = (short) right.length(); + + XTextCursor alpha = cursor.getText().createTextCursorByRange(cursor); + alpha.collapseToStart(); + + XTextCursor omega = cursor.getText().createTextCursorByRange(cursor); + omega.collapseToEnd(); + + if (leftLength > 0) { + alpha.goLeft(leftLength, true); + if (!left.equals(alpha.getString())) { + String msg = String.format("checkFillCursor:" + + " ('%s') is not prefixed with" + + " REFERENCE_MARK_LEFT_BRACKET, has '%s'", + cursor.getString(), alpha.getString()); + throw new RuntimeException(msg); + } + } + + if (rightLength > 0) { + omega.goRight(rightLength, true); + if (!right.equals(omega.getString())) { + String msg = String.format("checkFillCursor:" + + " ('%s') is not followed by" + + " REFERENCE_MARK_RIGHT_BRACKET, has '%s'", + cursor.getString(), omega.getString()); + throw new RuntimeException(msg); + } + } + } + + /** + * Remove brackets, but if the result would become empty, leave + * them; if the result would be a single characer, leave the left bracket. + * + * See: cleanFillCursorForCitationGroup + */ + @Override + public void nrCleanFillCursor(XTextDocument doc) + throws + NoDocumentException, + WrappedTargetException, + CreationException { + + // alwaysRemoveBrackets : full compatibility with JabRef 5.2: + // brackets are temporary, only exist between nrGetFillCursor + // and nrCleanFillCursor. + final boolean alwaysRemoveBrackets = false; + + // removeBracketsFromEmpty is intended to force removal if we + // are working on an "Empty citation" (INVISIBLE_CIT). + final boolean removeBracketsFromEmpty = false; + + final String left = REFERENCE_MARK_LEFT_BRACKET; + final String right = REFERENCE_MARK_RIGHT_BRACKET; + final short leftLength = (short) left.length(); + final short rightLength = (short) right.length(); + + String name = this.nrGetRangeName(); + + XTextCursor full = this.nrGetRawCursor(doc).orElseThrow(RuntimeException::new); + final String fullText = full.getString(); + final int fullTextLength = fullText.length(); + + if (!fullText.startsWith(left)) { + String msg = String.format("nrCleanFillCursor:" + + " (%s) does not start with REFERENCE_MARK_LEFT_BRACKET", + name); + throw new RuntimeException(msg); + } + + if (!fullText.endsWith(right)) { + String msg = String.format("nrCleanFillCursor:" + + " (%s) does not end with REFERENCE_MARK_RIGHT_BRACKET", + name); + throw new RuntimeException(msg); + } + + final int contentLength = (fullTextLength - (leftLength + rightLength)); + if (contentLength < 0) { + String msg = String.format("nrCleanFillCursor: length(%s) < 0", name); + throw new RuntimeException(msg); + } + + boolean removeRight = ((contentLength >= 1) + || ((contentLength == 0) && removeBracketsFromEmpty) + || alwaysRemoveBrackets); + + boolean removeLeft = ((contentLength >= 2) + || ((contentLength == 0) && removeBracketsFromEmpty) + || alwaysRemoveBrackets); + + if (removeRight) { + XTextCursor omega = full.getText().createTextCursorByRange(full); + omega.collapseToEnd(); + omega.goLeft(rightLength, true); + omega.setString(""); + } + + if (removeLeft) { + XTextCursor alpha = full.getText().createTextCursorByRange(full); + alpha.collapseToStart(); + alpha.goRight(leftLength, true); + alpha.setString(""); + } + } +} diff --git a/src/main/java/org/jabref/model/openoffice/backend/NamedRange.java b/src/main/java/org/jabref/model/openoffice/backend/NamedRange.java new file mode 100644 index 00000000000..e5c54773b07 --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/backend/NamedRange.java @@ -0,0 +1,65 @@ +package org.jabref.model.openoffice.backend; + +import java.util.Optional; + +import org.jabref.model.openoffice.uno.CreationException; +import org.jabref.model.openoffice.uno.NoDocumentException; + +import com.sun.star.container.NoSuchElementException; +import com.sun.star.lang.WrappedTargetException; +import com.sun.star.text.XTextCursor; +import com.sun.star.text.XTextDocument; +import com.sun.star.text.XTextRange; + +public interface NamedRange { + + public String nrGetRangeName(); + + /** + * @return Optional.empty if the mark is missing from the document. + */ + public Optional nrGetMarkRange(XTextDocument doc) + throws + NoDocumentException, + WrappedTargetException; + + /** + * Cursor for the reference marks as is, not prepared for filling, + * but does not need nrCleanFillCursor either. + */ + public Optional nrGetRawCursor(XTextDocument doc) + throws + NoDocumentException, + WrappedTargetException; + + /** + * Get a cursor for filling in text. + * + * Must be followed by nrCleanFillCursor() + */ + public XTextCursor nrGetFillCursor(XTextDocument doc) + throws + NoDocumentException, + WrappedTargetException, + CreationException; + + /** + * Remove brackets, but if the result would become empty, leave + * them; if the result would be a single characer, leave the left bracket. + * + */ + public void nrCleanFillCursor(XTextDocument doc) + throws + NoDocumentException, + WrappedTargetException, + CreationException; + + /** + * Note: create is in NamedRangeManager + */ + public void nrRemoveFromDocument(XTextDocument doc) + throws + WrappedTargetException, + NoDocumentException, + NoSuchElementException; +} diff --git a/src/main/java/org/jabref/model/openoffice/backend/NamedRangeManager.java b/src/main/java/org/jabref/model/openoffice/backend/NamedRangeManager.java new file mode 100644 index 00000000000..f53fcc72527 --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/backend/NamedRangeManager.java @@ -0,0 +1,31 @@ +package org.jabref.model.openoffice.backend; + +import java.util.List; +import java.util.Optional; + +import org.jabref.model.openoffice.uno.CreationException; +import org.jabref.model.openoffice.uno.NoDocumentException; + +import com.sun.star.lang.WrappedTargetException; +import com.sun.star.text.XTextCursor; +import com.sun.star.text.XTextDocument; + +public interface NamedRangeManager { + + public NamedRange nrmCreate(XTextDocument doc, + String markName, + XTextCursor position, + boolean insertSpaceAfter, + boolean withoutBrackets) + throws + CreationException; + + public List nrmGetUsedNames(XTextDocument doc) + throws + NoDocumentException; + + public Optional nrmGetFromDocument(XTextDocument doc, String markName) + throws + NoDocumentException, + WrappedTargetException; +} From a5b7a05d635771c4ca821f850d7bb97d6c3e61be Mon Sep 17 00:00:00 2001 From: Antal K Date: Thu, 3 Jun 2021 11:52:31 +0200 Subject: [PATCH 22/51] add frontend --- .../logic/openoffice/frontend/OOFrontend.java | 615 ++++++++++++++++++ .../frontend/RangeForOverlapCheck.java | 49 ++ .../frontend/UpdateBibliography.java | 153 +++++ .../frontend/UpdateCitationMarkers.java | 174 +++++ src/main/resources/l10n/JabRef_en.properties | 4 + 5 files changed, 995 insertions(+) create mode 100644 src/main/java/org/jabref/logic/openoffice/frontend/OOFrontend.java create mode 100644 src/main/java/org/jabref/logic/openoffice/frontend/RangeForOverlapCheck.java create mode 100644 src/main/java/org/jabref/logic/openoffice/frontend/UpdateBibliography.java create mode 100644 src/main/java/org/jabref/logic/openoffice/frontend/UpdateCitationMarkers.java diff --git a/src/main/java/org/jabref/logic/openoffice/frontend/OOFrontend.java b/src/main/java/org/jabref/logic/openoffice/frontend/OOFrontend.java new file mode 100644 index 00000000000..fd0523a4bf8 --- /dev/null +++ b/src/main/java/org/jabref/logic/openoffice/frontend/OOFrontend.java @@ -0,0 +1,615 @@ +package org.jabref.logic.openoffice.frontend; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; + +import org.jabref.logic.JabRefException; +import org.jabref.logic.l10n.Localization; +import org.jabref.logic.openoffice.backend.Backend52; +import org.jabref.model.openoffice.CitationEntry; +import org.jabref.model.openoffice.ootext.OOText; +import org.jabref.model.openoffice.rangesort.FunctionalTextViewCursor; +import org.jabref.model.openoffice.rangesort.RangeOverlap; +import org.jabref.model.openoffice.rangesort.RangeOverlapBetween; +import org.jabref.model.openoffice.rangesort.RangeOverlapWithin; +import org.jabref.model.openoffice.rangesort.RangeSet; +import org.jabref.model.openoffice.rangesort.RangeSort; +import org.jabref.model.openoffice.rangesort.RangeSortEntry; +import org.jabref.model.openoffice.rangesort.RangeSortVisual; +import org.jabref.model.openoffice.rangesort.RangeSortable; +import org.jabref.model.openoffice.style.CitationGroup; +import org.jabref.model.openoffice.style.CitationGroupId; +import org.jabref.model.openoffice.style.CitationGroups; +import org.jabref.model.openoffice.style.CitationType; +import org.jabref.model.openoffice.style.OODataModel; +import org.jabref.model.openoffice.uno.CreationException; +import org.jabref.model.openoffice.uno.NoDocumentException; +import org.jabref.model.openoffice.uno.UnoCursor; +import org.jabref.model.openoffice.uno.UnoTextRange; +import org.jabref.model.openoffice.util.OOListUtil; +import org.jabref.model.openoffice.util.OOVoidResult; + +import com.sun.star.beans.IllegalTypeException; +import com.sun.star.beans.NotRemoveableException; +import com.sun.star.beans.PropertyExistException; +import com.sun.star.beans.PropertyVetoException; +import com.sun.star.beans.UnknownPropertyException; +import com.sun.star.container.NoSuchElementException; +import com.sun.star.lang.WrappedTargetException; +import com.sun.star.text.XTextCursor; +import com.sun.star.text.XTextDocument; +import com.sun.star.text.XTextRange; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class OOFrontend { + private static final Logger LOGGER = LoggerFactory.getLogger(OOFrontend.class); + public final Backend52 backend; + public final CitationGroups citationGroups; + + public OOFrontend(XTextDocument doc) + throws + NoDocumentException, + WrappedTargetException { + + // TODO: dataModel should come from looking at the + // document and preferences. + // + this.backend = new Backend52(); + + // Get the citationGroupNames + List citationGroupNames = this.backend.getJabRefReferenceMarkNames(doc); + + Map citationGroups = + readCitationGroupsFromDocument(this.backend, doc, citationGroupNames); + this.citationGroups = new CitationGroups(citationGroups); + } + + public OODataModel getDataModel() { + return backend.dataModel; + } + + public Optional healthReport(XTextDocument doc) + throws + NoDocumentException { + return backend.healthReport(doc); + } + + private static Map + readCitationGroupsFromDocument(Backend52 backend, + XTextDocument doc, + List citationGroupNames) + throws + WrappedTargetException, + NoDocumentException { + + Map citationGroups = new HashMap<>(); + for (int i = 0; i < citationGroupNames.size(); i++) { + final String name = citationGroupNames.get(i); + CitationGroup cg = backend.readCitationGroupFromDocumentOrThrow(doc, name); + citationGroups.put(cg.cgid, cg); + } + return citationGroups; + } + + /** + * Creates a list of {@code RangeSortable} values for + * our {@code CitationGroup} values. Originally designed to be + * passed to {@code visualSort}. + * + * The elements of the returned list are actually of type + * {@code RangeSortEntry}. + * + * The result is sorted within {@code XTextRange.getText()} + * partitions of the citation groups according to their {@code XTextRange} + * (before mapping to footnote marks). + * + * In the result, RangeSortable.getIndexInPosition() contains + * unique indexes within the original partition (not after + * mapFootnotesToFootnoteMarks). + * + * @param mapFootnotesToFootnoteMarks If true, replace ranges in + * footnotes with the range of the corresponding footnote + * mark. This is used for numbering the citations. + * + */ + private List> + createVisualSortInput(XTextDocument doc, boolean mapFootnotesToFootnoteMarks) + throws + NoDocumentException, + WrappedTargetException { + + List> sortables = new ArrayList<>(); + for (CitationGroup cg : citationGroups.getCitationGroupsUnordered()) { + XTextRange range = (this + .getMarkRange(doc, cg) + .orElseThrow(RuntimeException::new)); + sortables.add(new RangeSortEntry<>(range, 0, cg)); + } + + /* + * At this point we are almost ready to return sortables. + * + * But we may want to number citations in a footnote + * as if it appeared where the footnote mark is. + * + * The following code replaces ranges within footnotes with + * the range for the corresponding footnote mark. + * + * This brings further ambiguity if we have multiple + * citation groups within the same footnote: for the comparison + * they become indistinguishable. Numbering between them is + * not controlled. Also combineCiteMarkers will see them in + * the wrong order (if we use this comparison), and will not + * be able to merge. To avoid these, we sort textually within + * each .getText() partition and add indexInPosition + * accordingly. + * + */ + + // Sort within partitions + RangeSort.RangePartitions> partitions = + RangeSort.partitionAndSortRanges(sortables); + + // build final list + List> result = new ArrayList<>(); + for (List> partition : partitions.getPartitions()) { + + int indexInPartition = 0; + for (int i = 0; i < partition.size(); i++) { + RangeSortEntry sortable = partition.get(i); + XTextRange aRange = sortable.getRange(); + sortable.setIndexInPosition(indexInPartition++); + if (mapFootnotesToFootnoteMarks) { + Optional footnoteMarkRange = + UnoTextRange.getFootnoteMarkRange(sortable.getRange()); + // Adjust range if we are inside a footnote: + if (footnoteMarkRange.isPresent()) { + sortable.setRange(footnoteMarkRange.get()); + } + } + result.add(sortable); + } + } + return result.stream().map(e -> e).collect(Collectors.toList()); + } + + /** + * @param mapFootnotesToFootnoteMarks If true, sort reference + * marks in footnotes as if they appeared at the + * corresponding footnote mark. + * + * @return citation groups sorted by their visual positions. + * + * Limitation: for two column layout visual (top-down, + * left-right) order does not match the expected (textual) + * order. + * + */ + private List + getVisuallySortedCitationGroups(XTextDocument doc, + boolean mapFootnotesToFootnoteMarks, + FunctionalTextViewCursor fcursor) + throws + WrappedTargetException, + NoDocumentException, + JabRefException { + + List> sortables = + createVisualSortInput(doc, mapFootnotesToFootnoteMarks); + + List> sorted = + RangeSortVisual.visualSort(sortables, + doc, + fcursor); + + List result = + (sorted.stream().map(e -> e.getContent()).collect(Collectors.toList())); + + return result; + } + + /** + * Return citation groups in visual order within (but not across) + * XText partitions. + * + * This is (1) sufficient for combineCiteMarkers which looks for + * consecutive XTextRanges within each XText, (2) not confused by + * multicolumn layout or multipage display. + */ + public List + getCitationGroupsSortedWithinPartitions(XTextDocument doc, boolean mapFootnotesToFootnoteMarks) + throws + NoDocumentException, + WrappedTargetException { + // This is like getVisuallySortedCitationGroups, + // but we skip the visualSort part. + List> sortables = + createVisualSortInput(doc, mapFootnotesToFootnoteMarks); + + return (sortables.stream().map(e -> e.getContent()).collect(Collectors.toList())); + } + + /** + * Create a citation group for the given citation keys, at the + * end of position. + * + * On return {@code position} is collapsed, and is after the + * inserted space, or at the end of the reference mark. + * + * @param citationKeys In storage order + * @param pageInfos In storage order + * @param citationType + * @param position Collapsed to its end. + * @param insertSpaceAfter If true, we insert a space after the mark, that + * carries on format of characters from + * the original position. + */ + public CitationGroup createCitationGroup(XTextDocument doc, + List citationKeys, + List> pageInfos, + CitationType citationType, + XTextCursor position, + boolean insertSpaceAfter) + throws + CreationException, + NoDocumentException, + WrappedTargetException, + NotRemoveableException, + PropertyExistException, + PropertyVetoException, + IllegalTypeException { + + Objects.requireNonNull(pageInfos); + if (pageInfos.size() != citationKeys.size()) { + throw new RuntimeException("pageInfos.size != citationKeys.size"); + } + CitationGroup cg = backend.createCitationGroup(doc, + citationKeys, + pageInfos, + citationType, + position, + insertSpaceAfter); + + this.citationGroups.afterCreateCitationGroup(cg); + return cg; + } + + /** + * Remove {@code cg} both from the document and notify {@code citationGroups} + */ + public void removeCitationGroup(CitationGroup cg, XTextDocument doc) + throws + WrappedTargetException, + NoDocumentException, + NoSuchElementException, + NotRemoveableException, + PropertyExistException, + IllegalTypeException { + + backend.removeCitationGroup(cg, doc); + this.citationGroups.afterRemoveCitationGroup(cg); + } + + public void removeCitationGroups(List cgs, XTextDocument doc) + throws + WrappedTargetException, + NoDocumentException, + NoSuchElementException, + NotRemoveableException, + PropertyExistException, + IllegalTypeException { + + for (CitationGroup cg : cgs) { + removeCitationGroup(cg, doc); + } + } + + /** + * ranges controlled by citation groups should not overlap with each other. + * + * @return Optional.empty() if the reference mark is missing. + * + */ + public Optional getMarkRange(XTextDocument doc, CitationGroup cg) + throws + NoDocumentException, + WrappedTargetException { + return backend.getMarkRange(cg, doc); + } + + public XTextCursor getFillCursorForCitationGroup(XTextDocument doc, CitationGroup cg) + throws + NoDocumentException, + WrappedTargetException, + CreationException { + return backend.getFillCursorForCitationGroup(cg, doc); + } + + /** + * Remove brackets added by getFillCursorForCitationGroup. + */ + public void cleanFillCursorForCitationGroup(XTextDocument doc, CitationGroup cg) + throws + NoDocumentException, + WrappedTargetException, + CreationException { + + backend.cleanFillCursorForCitationGroup(cg, doc); + } + + /** + * @return A RangeForOverlapCheck for each citation group. + * + * result.size() == nRefMarks + */ + public List> citationRanges(XTextDocument doc) + throws + NoDocumentException, + WrappedTargetException { + + List> result = + new ArrayList<>(citationGroups.numberOfCitationGroups()); + + for (CitationGroup cg : citationGroups.getCitationGroupsUnordered()) { + XTextRange range = this.getMarkRange(doc, cg).orElseThrow(RuntimeException::new); + String description = cg.cgid.citationGroupIdAsString(); // cg.cgRangeStorage.nrGetRangeName(); + result.add(new RangeForOverlapCheck<>(range, + cg.cgid, + RangeForOverlapCheck.REFERENCE_MARK_KIND, + description)); + } + return result; + } + + public List> bibliographyRanges(XTextDocument doc) + throws + NoDocumentException, + WrappedTargetException { + + List> result = new ArrayList<>(); + + Optional range = UpdateBibliography.getBibliographyRange(doc); + if (range.isPresent()) { + String description = "bibliography"; + result.add(new RangeForOverlapCheck<>(range.get(), + new CitationGroupId("bibliography"), + RangeForOverlapCheck.BIBLIOGRAPHY_MARK_KIND, + description)); + } + return result; + } + + public List> viewCursorRanges(XTextDocument doc) + throws + NoDocumentException, + WrappedTargetException { + + List> result = new ArrayList<>(); + + Optional range = UnoCursor.getViewCursor(doc).map(e -> e); + if (range.isPresent()) { + String description = "cursor"; + result.add(new RangeForOverlapCheck<>(range.get(), + new CitationGroupId("cursor"), + RangeForOverlapCheck.CURSOR_MARK_KIND, + description)); + } + return result; + } + + /** + * @return A range for each footnote mark where the footnote + * contains at least one citation group. + * + * Purpose: We do not want markers of footnotes containing + * reference marks to overlap with reference + * marks. Overwriting these footnote marks might kill our + * reference marks in the footnote. + * + * Note: Here we directly communicate to the document, not + * through the backend. This is because mapping ranges to + * footnote marks does not depend on how do we mark or + * structure those ranges. + */ + public List> + footnoteMarkRanges(XTextDocument doc, + List> citationRanges) + throws + NoDocumentException, + WrappedTargetException { + + // Avoid inserting the same mark twice. + // Could use RangeSet if we had that. + RangeSet seen = new RangeSet(); + + List> result = new ArrayList<>(); + + for (RangeForOverlapCheck citationRange : citationRanges) { + + Optional footnoteMarkRange = + UnoTextRange.getFootnoteMarkRange(citationRange.range); + + if (footnoteMarkRange.isEmpty()) { + // not in footnote + continue; + } + + boolean seenContains = seen.contains(footnoteMarkRange.get()); + if (!seenContains) { + seen.add(footnoteMarkRange.get()); + result.add(new RangeForOverlapCheck<>(footnoteMarkRange.get(), + citationRange.idWithinKind, + RangeForOverlapCheck.FOOTNOTE_MARK_KIND, + "FootnoteMark for " + citationRange.format())); + } + } + return result; + } + + static String + rangeOverlapsToMessage(List>> overlaps) { + + if (overlaps.size() == 0) { + return "(*no overlaps*)"; + } + + StringBuilder msg = new StringBuilder(); + for (RangeOverlap> overlap : overlaps) { + String listOfRanges = (overlap.valuesForOverlappingRanges.stream() + .map(v -> String.format("'%s'", v.format())) + .collect(Collectors.joining(", "))); + msg.append( + switch (overlap.kind) { + case EQUAL_RANGE -> Localization.lang("Found identical ranges"); + case OVERLAP -> Localization.lang("Found overlapping ranges"); + case TOUCH -> Localization.lang("Found touching ranges"); + }); + msg.append(": "); + msg.append(listOfRanges); + msg.append("\n"); + } + return msg.toString(); + } + + /** + * Check for any overlap between userRanges and protected ranges. + * + * Assume userRanges is small (usually 1 elements for checking the cursor) + * Returns on first problem found. + */ + public OOVoidResult + checkRangeOverlapsWithCursor(XTextDocument doc, + List> userRanges, + boolean requireSeparation) + throws + NoDocumentException, + WrappedTargetException { + + List> citationRanges = citationRanges(doc); + List> ranges = new ArrayList<>(); + + // ranges.addAll(userRanges); + ranges.addAll(bibliographyRanges(doc)); + ranges.addAll(citationRanges); + ranges.addAll(footnoteMarkRanges(doc, citationRanges)); + + List>> overlaps = + RangeOverlapBetween.findFirst( + doc, + userRanges, + ranges, + requireSeparation); + + if (overlaps.size() == 0) { + return OOVoidResult.ok(); + } + return OOVoidResult.error(new JabRefException("Found overlapping or touching ranges", + rangeOverlapsToMessage(overlaps))); + } + + /** + * @param requireSeparation Report range pairs that only share a boundary. + * @param reportAtMost Limit number of overlaps reported (0 for no limit) + * + */ + public OOVoidResult + checkRangeOverlaps(XTextDocument doc, + List> userRanges, + boolean requireSeparation, + int reportAtMost) + throws + NoDocumentException, + WrappedTargetException { + + List> citationRanges = citationRanges(doc); + List> ranges = new ArrayList<>(); + ranges.addAll(userRanges); + ranges.addAll(bibliographyRanges(doc)); + ranges.addAll(citationRanges); + ranges.addAll(footnoteMarkRanges(doc, citationRanges)); + + List>> overlaps = + RangeOverlapWithin.findOverlappingRanges(doc, ranges, requireSeparation, reportAtMost); + + if (overlaps.size() == 0) { + return OOVoidResult.ok(); + } + return OOVoidResult.error(new JabRefException("Found overlapping or touching ranges", + rangeOverlapsToMessage(overlaps))); + } + + /** + * GUI: Get a list of CitationEntry objects corresponding to citations + * in the document. + * + * Called from: ManageCitationsDialogViewModel constructor. + * + * @return A list with entries corresponding to citations in the + * text, in arbitrary order (same order as from + * getJabRefReferenceMarkNames). + * + * Note: visual or alphabetic order could be more + * manageable for the user. We could provide these + * here, but switching between them needs change on + * GUI (adding a toggle or selector). + * + * Note: CitationEntry implements Comparable, where + * compareTo() and equals() are based on refMarkName. + * The order used in the "Manage citations" dialog + * does not seem to use that. + * + * The 1st is labeled "Citation" (show citation in bold, + * and some context around it). + * + * The columns can be sorted by clicking on the column title. + * For the "Citation" column, the sorting is based on the content, + * (the context before the citation), not on the citation itself. + * + * In the "Extra information ..." column some visual indication + * of the editable part could be helpful. + * + * Wish: selecting an entry (or a button in the line) in + * the GUI could move the cursor in the document to + * the entry. + */ + public List getCitationEntries(XTextDocument doc) + throws + UnknownPropertyException, + WrappedTargetException, + NoDocumentException { + return this.backend.getCitationEntries(doc, citationGroups); + } + + public void applyCitationEntries(XTextDocument doc, List citationEntries) + throws + UnknownPropertyException, + NotRemoveableException, + PropertyExistException, + PropertyVetoException, + IllegalTypeException, + IllegalArgumentException, + NoDocumentException, + WrappedTargetException { + this.backend.applyCitationEntries(doc, citationEntries); + } + + public void imposeGlobalOrder(XTextDocument doc, FunctionalTextViewCursor fcursor) + throws + WrappedTargetException, + NoDocumentException, + JabRefException { + + boolean mapFootnotesToFootnoteMarks = true; + List sortedCitationGroups = + getVisuallySortedCitationGroups(doc, mapFootnotesToFootnoteMarks, fcursor); + List sortedCitationGroupIds = + OOListUtil.map(sortedCitationGroups, cg -> cg.cgid); + citationGroups.setGlobalOrder(sortedCitationGroupIds); + } +} diff --git a/src/main/java/org/jabref/logic/openoffice/frontend/RangeForOverlapCheck.java b/src/main/java/org/jabref/logic/openoffice/frontend/RangeForOverlapCheck.java new file mode 100644 index 00000000000..6cbf560ec62 --- /dev/null +++ b/src/main/java/org/jabref/logic/openoffice/frontend/RangeForOverlapCheck.java @@ -0,0 +1,49 @@ +package org.jabref.logic.openoffice.frontend; + +import org.jabref.model.openoffice.rangesort.RangeHolder; + +import com.sun.star.text.XTextRange; + +/** + * Describe a protected range for overlap checking and reporting. + * + * To check that our protected ranges do not overlap, we collect + * these ranges. To check for overlaps between these, we need the + * {@code range} itself. To report the results of overlap + * checking, we need a {@code description} that can be understood + * by the user. + * + * To be able to refer back to more extended data, we might need to + * identify its {@code kind}, and its index in the corresponding + * tables or other identifier within its kind ({@code idWithinKind}) + * + */ +public class RangeForOverlapCheck implements RangeHolder { + + public final static int REFERENCE_MARK_KIND = 0; + public final static int FOOTNOTE_MARK_KIND = 1; + public final static int CURSOR_MARK_KIND = 2; + public final static int BIBLIOGRAPHY_MARK_KIND = 3; + + public final XTextRange range; + + public final int kind; + public final T idWithinKind; + private final String description; + + public RangeForOverlapCheck(XTextRange range, T idWithinKind, int kind, String description) { + this.range = range; + this.kind = kind; + this.idWithinKind = idWithinKind; + this.description = description; + } + + public String format() { + return description; + } + + @Override + public XTextRange getRange() { + return range; + } +} diff --git a/src/main/java/org/jabref/logic/openoffice/frontend/UpdateBibliography.java b/src/main/java/org/jabref/logic/openoffice/frontend/UpdateBibliography.java new file mode 100644 index 00000000000..ef1a4c42dc5 --- /dev/null +++ b/src/main/java/org/jabref/logic/openoffice/frontend/UpdateBibliography.java @@ -0,0 +1,153 @@ +package org.jabref.logic.openoffice.frontend; + +import java.util.Optional; + +import org.jabref.logic.openoffice.style.OOBibStyle; +import org.jabref.logic.openoffice.style.OOFormatBibliography; +import org.jabref.model.openoffice.ootext.OOText; +import org.jabref.model.openoffice.ootext.OOTextIntoOO; +import org.jabref.model.openoffice.style.CitedKeys; +import org.jabref.model.openoffice.uno.CreationException; +import org.jabref.model.openoffice.uno.NoDocumentException; +import org.jabref.model.openoffice.uno.UnoBookmark; +import org.jabref.model.openoffice.uno.UnoTextSection; + +import com.sun.star.beans.PropertyVetoException; +import com.sun.star.beans.UnknownPropertyException; +import com.sun.star.container.NoSuchElementException; +import com.sun.star.lang.WrappedTargetException; +import com.sun.star.text.XTextCursor; +import com.sun.star.text.XTextDocument; +import com.sun.star.text.XTextRange; + +/* + * Update document: citation marks and bibliography + */ +public class UpdateBibliography { + + private static final String BIB_SECTION_NAME = "JR_bib"; + private static final String BIB_SECTION_END_NAME = "JR_bib_end"; + + public static Optional getBibliographyRange(XTextDocument doc) + throws + NoDocumentException, + WrappedTargetException { + Optional sectionRange = UnoTextSection.getAnchor(doc, BIB_SECTION_NAME); + return sectionRange; + } + + /** + * Rebuilds the bibliography. + */ + public static void rebuildBibTextSection(XTextDocument doc, + OOFrontend fr, + CitedKeys bibliography, + OOBibStyle style, + boolean alwaysAddCitedOnPages) + throws + NoSuchElementException, + WrappedTargetException, + IllegalArgumentException, + CreationException, + PropertyVetoException, + UnknownPropertyException, + NoDocumentException { + + clearBibTextSectionContent2(doc); + + populateBibTextSection(doc, + fr, + bibliography, + style, + alwaysAddCitedOnPages); + } + + /** + * Insert a paragraph break and create a text section for the bibliography. + * + * Only called from `clearBibTextSectionContent2` + */ + private static void createBibTextSection2(XTextDocument doc) + throws + CreationException, + IllegalArgumentException { + + // Always creating at the end of the document. + // Alternatively, we could receive a cursor. + XTextCursor textCursor = doc.getText().createTextCursor(); + textCursor.gotoEnd(false); + UnoTextSection.create(doc, BIB_SECTION_NAME, textCursor, false); + } + + /** + * Find and clear the text section BIB_SECTION_NAME to "", + * or create it. + * + * Only called from: `rebuildBibTextSection` + * + */ + private static void clearBibTextSectionContent2(XTextDocument doc) + throws + CreationException, + IllegalArgumentException, + NoDocumentException, + WrappedTargetException { + + // Optional sectionRange = UnoTextSection.getAnchor(doc, BIB_SECTION_NAME); + Optional sectionRange = getBibliographyRange(doc); + if (sectionRange.isEmpty()) { + createBibTextSection2(doc); + return; + } else { + // Clear it + XTextCursor cursor = doc.getText().createTextCursorByRange(sectionRange.get()); + cursor.setString(""); + } + } + + /** + * Only called from: `rebuildBibTextSection` + * + * Assumes the section named BIB_SECTION_NAME exists. + */ + private static void populateBibTextSection(XTextDocument doc, + OOFrontend fr, + CitedKeys bibliography, + OOBibStyle style, + boolean alwaysAddCitedOnPages) + throws + CreationException, + IllegalArgumentException, + NoDocumentException, + NoSuchElementException, + PropertyVetoException, + UnknownPropertyException, + WrappedTargetException { + + XTextRange sectionRange = getBibliographyRange(doc).orElseThrow(RuntimeException::new); + + XTextCursor cursor = doc.getText().createTextCursorByRange(sectionRange); + + // emit the title of the bibliography + OOTextIntoOO.removeDirectFormatting(cursor); + OOText bibliographyText = OOFormatBibliography.formatBibliography(fr.citationGroups, + bibliography, + style, + alwaysAddCitedOnPages); + OOTextIntoOO.write(doc, cursor, bibliographyText); + cursor.collapseToEnd(); + + // remove the inital empty paragraph from the section. + sectionRange = getBibliographyRange(doc).orElseThrow(RuntimeException::new); + XTextCursor initialParagraph = doc.getText().createTextCursorByRange(sectionRange); + initialParagraph.collapseToStart(); + initialParagraph.goRight((short) 1, true); + initialParagraph.setString(""); + + UnoBookmark.remove(doc, BIB_SECTION_END_NAME); + UnoBookmark.create(doc, BIB_SECTION_END_NAME, cursor, true); + + cursor.collapseToEnd(); + } + +} diff --git a/src/main/java/org/jabref/logic/openoffice/frontend/UpdateCitationMarkers.java b/src/main/java/org/jabref/logic/openoffice/frontend/UpdateCitationMarkers.java new file mode 100644 index 00000000000..7d864ab1063 --- /dev/null +++ b/src/main/java/org/jabref/logic/openoffice/frontend/UpdateCitationMarkers.java @@ -0,0 +1,174 @@ +package org.jabref.logic.openoffice.frontend; + +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +import org.jabref.logic.JabRefException; +import org.jabref.logic.openoffice.style.OOBibStyle; +import org.jabref.model.openoffice.ootext.OOText; +import org.jabref.model.openoffice.ootext.OOTextIntoOO; +import org.jabref.model.openoffice.style.CitationGroup; +import org.jabref.model.openoffice.style.CitationGroups; +import org.jabref.model.openoffice.style.CitationType; +import org.jabref.model.openoffice.uno.CreationException; +import org.jabref.model.openoffice.uno.NoDocumentException; + +import com.sun.star.beans.IllegalTypeException; +import com.sun.star.beans.NotRemoveableException; +import com.sun.star.beans.PropertyExistException; +import com.sun.star.beans.PropertyVetoException; +import com.sun.star.beans.UnknownPropertyException; +import com.sun.star.container.NoSuchElementException; +import com.sun.star.lang.WrappedTargetException; +import com.sun.star.text.XTextCursor; +import com.sun.star.text.XTextDocument; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/* + * Update document: citation marks and bibliography + */ +public class UpdateCitationMarkers { + + private static final Logger LOGGER = LoggerFactory.getLogger(UpdateCitationMarkers.class); + + /** + * Visit each reference mark in referenceMarkNames, overwrite its + * text content. + * + * After each fillCitationMarkInCursor call check if we lost the + * BIB_SECTION_NAME bookmark and recreate it if we did. + * + * @param fr + * + * @param style Bibliography style to use. + * + */ + public static void applyNewCitationMarkers(XTextDocument doc, OOFrontend fr, OOBibStyle style) + throws + NoDocumentException, + UnknownPropertyException, + CreationException, + WrappedTargetException, + PropertyVetoException, + NoSuchElementException, + JabRefException { + + CitationGroups citationGroups = fr.citationGroups; + + for (CitationGroup cg : citationGroups.getCitationGroupsUnordered()) { + + boolean withText = (cg.citationType != CitationType.INVISIBLE_CIT); + Optional marker = cg.getCitationMarker(); + + if (!marker.isPresent()) { + String msg = String.format("applyNewCitationMarkers: no marker for %s", + cg.cgid.citationGroupIdAsString()); + LOGGER.warn(msg); + continue; + } + + if (withText && marker.isPresent()) { + + XTextCursor cursor = fr.getFillCursorForCitationGroup(doc, cg); + + fillCitationMarkInCursor(doc, cursor, marker.get(), withText, style); + + fr.cleanFillCursorForCitationGroup(doc, cg); + } + + } + } + + public static void fillCitationMarkInCursor(XTextDocument doc, + XTextCursor cursor, + OOText citationText, + boolean withText, + OOBibStyle style) + throws + UnknownPropertyException, + PropertyVetoException, + WrappedTargetException, + NoSuchElementException, + CreationException, + IllegalArgumentException { + + Objects.requireNonNull(cursor); + Objects.requireNonNull(citationText); + Objects.requireNonNull(style); + + if (withText) { + OOText citationText2 = style.decorateCitationMarker(citationText); + // inject a ZERO_WIDTH_SPACE to hold the initial character format + final String ZERO_WIDTH_SPACE = "\u200b"; + citationText2 = OOText.fromString(ZERO_WIDTH_SPACE + citationText2.asString()); + OOTextIntoOO.write(doc, cursor, citationText2); + } else { + cursor.setString(""); + } + } + + /** + * Inserts a citation group in the document: creates and fills it. + * + * @param citationKeys BibTeX keys of + * @param pageInfos + * @param citationType + * + * @param citationText Text for the citation. A citation mark or + * placeholder if not yet available. + * + * @param position Location to insert at. + * @param style + * @param insertSpaceAfter A space inserted after the reference + * mark makes it easier to separate from the text + * coming after. But is not wanted when we recreate a + * reference mark. + */ + public static void createAndFillCitationGroup(OOFrontend fr, + XTextDocument doc, + List citationKeys, + List> pageInfos, + CitationType citationType, + OOText citationText, + XTextCursor position, + OOBibStyle style, + boolean insertSpaceAfter) + throws + UnknownPropertyException, + NotRemoveableException, + PropertyExistException, + PropertyVetoException, + WrappedTargetException, + PropertyVetoException, + IllegalArgumentException, + CreationException, + NoDocumentException, + IllegalTypeException, + NoSuchElementException { + + Objects.requireNonNull(pageInfos); + if (pageInfos.size() != citationKeys.size()) { + throw new RuntimeException("pageInfos.size != citationKeys.size"); + } + CitationGroup cg = fr.createCitationGroup(doc, + citationKeys, + pageInfos, + citationType, + position, + insertSpaceAfter); + + final boolean withText = citationType.withText(); + + if (withText) { + XTextCursor c2 = fr.getFillCursorForCitationGroup(doc, cg); + + UpdateCitationMarkers.fillCitationMarkInCursor(doc, c2, citationText, withText, style); + + fr.cleanFillCursorForCitationGroup(doc, cg); + } + position.collapseToEnd(); + } + +} diff --git a/src/main/resources/l10n/JabRef_en.properties b/src/main/resources/l10n/JabRef_en.properties index c2d0ea46ee7..dc2fd8df58a 100644 --- a/src/main/resources/l10n/JabRef_en.properties +++ b/src/main/resources/l10n/JabRef_en.properties @@ -1529,6 +1529,10 @@ Custom=Custom Export\ cited=Export cited Unable\ to\ generate\ new\ library=Unable to generate new library +Found\ identical\ ranges=Found identical ranges +Found\ overlapping\ ranges=Found overlapping ranges +Found\ touching\ ranges=Found touching ranges + Note\:\ Use\ the\ placeholder\ %DIR%\ for\ the\ location\ of\ the\ opened\ library\ file.=Note: Use the placeholder %DIR% for the location of the opened library file. Error\ occured\ while\ executing\ the\ command\ \"%0\".=Error occured while executing the command \"%0\". Reformat\ ISSN=Reformat ISSN From c50c458643b6de3be4f6d217baf38d14702c1a2c Mon Sep 17 00:00:00 2001 From: Antal K Date: Thu, 3 Jun 2021 11:58:32 +0200 Subject: [PATCH 23/51] add actions --- .../logic/openoffice/action/EditInsert.java | 130 ++++++ .../logic/openoffice/action/EditMerge.java | 372 ++++++++++++++++++ .../logic/openoffice/action/EditSeparate.java | 106 +++++ .../logic/openoffice/action/ExportCited.java | 98 +++++ .../openoffice/action/ManageCitations.java | 41 ++ .../logic/openoffice/action/Update.java | 145 +++++++ 6 files changed, 892 insertions(+) create mode 100644 src/main/java/org/jabref/logic/openoffice/action/EditInsert.java create mode 100644 src/main/java/org/jabref/logic/openoffice/action/EditMerge.java create mode 100644 src/main/java/org/jabref/logic/openoffice/action/EditSeparate.java create mode 100644 src/main/java/org/jabref/logic/openoffice/action/ExportCited.java create mode 100644 src/main/java/org/jabref/logic/openoffice/action/ManageCitations.java create mode 100644 src/main/java/org/jabref/logic/openoffice/action/Update.java diff --git a/src/main/java/org/jabref/logic/openoffice/action/EditInsert.java b/src/main/java/org/jabref/logic/openoffice/action/EditInsert.java new file mode 100644 index 00000000000..277b92bec76 --- /dev/null +++ b/src/main/java/org/jabref/logic/openoffice/action/EditInsert.java @@ -0,0 +1,130 @@ +package org.jabref.logic.openoffice.action; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import org.jabref.logic.JabRefException; +import org.jabref.logic.openoffice.frontend.OOFrontend; +import org.jabref.logic.openoffice.frontend.UpdateCitationMarkers; +import org.jabref.logic.openoffice.style.OOBibStyle; +import org.jabref.model.database.BibDatabase; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.openoffice.ootext.OOText; +import org.jabref.model.openoffice.style.Citation; +import org.jabref.model.openoffice.style.CitationMarkerEntry; +import org.jabref.model.openoffice.style.CitationType; +import org.jabref.model.openoffice.style.NonUniqueCitationMarker; +import org.jabref.model.openoffice.style.OODataModel; +import org.jabref.model.openoffice.uno.CreationException; +import org.jabref.model.openoffice.uno.NoDocumentException; +import org.jabref.model.openoffice.uno.UnoScreenRefresh; +import org.jabref.model.openoffice.util.OOListUtil; + +import com.sun.star.beans.IllegalTypeException; +import com.sun.star.beans.NotRemoveableException; +import com.sun.star.beans.PropertyExistException; +import com.sun.star.beans.PropertyVetoException; +import com.sun.star.beans.UnknownPropertyException; +import com.sun.star.container.NoSuchElementException; +import com.sun.star.lang.WrappedTargetException; +import com.sun.star.text.XTextCursor; +import com.sun.star.text.XTextDocument; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class EditInsert { + + private static final Logger LOGGER = LoggerFactory.getLogger(EditInsert.class); + + /** + * In insertEntry we receive BibEntry values from the GUI. + * + * In the document we store citations by their citation key. + * + * If the citation key is missing, the best we can do is to notify + * the user. Or the programmer, that we cannot accept such input. + * + */ + private static String insertEntryGetCitationKey(BibEntry entry) { + Optional key = entry.getCitationKey(); + if (key.isEmpty()) { + throw new RuntimeException("insertEntryGetCitationKey:" + + " cannot cite entries without citation key"); + } + return key.get(); + } + + /* + * @param cursor Where to insert. + * + * @param sync If not empty, update citation markers and, + * depending on the embedded options, the + * bibliography. + * + * @param fcursor If sync.isPresent(), it must provide a + * FunctionalTextViewCursor. Otherwise not used. + */ + public static void insertCitationGroup(XTextDocument doc, + OOFrontend fr, + XTextCursor cursor, + List entries, + BibDatabase database, + OOBibStyle style, + CitationType citationType, + String pageInfo) + throws + UnknownPropertyException, + NoDocumentException, + NotRemoveableException, + WrappedTargetException, + PropertyVetoException, + PropertyExistException, + NoSuchElementException, + CreationException, + IllegalTypeException, + JabRefException { + + List citationKeys = OOListUtil.map(entries, EditInsert::insertEntryGetCitationKey); + + final int nEntries = entries.size(); + List> pageInfos = OODataModel.fakePageInfos(pageInfo, nEntries); + + List citations = new ArrayList<>(nEntries); + for (int i = 0; i < nEntries; i++) { + Citation cit = new Citation(citationKeys.get(i)); + cit.lookupInDatabases(Collections.singletonList(database)); + cit.setPageInfo(pageInfos.get(i)); + citations.add(cit); + } + + // The text we insert + OOText citeText = + (style.isNumberEntries() + ? OOText.fromString("[-]") // A dash only. Only refresh later. + : style.createCitationMarker(citations, + citationType.inParenthesis(), + NonUniqueCitationMarker.FORGIVEN)); + + if ("".equals(OOText.toString(citeText))) { + citeText = OOText.fromString("[?]"); + } + + try { + UnoScreenRefresh.lockControllers(doc); + UpdateCitationMarkers.createAndFillCitationGroup(fr, + doc, + citationKeys, + pageInfos, + citationType, + citeText, + cursor, + style, + true /* insertSpaceAfter */); + } finally { + UnoScreenRefresh.unlockControllers(doc); + } + + } +} diff --git a/src/main/java/org/jabref/logic/openoffice/action/EditMerge.java b/src/main/java/org/jabref/logic/openoffice/action/EditMerge.java new file mode 100644 index 00000000000..823610c51cf --- /dev/null +++ b/src/main/java/org/jabref/logic/openoffice/action/EditMerge.java @@ -0,0 +1,372 @@ +package org.jabref.logic.openoffice.action; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; + +import org.jabref.logic.JabRefException; +import org.jabref.logic.openoffice.frontend.OOFrontend; +import org.jabref.logic.openoffice.frontend.UpdateCitationMarkers; +import org.jabref.logic.openoffice.style.OOBibStyle; +import org.jabref.model.openoffice.ootext.OOText; +import org.jabref.model.openoffice.style.Citation; +import org.jabref.model.openoffice.style.CitationGroup; +import org.jabref.model.openoffice.style.CitationType; +import org.jabref.model.openoffice.uno.CreationException; +import org.jabref.model.openoffice.uno.NoDocumentException; +import org.jabref.model.openoffice.uno.UnoScreenRefresh; +import org.jabref.model.openoffice.uno.UnoTextRange; +import org.jabref.model.openoffice.util.OOListUtil; + +import com.sun.star.beans.IllegalTypeException; +import com.sun.star.beans.NotRemoveableException; +import com.sun.star.beans.PropertyExistException; +import com.sun.star.beans.PropertyVetoException; +import com.sun.star.beans.UnknownPropertyException; +import com.sun.star.container.NoSuchElementException; +import com.sun.star.lang.WrappedTargetException; +import com.sun.star.text.XTextCursor; +import com.sun.star.text.XTextDocument; +import com.sun.star.text.XTextRange; +import com.sun.star.util.InvalidStateException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class EditMerge { + + private static final Logger LOGGER = LoggerFactory.getLogger(EditMerge.class); + + private EditMerge() { + // hide constructor + } + + /* + * @return true if modified document + */ + public static boolean mergeCitationGroups(XTextDocument doc, OOFrontend fr, OOBibStyle style) + throws + CreationException, + IllegalArgumentException, + IllegalTypeException, + InvalidStateException, + JabRefException, + NoDocumentException, + NoSuchElementException, + NotRemoveableException, + PropertyExistException, + PropertyVetoException, + UnknownPropertyException, + WrappedTargetException { + + boolean madeModifications = false; + + try { + UnoScreenRefresh.lockControllers(doc); + + List joinableGroups = EditMerge.scan(doc, fr); + + for (JoinableGroupData joinableGroupData : joinableGroups) { + + List cgs = joinableGroupData.group; + + List newCitations = (cgs.stream() + .flatMap(cg -> cg.citationsInStorageOrder.stream()) + .collect(Collectors.toList())); + + CitationType citationType = cgs.get(0).citationType; + List> pageInfos = fr.backend.combinePageInfos(cgs); + + fr.removeCitationGroups(cgs, doc); + XTextCursor textCursor = joinableGroupData.groupCursor; + textCursor.setString(""); // Also remove the spaces between. + + List citationKeys = OOListUtil.map(newCitations, Citation::getCitationKey); + + /* insertSpaceAfter: no, it is already there (or could be) */ + boolean insertSpaceAfter = false; + UpdateCitationMarkers.createAndFillCitationGroup(fr, + doc, + citationKeys, + pageInfos, + citationType, + OOText.fromString("tmp"), + textCursor, + style, + insertSpaceAfter); + } + + madeModifications = !joinableGroups.isEmpty(); + + } finally { + UnoScreenRefresh.unlockControllers(doc); + } + + return madeModifications; + } + + private static class JoinableGroupData { + /* + * A list of consecutive citation groups only separated by spaces. + */ + List group; + /* + * A cursor covering the XTextRange of each entry in group + * (and the spaces between them) + */ + XTextCursor groupCursor; + + JoinableGroupData(List group, XTextCursor groupCursor) { + this.group = group; + this.groupCursor = groupCursor; + } + } + + private static class ScanState { + + // Citation groups in the current group + List currentGroup; + + // A cursor that covers the Citation groups in currentGroup, + // including the space between them. + // Null if currentGroup.isEmpty() + XTextCursor currentGroupCursor; + + // A cursor starting at the end of the last CitationGroup in + // currentGroup. Null if currentGroup.isEmpty() + XTextCursor cursorBetween; + + // The last element of currentGroup. + // Null if currentGroup.isEmpty() + CitationGroup prev; + + // The XTextRange for prev. + // Null if currentGroup.isEmpty() + XTextRange prevRange; + + ScanState() { + reset(); + } + + void reset() { + currentGroup = new ArrayList<>(); + currentGroupCursor = null; + cursorBetween = null; + prev = null; + prevRange = null; + } + } + + /** + * Decide if cg could be added to state.currentGroup + * + * @param cg The CitationGroup to test + * @param currentRange The XTextRange corresponding to cg. + * + * @return false if cannot add, true if can. If returned true, + * then state.cursorBetween and state.currentGroupCursor are + * expanded to end at the start of currentRange. + */ + private static boolean checkAddToGroup(ScanState state, CitationGroup cg, XTextRange currentRange) { + + if (state.currentGroup.isEmpty()) { + return false; + } + + Objects.requireNonNull(state.currentGroupCursor); + Objects.requireNonNull(state.cursorBetween); + Objects.requireNonNull(state.prev); + Objects.requireNonNull(state.prevRange); + + // Only combine (Author 2000) type citations + if (cg.citationType != CitationType.AUTHORYEAR_PAR) { + return false; + } + + if (state.prev != null) { + + // Even if we combine AUTHORYEAR_INTEXT citations, we + // would not mix them with AUTHORYEAR_PAR + if (cg.citationType != state.prev.citationType) { + return false; + } + + if (!UnoTextRange.comparables(state.prevRange, currentRange)) { + return false; + } + + // Sanity check: the current range should start later than + // the previous. + int textOrder = UnoTextRange.compareStarts(state.prevRange, currentRange); + if (textOrder != (-1)) { + String msg = + String.format("MergeCitationGroups:" + + " \"%s\" supposed to be followed by \"%s\"," + + " but %s", + state.prevRange.getString(), + currentRange.getString(), + ((textOrder == 0) + ? "they start at the same position" + : ("the start of the latter precedes" + + " the start of the first"))); + LOGGER.warn(msg); + return false; + } + } + + if (state.cursorBetween == null) { + return false; + } + + Objects.requireNonNull(state.cursorBetween); + Objects.requireNonNull(state.currentGroupCursor); + + // assume: currentGroupCursor.getEnd() == cursorBetween.getEnd() + if (UnoTextRange.compareEnds(state.cursorBetween, state.currentGroupCursor) != 0) { + String msg = ("MergeCitationGroups:" + + " cursorBetween.end != currentGroupCursor.end"); + throw new RuntimeException(msg); + } + + /* + * Try to expand state.currentGroupCursor and state.cursorBetween by going right + * to reach rangeStart. + */ + XTextRange rangeStart = currentRange.getStart(); + boolean couldExpand = true; + XTextCursor thisCharCursor = + (currentRange.getText().createTextCursorByRange(state.cursorBetween.getEnd())); + + while (couldExpand && (UnoTextRange.compareEnds(state.cursorBetween, rangeStart) < 0)) { + // + // Check that we only walk through inline whitespace. + // + couldExpand = thisCharCursor.goRight((short) 1, true); + String thisChar = thisCharCursor.getString(); + thisCharCursor.collapseToEnd(); + if (thisChar.isEmpty() || thisChar.equals("\n") || !thisChar.trim().isEmpty()) { + couldExpand = false; + if (!thisChar.isEmpty()) { + thisCharCursor.goLeft((short) 1, false); + } + break; + } + state.cursorBetween.goRight((short) 1, true); + state.currentGroupCursor.goRight((short) 1, true); + + // These two should move in sync: + if (UnoTextRange.compareEnds(state.cursorBetween, state.currentGroupCursor) != 0) { + String msg = ("MergeCitationGroups:" + + " cursorBetween.end != currentGroupCursor.end" + + " (during expand)"); + throw new RuntimeException(msg); + } + } // while + + if (!couldExpand) { + return false; + } + + return true; + } + + /** + * Add cg to state.currentGroup + * Set state.cursorBetween to start at currentRange.getEnd() + * Expand state.currentGroupCursor to also cover currentRange + * Set state.prev to cg, state.prevRange to currentRange + */ + private static void addToCurrentGroup(ScanState state, CitationGroup cg, XTextRange currentRange) { + final boolean isNewGroup = state.currentGroup.isEmpty(); + if (!isNewGroup) { + Objects.requireNonNull(state.currentGroupCursor); + Objects.requireNonNull(state.cursorBetween); + Objects.requireNonNull(state.prev); + Objects.requireNonNull(state.prevRange); + } + + // Add the current entry to a group. + state.currentGroup.add(cg); + + // Set up cursorBetween to start at currentRange.getEnd() + XTextRange rangeEnd = currentRange.getEnd(); + state.cursorBetween = currentRange.getText().createTextCursorByRange(rangeEnd); + + // If new group, create currentGroupCursor + if (isNewGroup) { + state.currentGroupCursor = (currentRange.getText() + .createTextCursorByRange(currentRange.getStart())); + } + + // include currentRange in currentGroupCursor + state.currentGroupCursor.goRight((short) (currentRange.getString().length()), true); + + if (UnoTextRange.compareEnds(state.cursorBetween, state.currentGroupCursor) != 0) { + String msg = ("MergeCitationGroups: cursorBetween.end != currentGroupCursor.end"); + throw new RuntimeException(msg); + } + + /* Store data about last entry in currentGroup */ + state.prev = cg; + state.prevRange = currentRange; + } + + /** + * Scan the document for joinable groups. Return those found. + */ + private static List scan(XTextDocument doc, OOFrontend fr) + throws + NoDocumentException, + WrappedTargetException { + List result = new ArrayList<>(); + + List cgs = + fr.getCitationGroupsSortedWithinPartitions(doc, + false /* mapFootnotesToFootnoteMarks */); + if (cgs.isEmpty()) { + return result; + } + + ScanState state = new ScanState(); + + for (CitationGroup cg : cgs) { + + XTextRange currentRange = (fr.getMarkRange(doc, cg) + .orElseThrow(RuntimeException::new)); + + /* + * Decide if we add cg to the group. False when the group is empty. + */ + boolean addToGroup = checkAddToGroup(state, cg, currentRange); + + /* + * Even if we do not add it to an existing group, + * we might use it to start a new group. + * + * Can it start a new group? + */ + boolean canStartGroup = (cg.citationType == CitationType.AUTHORYEAR_PAR); + + if (!addToGroup) { + // close currentGroup + if (state.currentGroup.size() > 1) { + result.add(new JoinableGroupData(state.currentGroup, state.currentGroupCursor)); + } + // Start a new, empty group + state.reset(); + } + + if (addToGroup || canStartGroup) { + addToCurrentGroup(state, cg, currentRange); + } + } + + // close currentGroup + if (state.currentGroup.size() > 1) { + result.add(new JoinableGroupData(state.currentGroup, state.currentGroupCursor)); + } + return result; + } + +} diff --git a/src/main/java/org/jabref/logic/openoffice/action/EditSeparate.java b/src/main/java/org/jabref/logic/openoffice/action/EditSeparate.java new file mode 100644 index 00000000000..31c23ed52d0 --- /dev/null +++ b/src/main/java/org/jabref/logic/openoffice/action/EditSeparate.java @@ -0,0 +1,106 @@ +package org.jabref.logic.openoffice.action; + +import java.util.List; + +import org.jabref.logic.JabRefException; +import org.jabref.logic.openoffice.frontend.OOFrontend; +import org.jabref.logic.openoffice.frontend.UpdateCitationMarkers; +import org.jabref.logic.openoffice.style.OOBibStyle; +import org.jabref.logic.openoffice.style.OOProcess; +import org.jabref.model.database.BibDatabase; +import org.jabref.model.openoffice.ootext.OOText; +import org.jabref.model.openoffice.style.Citation; +import org.jabref.model.openoffice.style.CitationGroup; +import org.jabref.model.openoffice.uno.CreationException; +import org.jabref.model.openoffice.uno.NoDocumentException; +import org.jabref.model.openoffice.uno.UnoScreenRefresh; + +import com.sun.star.beans.IllegalTypeException; +import com.sun.star.beans.NotRemoveableException; +import com.sun.star.beans.PropertyExistException; +import com.sun.star.beans.PropertyVetoException; +import com.sun.star.beans.UnknownPropertyException; +import com.sun.star.container.NoSuchElementException; +import com.sun.star.lang.WrappedTargetException; +import com.sun.star.text.XTextCursor; +import com.sun.star.text.XTextDocument; +import com.sun.star.text.XTextRange; +import com.sun.star.util.InvalidStateException; + +public class EditSeparate { + + public static boolean separateCitations(XTextDocument doc, + OOFrontend fr, + List databases, + OOBibStyle style) + throws + CreationException, + IllegalTypeException, + InvalidStateException, + JabRefException, + NoDocumentException, + NoSuchElementException, + NotRemoveableException, + PropertyExistException, + PropertyVetoException, + UnknownPropertyException, + WrappedTargetException, + com.sun.star.lang.IllegalArgumentException { + + boolean madeModifications = false; + + // To reduce surprises in JabRef52 mode, impose localOrder to + // decide the visually last Citation in the group. Unless the + // style changed since refresh this is the last on the screen + // as well. + fr.citationGroups.lookupCitations(databases); + fr.citationGroups.imposeLocalOrder(OOProcess.comparatorForMulticite(style)); + + List cgs = fr.citationGroups.getCitationGroupsUnordered(); + + try { + UnoScreenRefresh.lockControllers(doc); + + for (CitationGroup cg : cgs) { + + XTextRange range1 = (fr + .getMarkRange(doc, cg) + .orElseThrow(RuntimeException::new)); + XTextCursor textCursor = range1.getText().createTextCursorByRange(range1); + + List cits = cg.citationsInStorageOrder; + if (cits.size() <= 1) { + continue; + } + + fr.removeCitationGroup(cg, doc); + // Now we own the content of cits + + // Create a citation group for each citation. + final int last = cits.size() - 1; + for (int i = 0; i < cits.size(); i++) { + boolean insertSpaceAfter = (i != last); + Citation cit = cits.get(i); + + UpdateCitationMarkers.createAndFillCitationGroup( + fr, + doc, + List.of(cit.citationKey), + List.of(cit.getPageInfo()), + cg.citationType, + OOText.fromString(cit.citationKey), + textCursor, + style, + insertSpaceAfter); + + textCursor.collapseToEnd(); + } + + madeModifications = true; + } + } finally { + UnoScreenRefresh.unlockControllers(doc); + } + return madeModifications; + } +} diff --git a/src/main/java/org/jabref/logic/openoffice/action/ExportCited.java b/src/main/java/org/jabref/logic/openoffice/action/ExportCited.java new file mode 100644 index 00000000000..8d344688c68 --- /dev/null +++ b/src/main/java/org/jabref/logic/openoffice/action/ExportCited.java @@ -0,0 +1,98 @@ +package org.jabref.logic.openoffice.action; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.jabref.logic.openoffice.frontend.OOFrontend; +import org.jabref.model.database.BibDatabase; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.field.StandardField; +import org.jabref.model.openoffice.style.CitedKey; +import org.jabref.model.openoffice.style.CitedKeys; +import org.jabref.model.openoffice.uno.NoDocumentException; + +import com.sun.star.beans.UnknownPropertyException; +import com.sun.star.container.NoSuchElementException; +import com.sun.star.lang.WrappedTargetException; +import com.sun.star.text.XTextDocument; + +public class ExportCited { + + public static class GenerateDatabaseResult { + /** + * null: not done; isEmpty: no unresolved + */ + public final List unresolvedKeys; + public final BibDatabase newDatabase; + + GenerateDatabaseResult(List unresolvedKeys, BibDatabase newDatabase) { + this.unresolvedKeys = unresolvedKeys; + this.newDatabase = newDatabase; + } + } + + /** + * + * @param databases The databases to look up the citation keys in the document from. + * @return A new database, with cloned entries. + * + * If a key is not found, it is added to result.unresolvedKeys + * + * Cross references (in StandardField.CROSSREF) are followed (not recursively): + * if the referenced entry is found, it is included in the result. + * If it is not found, it is silently ignored. + */ + public static GenerateDatabaseResult generateDatabase(XTextDocument doc, List databases) + throws + NoDocumentException, + NoSuchElementException, + UnknownPropertyException, + WrappedTargetException { + + OOFrontend fr = new OOFrontend(doc); + CitedKeys cks = fr.citationGroups.getCitedKeysUnordered(); + cks.lookupInDatabases(databases); + + List unresolvedKeys = new ArrayList<>(); + BibDatabase resultDatabase = new BibDatabase(); + + List entriesToInsert = new ArrayList<>(); + Set seen = new HashSet<>(); // Only add crossReference once. + + for (CitedKey ck : cks.values()) { + if (ck.getLookupResult().isEmpty()) { + unresolvedKeys.add(ck.citationKey); + continue; + } else { + BibEntry entry = ck.getLookupResult().get().entry; + BibDatabase loopDatabase = ck.getLookupResult().get().database; + + // If entry found + BibEntry clonedEntry = (BibEntry) entry.clone(); + + // Insert a copy of the entry + entriesToInsert.add(clonedEntry); + + // Check if the cloned entry has a cross-reference field + clonedEntry + .getField(StandardField.CROSSREF) + .ifPresent(crossReference -> { + boolean isNew = !seen.contains(crossReference); + if (isNew) { + // Add it if it is in the current library + loopDatabase + .getEntryByCitationKey(crossReference) + .ifPresent(entriesToInsert::add); + seen.add(crossReference); + } + }); + } + } + + resultDatabase.insertEntries(entriesToInsert); + return new GenerateDatabaseResult(unresolvedKeys, resultDatabase); + } + +} diff --git a/src/main/java/org/jabref/logic/openoffice/action/ManageCitations.java b/src/main/java/org/jabref/logic/openoffice/action/ManageCitations.java new file mode 100644 index 00000000000..4046cbbe11b --- /dev/null +++ b/src/main/java/org/jabref/logic/openoffice/action/ManageCitations.java @@ -0,0 +1,41 @@ +package org.jabref.logic.openoffice.action; + +import java.util.List; + +import org.jabref.logic.openoffice.frontend.OOFrontend; +import org.jabref.model.openoffice.CitationEntry; +import org.jabref.model.openoffice.uno.NoDocumentException; + +import com.sun.star.beans.IllegalTypeException; +import com.sun.star.beans.NotRemoveableException; +import com.sun.star.beans.PropertyExistException; +import com.sun.star.beans.PropertyVetoException; +import com.sun.star.beans.UnknownPropertyException; +import com.sun.star.lang.WrappedTargetException; +import com.sun.star.text.XTextDocument; + +public class ManageCitations { + + public static List getCitationEntries(XTextDocument doc) + throws + NoDocumentException, + UnknownPropertyException, + WrappedTargetException { + OOFrontend fr = new OOFrontend(doc); + return fr.getCitationEntries(doc); + } + + public static void applyCitationEntries(XTextDocument doc, List citationEntries) + throws + NoDocumentException, + UnknownPropertyException, + NotRemoveableException, + PropertyExistException, + PropertyVetoException, + IllegalTypeException, + WrappedTargetException, + IllegalArgumentException { + OOFrontend fr = new OOFrontend(doc); + fr.applyCitationEntries(doc, citationEntries); + } +} diff --git a/src/main/java/org/jabref/logic/openoffice/action/Update.java b/src/main/java/org/jabref/logic/openoffice/action/Update.java new file mode 100644 index 00000000000..151e4b6575b --- /dev/null +++ b/src/main/java/org/jabref/logic/openoffice/action/Update.java @@ -0,0 +1,145 @@ +package org.jabref.logic.openoffice.action; + +import java.util.List; + +import org.jabref.logic.JabRefException; +import org.jabref.logic.openoffice.frontend.OOFrontend; +import org.jabref.logic.openoffice.frontend.UpdateBibliography; +import org.jabref.logic.openoffice.frontend.UpdateCitationMarkers; +import org.jabref.logic.openoffice.style.OOBibStyle; +import org.jabref.logic.openoffice.style.OOProcess; +import org.jabref.model.database.BibDatabase; +import org.jabref.model.openoffice.rangesort.FunctionalTextViewCursor; +import org.jabref.model.openoffice.uno.CreationException; +import org.jabref.model.openoffice.uno.NoDocumentException; +import org.jabref.model.openoffice.uno.UnoScreenRefresh; + +import com.sun.star.beans.PropertyVetoException; +import com.sun.star.beans.UnknownPropertyException; +import com.sun.star.container.NoSuchElementException; +import com.sun.star.lang.WrappedTargetException; +import com.sun.star.text.XTextDocument; + +/* + * Update document: citation marks and bibliography + */ +public class Update { + + /* + * @return unresolvedKeys + */ + private static List updateDocument(XTextDocument doc, + OOFrontend fr, + List databases, + OOBibStyle style, + FunctionalTextViewCursor fcursor, + boolean doUpdateBibliography, + boolean alwaysAddCitedOnPages) + throws + CreationException, + JabRefException, + NoDocumentException, + NoSuchElementException, + PropertyVetoException, + UnknownPropertyException, + WrappedTargetException, + com.sun.star.lang.IllegalArgumentException { + + final boolean useLockControllers = true; + + fr.imposeGlobalOrder(doc, fcursor); + OOProcess.produceCitationMarkers(fr.citationGroups, databases, style); + + try { + if (useLockControllers) { + UnoScreenRefresh.lockControllers(doc); + } + + UpdateCitationMarkers.applyNewCitationMarkers(doc, fr, style); + + if (doUpdateBibliography) { + UpdateBibliography.rebuildBibTextSection(doc, + fr, + fr.citationGroups.getBibliography().get(), + style, + alwaysAddCitedOnPages); + } + List result = fr.citationGroups.getUnresolvedKeys(); + return result; + } finally { + if (useLockControllers && UnoScreenRefresh.hasControllersLocked(doc)) { + UnoScreenRefresh.unlockControllers(doc); + } + } + } + + public static class SyncOptions { + + public final List databases; + boolean updateBibliography; + boolean alwaysAddCitedOnPages; + + public SyncOptions(List databases) { + this.databases = databases; + this.updateBibliography = false; + this.alwaysAddCitedOnPages = false; + } + + public SyncOptions setUpdateBibliography(boolean value) { + this.updateBibliography = value; + return this; + } + + public SyncOptions setAlwaysAddCitedOnPages(boolean value) { + this.alwaysAddCitedOnPages = value; + return this; + } + } + + public static List synchronizeDocument(XTextDocument doc, + OOFrontend fr, + OOBibStyle style, + FunctionalTextViewCursor fcursor, + SyncOptions syncOptions) + throws + CreationException, + JabRefException, + NoDocumentException, + NoSuchElementException, + PropertyVetoException, + UnknownPropertyException, + WrappedTargetException, + com.sun.star.lang.IllegalArgumentException { + + return Update.updateDocument(doc, + fr, + syncOptions.databases, + style, + fcursor, + syncOptions.updateBibliography, + syncOptions.alwaysAddCitedOnPages); + } + + /* + * Reread document before sync + */ + public static List resyncDocument(XTextDocument doc, + OOBibStyle style, + FunctionalTextViewCursor fcursor, + SyncOptions syncOptions) + throws + CreationException, + JabRefException, + NoDocumentException, + NoSuchElementException, + PropertyVetoException, + UnknownPropertyException, + WrappedTargetException, + com.sun.star.lang.IllegalArgumentException { + + OOFrontend fr = new OOFrontend(doc); + + return Update.synchronizeDocument(doc, fr, style, fcursor, syncOptions); + } + +} From 18ef070015052631ecaadc7c437d16c789b7940c Mon Sep 17 00:00:00 2001 From: Antal K Date: Sat, 19 Jun 2021 13:30:28 +0200 Subject: [PATCH 24/51] apply oobranch-[DEFG]-update.patch --- .../logic/openoffice/action/EditInsert.java | 17 +- .../logic/openoffice/action/EditMerge.java | 64 ++--- .../logic/openoffice/action/EditSeparate.java | 21 +- .../logic/openoffice/action/ExportCited.java | 2 +- .../logic/openoffice/action/Update.java | 6 +- .../logic/openoffice/backend/Backend52.java | 65 +++-- .../logic/openoffice/backend/Codec52.java | 32 +-- .../logic/openoffice/backend/GetContext.java | 8 +- .../NamedRangeManagerReferenceMark.java | 6 +- .../backend/NamedRangeReferenceMark.java | 164 ++++------- .../logic/openoffice/frontend/OOFrontend.java | 254 +++++++++--------- .../frontend/UpdateBibliography.java | 11 +- .../frontend/UpdateCitationMarkers.java | 4 +- .../logic/openoffice/style/OOBibStyle.java | 16 +- .../style/OOBibStyleGetCitationMarker.java | 12 +- .../style/OOBibStyleGetNumCitationMarker.java | 15 +- .../style/OOFormatBibliography.java | 25 +- .../logic/openoffice/style/OOProcess.java | 6 +- .../model/openoffice/backend/NamedRange.java | 8 +- .../model/openoffice/ootext/OOFormat.java | 59 ++-- .../model/openoffice/ootext/OOText.java | 28 +- .../model/openoffice/ootext/OOTextIntoOO.java | 120 ++++----- .../rangesort/FunctionalTextViewCursor.java | 52 ++-- .../rangesort/RangeOverlapBetween.java | 42 +-- .../rangesort/RangeOverlapWithin.java | 27 +- .../model/openoffice/rangesort/RangeSet.java | 7 +- .../model/openoffice/rangesort/RangeSort.java | 19 +- .../openoffice/rangesort/RangeSortVisual.java | 52 ++-- .../openoffice/rangesort/RangeSortable.java | 6 +- .../model/openoffice/style/Citation.java | 2 +- .../model/openoffice/style/CitationGroup.java | 9 +- .../openoffice/style/CitationGroupId.java | 3 +- .../openoffice/style/CitationGroups.java | 59 ++-- .../style/CitationLookupResult.java | 3 +- .../openoffice/style/CitationMarkerEntry.java | 10 +- .../style/CitationMarkerNormEntry.java | 7 +- .../model/openoffice/style/CitationPath.java | 4 +- .../model/openoffice/style/CitedKey.java | 11 +- .../openoffice/style/CompareCitation.java | 4 +- .../openoffice/style/CompareCitedKey.java | 4 +- .../style/NonUniqueCitationMarker.java | 6 +- .../model/openoffice/style/OODataModel.java | 12 +- .../model/openoffice/style/PageInfo.java | 2 +- .../model/openoffice/uno/UnoBookmark.java | 34 +-- .../jabref/model/openoffice/uno/UnoCast.java | 11 +- .../model/openoffice/uno/UnoCrossRef.java | 56 ++-- .../model/openoffice/uno/UnoCursor.java | 4 +- .../model/openoffice/uno/UnoNameAccess.java | 5 +- .../jabref/model/openoffice/uno/UnoNamed.java | 40 ++- .../model/openoffice/uno/UnoProperties.java | 2 +- .../model/openoffice/uno/UnoRedlines.java | 23 +- .../openoffice/uno/UnoReferenceMark.java | 50 ++-- .../model/openoffice/uno/UnoSelection.java | 16 +- .../jabref/model/openoffice/uno/UnoStyle.java | 19 +- .../model/openoffice/uno/UnoTextDocument.java | 11 +- .../model/openoffice/uno/UnoTextRange.java | 18 +- .../model/openoffice/uno/UnoTextSection.java | 26 +- .../jabref/model/openoffice/uno/UnoUndo.java | 11 +- .../uno/UnoUserDefinedProperty.java | 58 ++-- .../model/openoffice/util/OOResult.java | 29 +- .../model/openoffice/util/OOTuple3.java | 11 + .../openoffice/style/OOBibStyleTest.java | 22 +- .../style/OOBibStyleTestHelper.java | 16 +- 63 files changed, 787 insertions(+), 959 deletions(-) diff --git a/src/main/java/org/jabref/logic/openoffice/action/EditInsert.java b/src/main/java/org/jabref/logic/openoffice/action/EditInsert.java index 277b92bec76..1345f1f2a99 100644 --- a/src/main/java/org/jabref/logic/openoffice/action/EditInsert.java +++ b/src/main/java/org/jabref/logic/openoffice/action/EditInsert.java @@ -43,28 +43,21 @@ public class EditInsert { * * In the document we store citations by their citation key. * - * If the citation key is missing, the best we can do is to notify - * the user. Or the programmer, that we cannot accept such input. + * If the citation key is missing, the best we can do is to notify the user. Or the programmer, + * that we cannot accept such input. * */ private static String insertEntryGetCitationKey(BibEntry entry) { Optional key = entry.getCitationKey(); if (key.isEmpty()) { - throw new RuntimeException("insertEntryGetCitationKey:" - + " cannot cite entries without citation key"); + throw new IllegalArgumentException("insertEntryGetCitationKey: cannot cite entries without citation key"); } return key.get(); } - /* + /** * @param cursor Where to insert. - * - * @param sync If not empty, update citation markers and, - * depending on the embedded options, the - * bibliography. - * - * @param fcursor If sync.isPresent(), it must provide a - * FunctionalTextViewCursor. Otherwise not used. + * @param pageInfo A single pageInfo for a list of entries. This is what we get from the GUI. */ public static void insertCitationGroup(XTextDocument doc, OOFrontend fr, diff --git a/src/main/java/org/jabref/logic/openoffice/action/EditMerge.java b/src/main/java/org/jabref/logic/openoffice/action/EditMerge.java index 823610c51cf..4cd225eb1a8 100644 --- a/src/main/java/org/jabref/logic/openoffice/action/EditMerge.java +++ b/src/main/java/org/jabref/logic/openoffice/action/EditMerge.java @@ -107,14 +107,10 @@ public static boolean mergeCitationGroups(XTextDocument doc, OOFrontend fr, OOBi } private static class JoinableGroupData { - /* - * A list of consecutive citation groups only separated by spaces. - */ + /** A list of consecutive citation groups only separated by spaces. */ List group; - /* - * A cursor covering the XTextRange of each entry in group - * (and the spaces between them) - */ + + /** A cursor covering the XTextRange of each entry in group (and the spaces between them) */ XTextCursor groupCursor; JoinableGroupData(List group, XTextCursor groupCursor) { @@ -128,21 +124,21 @@ private static class ScanState { // Citation groups in the current group List currentGroup; - // A cursor that covers the Citation groups in currentGroup, - // including the space between them. - // Null if currentGroup.isEmpty() + // A cursor that covers the Citation groups in currentGroup, including the space between + // them. + // null if currentGroup.isEmpty() XTextCursor currentGroupCursor; // A cursor starting at the end of the last CitationGroup in - // currentGroup. Null if currentGroup.isEmpty() + // currentGroup. null if currentGroup.isEmpty() XTextCursor cursorBetween; // The last element of currentGroup. - // Null if currentGroup.isEmpty() + // null if currentGroup.isEmpty() CitationGroup prev; // The XTextRange for prev. - // Null if currentGroup.isEmpty() + // null if currentGroup.isEmpty() XTextRange prevRange; ScanState() { @@ -164,9 +160,8 @@ void reset() { * @param cg The CitationGroup to test * @param currentRange The XTextRange corresponding to cg. * - * @return false if cannot add, true if can. If returned true, - * then state.cursorBetween and state.currentGroupCursor are - * expanded to end at the start of currentRange. + * @return false if cannot add, true if can. If returned true, then state.cursorBetween and + * state.currentGroupCursor are expanded to end at the start of currentRange. */ private static boolean checkAddToGroup(ScanState state, CitationGroup cg, XTextRange currentRange) { @@ -186,8 +181,7 @@ private static boolean checkAddToGroup(ScanState state, CitationGroup cg, XTextR if (state.prev != null) { - // Even if we combine AUTHORYEAR_INTEXT citations, we - // would not mix them with AUTHORYEAR_PAR + // Even if we combine AUTHORYEAR_INTEXT citations, we would not mix them with AUTHORYEAR_PAR if (cg.citationType != state.prev.citationType) { return false; } @@ -196,8 +190,7 @@ private static boolean checkAddToGroup(ScanState state, CitationGroup cg, XTextR return false; } - // Sanity check: the current range should start later than - // the previous. + // Sanity check: the current range should start later than the previous. int textOrder = UnoTextRange.compareStarts(state.prevRange, currentRange); if (textOrder != (-1)) { String msg = @@ -208,8 +201,7 @@ private static boolean checkAddToGroup(ScanState state, CitationGroup cg, XTextR currentRange.getString(), ((textOrder == 0) ? "they start at the same position" - : ("the start of the latter precedes" - + " the start of the first"))); + : "the start of the latter precedes the start of the first")); LOGGER.warn(msg); return false; } @@ -224,14 +216,13 @@ private static boolean checkAddToGroup(ScanState state, CitationGroup cg, XTextR // assume: currentGroupCursor.getEnd() == cursorBetween.getEnd() if (UnoTextRange.compareEnds(state.cursorBetween, state.currentGroupCursor) != 0) { - String msg = ("MergeCitationGroups:" - + " cursorBetween.end != currentGroupCursor.end"); - throw new RuntimeException(msg); + String msg = ("MergeCitationGroups: cursorBetween.end != currentGroupCursor.end"); + throw new IllegalStateException(msg); } /* - * Try to expand state.currentGroupCursor and state.cursorBetween by going right - * to reach rangeStart. + * Try to expand state.currentGroupCursor and state.cursorBetween by going right to reach + * rangeStart. */ XTextRange rangeStart = currentRange.getStart(); boolean couldExpand = true; @@ -257,12 +248,10 @@ private static boolean checkAddToGroup(ScanState state, CitationGroup cg, XTextR // These two should move in sync: if (UnoTextRange.compareEnds(state.cursorBetween, state.currentGroupCursor) != 0) { - String msg = ("MergeCitationGroups:" - + " cursorBetween.end != currentGroupCursor.end" - + " (during expand)"); - throw new RuntimeException(msg); + String msg = ("MergeCitationGroups: cursorBetween.end != currentGroupCursor.end (during expand)"); + throw new IllegalStateException(msg); } - } // while + } if (!couldExpand) { return false; @@ -304,7 +293,7 @@ private static void addToCurrentGroup(ScanState state, CitationGroup cg, XTextRa if (UnoTextRange.compareEnds(state.cursorBetween, state.currentGroupCursor) != 0) { String msg = ("MergeCitationGroups: cursorBetween.end != currentGroupCursor.end"); - throw new RuntimeException(msg); + throw new IllegalStateException(msg); } /* Store data about last entry in currentGroup */ @@ -321,9 +310,7 @@ private static List scan(XTextDocument doc, OOFrontend fr) WrappedTargetException { List result = new ArrayList<>(); - List cgs = - fr.getCitationGroupsSortedWithinPartitions(doc, - false /* mapFootnotesToFootnoteMarks */); + List cgs = fr.getCitationGroupsSortedWithinPartitions(doc, false /* mapFootnotesToFootnoteMarks */); if (cgs.isEmpty()) { return result; } @@ -333,7 +320,7 @@ private static List scan(XTextDocument doc, OOFrontend fr) for (CitationGroup cg : cgs) { XTextRange currentRange = (fr.getMarkRange(doc, cg) - .orElseThrow(RuntimeException::new)); + .orElseThrow(IllegalStateException::new)); /* * Decide if we add cg to the group. False when the group is empty. @@ -341,8 +328,7 @@ private static List scan(XTextDocument doc, OOFrontend fr) boolean addToGroup = checkAddToGroup(state, cg, currentRange); /* - * Even if we do not add it to an existing group, - * we might use it to start a new group. + * Even if we do not add it to an existing group, we might use it to start a new group. * * Can it start a new group? */ diff --git a/src/main/java/org/jabref/logic/openoffice/action/EditSeparate.java b/src/main/java/org/jabref/logic/openoffice/action/EditSeparate.java index 31c23ed52d0..0293159d34f 100644 --- a/src/main/java/org/jabref/logic/openoffice/action/EditSeparate.java +++ b/src/main/java/org/jabref/logic/openoffice/action/EditSeparate.java @@ -65,7 +65,7 @@ public static boolean separateCitations(XTextDocument doc, XTextRange range1 = (fr .getMarkRange(doc, cg) - .orElseThrow(RuntimeException::new)); + .orElseThrow(IllegalStateException::new)); XTextCursor textCursor = range1.getText().createTextCursorByRange(range1); List cits = cg.citationsInStorageOrder; @@ -82,16 +82,15 @@ public static boolean separateCitations(XTextDocument doc, boolean insertSpaceAfter = (i != last); Citation cit = cits.get(i); - UpdateCitationMarkers.createAndFillCitationGroup( - fr, - doc, - List.of(cit.citationKey), - List.of(cit.getPageInfo()), - cg.citationType, - OOText.fromString(cit.citationKey), - textCursor, - style, - insertSpaceAfter); + UpdateCitationMarkers.createAndFillCitationGroup(fr, + doc, + List.of(cit.citationKey), + List.of(cit.getPageInfo()), + cg.citationType, + OOText.fromString(cit.citationKey), + textCursor, + style, + insertSpaceAfter); textCursor.collapseToEnd(); } diff --git a/src/main/java/org/jabref/logic/openoffice/action/ExportCited.java b/src/main/java/org/jabref/logic/openoffice/action/ExportCited.java index 8d344688c68..ef7985d666a 100644 --- a/src/main/java/org/jabref/logic/openoffice/action/ExportCited.java +++ b/src/main/java/org/jabref/logic/openoffice/action/ExportCited.java @@ -41,7 +41,7 @@ public static class GenerateDatabaseResult { * If a key is not found, it is added to result.unresolvedKeys * * Cross references (in StandardField.CROSSREF) are followed (not recursively): - * if the referenced entry is found, it is included in the result. + * If the referenced entry is found, it is included in the result. * If it is not found, it is silently ignored. */ public static GenerateDatabaseResult generateDatabase(XTextDocument doc, List databases) diff --git a/src/main/java/org/jabref/logic/openoffice/action/Update.java b/src/main/java/org/jabref/logic/openoffice/action/Update.java index 151e4b6575b..675afb2a79f 100644 --- a/src/main/java/org/jabref/logic/openoffice/action/Update.java +++ b/src/main/java/org/jabref/logic/openoffice/action/Update.java @@ -20,13 +20,13 @@ import com.sun.star.lang.WrappedTargetException; import com.sun.star.text.XTextDocument; -/* +/** * Update document: citation marks and bibliography */ public class Update { - /* - * @return unresolvedKeys + /** + * @return the list of unresolved citation keys */ private static List updateDocument(XTextDocument doc, OOFrontend fr, diff --git a/src/main/java/org/jabref/logic/openoffice/backend/Backend52.java b/src/main/java/org/jabref/logic/openoffice/backend/Backend52.java index d7c1b62086a..cf91ad1043e 100644 --- a/src/main/java/org/jabref/logic/openoffice/backend/Backend52.java +++ b/src/main/java/org/jabref/logic/openoffice/backend/Backend52.java @@ -67,8 +67,8 @@ public List getJabRefReferenceMarkNames(XTextDocument doc) } /** - * Names of custom properties belonging to us, but without a - * corresponding reference mark. These can be deleted. + * Names of custom properties belonging to us, but without a corresponding reference mark. + * These can be deleted. * * @param citationGroupNames These are the names that are used. * @@ -178,16 +178,14 @@ public CitationGroup readCitationGroupFromDocumentOrThrow(XTextDocument doc, Str } /** - * Create a reference mark with the given name, at the - * end of position. + * Create a reference mark with the given name, at the end of position. * - * On return {@code position} is collapsed, and is after the - * inserted space, or at the end of the reference mark. + * On return {@code position} is collapsed, and is after the inserted space, or at the end of + * the reference mark. * * @param position Collapsed to its end. - * @param insertSpaceAfter We insert a space after the mark, that - * carries on format of characters from - * the original position. + * @param insertSpaceAfter We insert a space after the mark, that carries on format of + * characters from the original position. */ public CitationGroup createCitationGroup(XTextDocument doc, List citationKeys, @@ -206,7 +204,7 @@ public CitationGroup createCitationGroup(XTextDocument doc, Objects.requireNonNull(pageInfos); if (pageInfos.size() != citationKeys.size()) { - throw new RuntimeException("pageInfos.size != citationKeys.size"); + throw new IllegalArgumentException("pageInfos.size != citationKeys.size"); } // Get a new refMarkName @@ -237,7 +235,7 @@ public CitationGroup createCitationGroup(XTextDocument doc, } } break; - case JabRef53: + case JabRef60: cit.setPageInfo(pageInfo); break; } @@ -257,7 +255,7 @@ public CitationGroup createCitationGroup(XTextDocument doc, if (pageInfo.isPresent()) { String pageInfoString = OOText.toString(pageInfo.get()); - UnoUserDefinedProperty.createStringProperty(doc, refMarkName, pageInfoString); + UnoUserDefinedProperty.setStringProperty(doc, refMarkName, pageInfoString); } else { // do not inherit from trash UnoUserDefinedProperty.removeIfExists(doc, refMarkName); @@ -269,16 +267,15 @@ public CitationGroup createCitationGroup(XTextDocument doc, this.cgidToNamedRange.put(cgid, namedRange); return cg; default: - throw new RuntimeException("Backend52 requires JabRef52 dataModel"); + throw new IllegalStateException("Backend52 requires JabRef52 dataModel"); } } /** - * @return A list with a nullable pageInfo entry for each citation in - * joinableGroups. + * @return A list with a nullable pageInfo entry for each citation in joinableGroups. * - * TODO: JabRef52 combinePageInfos is not reversible. Should warn - * user to check the result. Or ask what to do. + * TODO: JabRef52 combinePageInfos is not reversible. Should warn user to check the result. Or + * ask what to do. */ public static List> combinePageInfosCommon(OODataModel dataModel, List joinableGroup) { @@ -296,20 +293,20 @@ public CitationGroup createCitationGroup(XTextDocument doc, .collect(Collectors.joining("; "))); int nCitations = (joinableGroup.stream() - .map(cg -> cg.numberOfCitations()) + .map(CitationGroup::numberOfCitations) .mapToInt(Integer::intValue).sum()); if ("".equals(cgPageInfo)) { cgPageInfo = null; } return OODataModel.fakePageInfos(cgPageInfo, nCitations); - case JabRef53: + case JabRef60: return (joinableGroup.stream() .flatMap(cg -> (cg.citationsInStorageOrder.stream() .map(Citation::getPageInfo))) .collect(Collectors.toList())); default: - throw new RuntimeException("unhandled dataModel here"); + throw new IllegalArgumentException("unhandled dataModel here"); } } @@ -323,8 +320,8 @@ public List> combinePageInfos(List joinableGroup private NamedRange getNamedRangeOrThrow(CitationGroup cg) { NamedRange namedRange = this.cgidToNamedRange.get(cg.cgid); if (namedRange == null) { - LOGGER.warn("getNamedRange: could not lookup namedRange"); - throw new RuntimeException("getNamedRange: could not lookup namedRange"); + String msg = "getNamedRange: could not lookup namedRange"; + throw new IllegalStateException("getNamedRange: could not lookup namedRange"); } return namedRange; } @@ -346,9 +343,7 @@ public void removeCitationGroup(CitationGroup cg, XTextDocument doc) } /** - * * @return Optional.empty if the reference mark is missing. - * */ public Optional getMarkRange(CitationGroup cg, XTextDocument doc) throws @@ -360,8 +355,8 @@ public Optional getMarkRange(CitationGroup cg, XTextDocument doc) } /** - * Cursor for the reference marks as is, not prepared for filling, - * but does not need cleanFillCursorForCitationGroup either. + * Cursor for the reference marks as is: not prepared for filling, but does not need + * cleanFillCursorForCitationGroup either. */ public Optional getRawCursorForCitationGroup(CitationGroup cg, XTextDocument doc) throws @@ -403,14 +398,14 @@ public List getCitationEntries(XTextDocument doc, CitationGroups switch (dataModel) { case JabRef52: // One context per CitationGroup: Backend52 (DataModel.JabRef52) - // For DataModel.JabRef53 (Backend53) we need one context per Citation + // For DataModel.JabRef60 (Backend60) we need one context per Citation int n = cgs.numberOfCitationGroups(); List citations = new ArrayList<>(n); for (CitationGroup cg : cgs.getCitationGroupsUnordered()) { String name = cg.cgid.citationGroupIdAsString(); XTextCursor cursor = (this .getRawCursorForCitationGroup(cg, doc) - .orElseThrow(RuntimeException::new)); + .orElseThrow(IllegalStateException::new)); String context = GetContext.getCursorStringWithContext(cursor, 30, 30, true); Optional pageInfo = (cg.numberOfCitations() > 0 ? (getPageInfoFromData(cg) @@ -420,11 +415,11 @@ public List getCitationEntries(XTextDocument doc, CitationGroups citations.add(entry); } return citations; - case JabRef53: + case JabRef60: // xx - throw new RuntimeException("getCitationEntries for JabRef53 is not implemented yet"); + throw new IllegalStateException("getCitationEntries for JabRef60 is not implemented yet"); default: - throw new RuntimeException("getCitationEntries: unhandled dataModel "); + throw new IllegalStateException("getCitationEntries: unhandled dataModel "); } } @@ -449,15 +444,15 @@ public void applyCitationEntries(XTextDocument doc, List citation pageInfo = PageInfo.normalizePageInfo(pageInfo); if (pageInfo.isPresent()) { String name = entry.getRefMarkName(); - UnoUserDefinedProperty.createStringProperty(doc, name, pageInfo.get().asString()); + UnoUserDefinedProperty.setStringProperty(doc, name, pageInfo.get().toString()); } } break; - case JabRef53: + case JabRef60: // xx - throw new RuntimeException("applyCitationEntries for JabRef53 is not implemented yet"); + throw new IllegalStateException("applyCitationEntries for JabRef60 is not implemented yet"); default: - throw new RuntimeException("applyCitationEntries: unhandled dataModel "); + throw new IllegalStateException("applyCitationEntries: unhandled dataModel "); } } diff --git a/src/main/java/org/jabref/logic/openoffice/backend/Codec52.java b/src/main/java/org/jabref/logic/openoffice/backend/Codec52.java index 352df27cdeb..e4405dc6115 100644 --- a/src/main/java/org/jabref/logic/openoffice/backend/Codec52.java +++ b/src/main/java/org/jabref/logic/openoffice/backend/Codec52.java @@ -16,7 +16,6 @@ * How and what is encoded in a mark names. * * - pageInfo does not appear here. It is not encoded in the mark name. - * - Does not depend on the type of marks (reference mark of bookmark) used. */ class Codec52 { private static final String BIB_CITATION = "JR_cite"; @@ -27,7 +26,6 @@ class Codec52 { /** * This is what we get back from parsing a refMarkName. - * */ public static class ParsedMarkName { /** "", "0", "1" ... */ @@ -46,9 +44,8 @@ public static class ParsedMarkName { } } - /* - * Integer representation was written into the document in - * JabRef52, keep it for compatibility. + /** + * Integer representation was written into the document in JabRef52, keep it for compatibility. */ public static CitationType CitationTypeFromInt(int i) { switch (i) { @@ -59,7 +56,7 @@ public static CitationType CitationTypeFromInt(int i) { case 3: return CitationType.INVISIBLE_CIT; default: - throw new RuntimeException("Invalid CitationType code"); + throw new IllegalArgumentException("Invalid CitationType code"); } } @@ -72,21 +69,18 @@ public static int CitationTypeToInt(CitationType i) { case INVISIBLE_CIT: return 3; default: - throw new RuntimeException("Invalid CitationType"); + throw new IllegalArgumentException("Invalid CitationType"); } } /** - * Produce a reference mark name for JabRef for the given citation - * key and citationType that does not yet appear among the reference - * marks of the document. + * Produce a reference mark name for JabRef for the given citation key and citationType that + * does not yet appear among the reference marks of the document. * * @param bibtexKey The citation key. - * @param citationType Encodes the effect of withText and - * inParenthesis options. + * @param citationType Encodes the effect of withText and inParenthesis options. * - * The first occurrence of bibtexKey gets no serial number, the - * second gets 0, the third 1 ... + * The first occurrence of bibtexKey gets no serial number, the second gets 0, the third 1 ... * * Or the first unused in this series, after removals. */ @@ -97,10 +91,10 @@ public static String getUniqueMarkName(Set usedNames, NoDocumentException { int i = 0; - int j = CitationTypeToInt(citationType); - String name = BIB_CITATION + '_' + j + '_' + bibtexKey; + int citTypeCode = CitationTypeToInt(citationType); + String name = BIB_CITATION + '_' + citTypeCode + '_' + bibtexKey; while (usedNames.contains(name)) { - name = BIB_CITATION + i + '_' + j + '_' + bibtexKey; + name = BIB_CITATION + i + '_' + citTypeCode + '_' + bibtexKey; i++; } return name; @@ -121,8 +115,8 @@ public static Optional parseMarkName(String refMarkName) { List keys = Arrays.asList(citeMatcher.group(3).split(",")); String i = citeMatcher.group(1); - int j = Integer.parseInt(citeMatcher.group(2)); - CitationType citationType = CitationTypeFromInt(j); + int citTypeCode = Integer.parseInt(citeMatcher.group(2)); + CitationType citationType = CitationTypeFromInt(citTypeCode); return (Optional.of(new Codec52.ParsedMarkName(i, citationType, keys))); } diff --git a/src/main/java/org/jabref/logic/openoffice/backend/GetContext.java b/src/main/java/org/jabref/logic/openoffice/backend/GetContext.java index 468d630e756..4cde12ad236 100644 --- a/src/main/java/org/jabref/logic/openoffice/backend/GetContext.java +++ b/src/main/java/org/jabref/logic/openoffice/backend/GetContext.java @@ -27,8 +27,8 @@ private GetContext() { * @param cursor * @param charBefore Number of characters requested. * @param charAfter Number of characters requested. - * @param htmlMarkup If true, the text belonging to the - * reference mark is surrounded by bold html tag. + * @param htmlMarkup If true, the text belonging to the reference mark is surrounded by bold + * html tag. * */ public static String getCursorStringWithContext(XTextCursor cursor, @@ -46,8 +46,8 @@ public static String getCursorStringWithContext(XTextCursor cursor, for (int i = 0; i < charBefore; i++) { try { cursor.goLeft((short) 1, true); - // If we are close to charBefore and see a space, - // then cut here. Might avoid cutting a word in half. + // If we are close to charBefore and see a space, then cut here. Might avoid cutting + // a word in half. if ((i >= (charBefore - flex)) && Character.isWhitespace(cursor.getString().charAt(0))) { break; diff --git a/src/main/java/org/jabref/logic/openoffice/backend/NamedRangeManagerReferenceMark.java b/src/main/java/org/jabref/logic/openoffice/backend/NamedRangeManagerReferenceMark.java index 157dd87cf0c..d82ad08378e 100644 --- a/src/main/java/org/jabref/logic/openoffice/backend/NamedRangeManagerReferenceMark.java +++ b/src/main/java/org/jabref/logic/openoffice/backend/NamedRangeManagerReferenceMark.java @@ -23,11 +23,7 @@ public NamedRange nrmCreate(XTextDocument doc, boolean withoutBrackets) throws CreationException { - return NamedRangeReferenceMark.create(doc, - refMarkName, - position, - insertSpaceAfter, - withoutBrackets); + return NamedRangeReferenceMark.create(doc, refMarkName, position, insertSpaceAfter, withoutBrackets); } @Override diff --git a/src/main/java/org/jabref/logic/openoffice/backend/NamedRangeReferenceMark.java b/src/main/java/org/jabref/logic/openoffice/backend/NamedRangeReferenceMark.java index 38ac716d9ac..63d5f917fc5 100644 --- a/src/main/java/org/jabref/logic/openoffice/backend/NamedRangeReferenceMark.java +++ b/src/main/java/org/jabref/logic/openoffice/backend/NamedRangeReferenceMark.java @@ -47,11 +47,11 @@ String getId() { } /** - * Insert {@code n} spaces in a way that reference - * marks just before or just after the cursor are not affected. + * Insert {@code n} spaces in a way that reference marks just before or just after the cursor + * are not affected. * - * This is based on the observation, that starting two - * new paragraphs separates us from a reference mark on either side. + * This is based on the observation, that starting two new paragraphs separates us from + * reference marks on either side. * * The pattern used is: * {@code safeInsertSpaces(n): para, para, left, space(n), right-delete, left(n), left-delete} @@ -61,8 +61,6 @@ String getId() { * * @return a new cursor, covering the just-inserted spaces. * - * This could be generalized to insert arbitrary text safely - * between two reference marks. But we do not need that now. */ private static XTextCursor safeInsertSpacesBetweenReferenceMarks(XTextRange position, int n) { // Start with an empty cursor at position.getStart(); @@ -129,19 +127,14 @@ static NamedRangeReferenceMark create(XTextDocument doc, throws CreationException { - createReprInDocument(doc, - refMarkName, - position, - insertSpaceAfter, - withoutBrackets); + createReprInDocument(doc, refMarkName, position, insertSpaceAfter, withoutBrackets); return new NamedRangeReferenceMark(refMarkName); } /** * @return Optional.empty if there is no corresponding range. */ - static Optional getFromDocument(XTextDocument doc, - String refMarkName) + static Optional getFromDocument(XTextDocument doc, String refMarkName) throws NoDocumentException, WrappedTargetException { @@ -160,7 +153,7 @@ public void nrRemoveFromDocument(XTextDocument doc) WrappedTargetException, NoDocumentException, NoSuchElementException { - UnoReferenceMark.remove(doc, this.nrGetRangeName()); + UnoReferenceMark.removeIfExists(doc, this.nrGetRangeName()); } @Override @@ -185,11 +178,11 @@ public Optional nrGetMarkRange(XTextDocument doc) } /** - * Cursor for the reference marks as is, not prepared for filling, - * but does not need nrCleanFillCursor either. + * Cursor for the reference marks as is, not prepared for filling, but does not need + * nrCleanFillCursor either. * * @return Optional.empty() if reference mark is missing from the document, - * otherwise an XTextCursor for getMarkRange + * otherwise an XTextCursor for getMarkRange * * See: getRawCursorForCitationGroup */ @@ -230,11 +223,11 @@ public XTextCursor nrGetFillCursor(XTextDocument doc) String name = this.nrGetRangeName(); - final boolean debugThisFun = false; final String left = NamedRangeReferenceMark.REFERENCE_MARK_LEFT_BRACKET; final String right = NamedRangeReferenceMark.REFERENCE_MARK_RIGHT_BRACKET; final short leftLength = (short) left.length(); final short rightLength = (short) right.length(); + final boolean debugThisFun = false; XTextCursor full = null; String fullText = null; @@ -242,86 +235,62 @@ public XTextCursor nrGetFillCursor(XTextDocument doc) Optional markAsTextContent = UnoReferenceMark.getAsTextContent(doc, name); if (markAsTextContent.isEmpty()) { - String msg = String.format("nrGetFillCursor:" - + " markAsTextContent(%s).isEmpty (attempt %d)", - name, - i); - throw new RuntimeException(msg); + String msg = String.format("nrGetFillCursor: markAsTextContent(%s).isEmpty (attempt %d)", name, i); + throw new IllegalStateException(msg); } full = UnoCursor.getTextCursorOfTextContentAnchor(markAsTextContent.get()).orElse(null); if (full == null) { String msg = String.format("nrGetFillCursor: full == null (attempt %d)", i); - throw new RuntimeException(msg); + throw new IllegalStateException(msg); } fullText = full.getString(); - if (debugThisFun) { - System.out.printf("nrGetFillCursor: fulltext = '%s'%n", fullText); - } + LOGGER.debug("nrGetFillCursor: fulltext = '{}'", fullText); if (fullText.length() >= 2) { - if (debugThisFun) { - System.out.printf("nrGetFillCursor: (attempt: %d) fulltext.length() >= 2," - + " break loop%n", i); - } + LOGGER.debug("nrGetFillCursor: (attempt: {}) fulltext.length() >= 2, break loop%n", i); break; } else { // (fullText.length() < 2) if (i == 2) { - String msg = String.format("nrGetFillCursor:" - + " (fullText.length() < 2) (attempt %d)", - i); - throw new RuntimeException(msg); + String msg = String.format("nrGetFillCursor: (fullText.length() < 2) (attempt %d)", i); + throw new IllegalStateException(msg); } // too short, recreate - if (true || debugThisFun) { - System.out.println("nrGetFillCursor: too short, recreate"); - } + LOGGER.warn("nrGetFillCursor: too short, recreate"); + full.setString(""); - try { - UnoReferenceMark.remove(doc, name); - } catch (NoSuchElementException ex) { - String msg = String.format("nrGetFillCursor got NoSuchElementException" - + " for '%s'", - name); - LOGGER.warn(msg); - } - createReprInDocument(doc, - name, - full, - false, /* insertSpaceAfter */ - false /* withoutBrackets */); + UnoReferenceMark.removeIfExists(doc, name); + + final boolean insertSpaceAfter = false; + final boolean withoutBrackets = false; + createReprInDocument(doc, name, full, insertSpaceAfter, withoutBrackets); } } if (full == null) { - throw new RuntimeException("nrGetFillCursorFor: full == null (after loop)"); + throw new IllegalStateException("nrGetFillCursorFor: full == null (after loop)"); } if (fullText == null) { - throw new RuntimeException("nrGetFillCursor: fullText == null (after loop)"); + throw new IllegalStateException("nrGetFillCursor: fullText == null (after loop)"); } fullText = full.getString(); if (fullText.length() < 2) { - throw new RuntimeException("nrGetFillCursor: fullText.length() < 2 (after loop)'%n"); + throw new IllegalStateException("nrGetFillCursor: fullText.length() < 2 (after loop)'%n"); } XTextCursor beta = full.getText().createTextCursorByRange(full); beta.collapseToStart(); beta.goRight((short) 1, false); beta.goRight((short) (fullText.length() - 2), true); - if (debugThisFun) { - System.out.printf("nrGetFillCursor: beta(1) covers '%s'%n", beta.getString()); - } + LOGGER.debug("nrGetFillCursor: beta(1) covers '{}'", beta.getString()); + if (fullText.startsWith(left) && fullText.endsWith(right)) { beta.setString(""); } else { - if (debugThisFun) { - String msg = String.format("nrGetFillCursor: recreating brackets for '%s'", fullText); - // LOGGER.warn(msg); - System.out.println(msg); - } + LOGGER.debug("nrGetFillCursor: recreating brackets for '{}'", fullText); // we have at least two characters inside XTextCursor alpha = full.getText().createTextCursorByRange(full); @@ -336,47 +305,40 @@ public XTextCursor nrGetFillCursor(XTextDocument doc) String paddingy = "y"; String paddingz = "z"; beta.setString(paddingx + left + paddingy + right + paddingz); - if (debugThisFun) { - System.out.printf("nrGetFillCursor: beta(2) covers '%s'%n", beta.getString()); - } + LOGGER.debug("nrGetFillCursor: beta(2) covers '{}'", beta.getString()); + // move beta to before the right bracket beta.collapseToEnd(); beta.goLeft((short) (rightLength + 1), false); // remove middle padding beta.goLeft((short) 1, true); - if (debugThisFun) { - System.out.printf("nrGetFillCursor: beta(3) covers '%s'%n", beta.getString()); - } + LOGGER.debug("nrGetFillCursor: beta(3) covers '{}'", beta.getString()); + // only drop paddingy later: beta.setString(""); // drop the initial character and paddingx alpha.collapseToStart(); alpha.goRight((short) (1 + 1), true); - if (debugThisFun) { - System.out.printf("nrGetFillCursor: alpha(4) covers '%s'%n", alpha.getString()); - } + LOGGER.debug("nrGetFillCursor: alpha(4) covers '{}'", alpha.getString()); + alpha.setString(""); // drop the last character and paddingz omega.collapseToEnd(); omega.goLeft((short) (1 + 1), true); - if (debugThisFun) { - System.out.printf("nrGetFillCursor: omega(5) covers '%s'%n", omega.getString()); - } + LOGGER.debug("nrGetFillCursor: omega(5) covers '{}'", omega.getString()); + omega.setString(""); // drop paddingy now - if (debugThisFun) { - System.out.printf("nrGetFillCursor: beta(6) covers '%s'%n", beta.getString()); - } + LOGGER.debug("nrGetFillCursor: beta(6) covers '{}'", beta.getString()); + beta.setString(""); // should be OK now. if (debugThisFun) { alpha.goRight(leftLength, true); - System.out.printf("nrGetFillCursor: alpha(7) covers '%s', should be '%s'%n", - alpha.getString(), left); + LOGGER.debug("nrGetFillCursor: alpha(7) covers '{}', should be '{}'", alpha.getString(), left); omega.goLeft(rightLength, true); - System.out.printf("nrGetFillCursor: omega(8) covers '%s', should be '%s'%n", - omega.getString(), right); + LOGGER.debug("nrGetFillCursor: omega(8) covers '%s', should be '%s'%n", omega.getString(), right); } } @@ -385,7 +347,7 @@ public XTextCursor nrGetFillCursor(XTextDocument doc) } /* - * Throw RuntimeException if the brackets are not there. + * Throw IllegalStateException if the brackets are not there. */ public static void checkFillCursor(XTextCursor cursor) { final String left = REFERENCE_MARK_LEFT_BRACKET; @@ -403,10 +365,9 @@ public static void checkFillCursor(XTextCursor cursor) { alpha.goLeft(leftLength, true); if (!left.equals(alpha.getString())) { String msg = String.format("checkFillCursor:" - + " ('%s') is not prefixed with" - + " REFERENCE_MARK_LEFT_BRACKET, has '%s'", + + " ('%s') is not prefixed with REFERENCE_MARK_LEFT_BRACKET, has '%s'", cursor.getString(), alpha.getString()); - throw new RuntimeException(msg); + throw new IllegalStateException(msg); } } @@ -414,17 +375,16 @@ public static void checkFillCursor(XTextCursor cursor) { omega.goRight(rightLength, true); if (!right.equals(omega.getString())) { String msg = String.format("checkFillCursor:" - + " ('%s') is not followed by" - + " REFERENCE_MARK_RIGHT_BRACKET, has '%s'", + + " ('%s') is not followed by REFERENCE_MARK_RIGHT_BRACKET, has '%s'", cursor.getString(), omega.getString()); - throw new RuntimeException(msg); + throw new IllegalStateException(msg); } } } /** - * Remove brackets, but if the result would become empty, leave - * them; if the result would be a single characer, leave the left bracket. + * Remove brackets, but if the result would become empty, leave them; if the result would be a + * single characer, leave the left bracket. * * See: cleanFillCursorForCitationGroup */ @@ -435,13 +395,11 @@ public void nrCleanFillCursor(XTextDocument doc) WrappedTargetException, CreationException { - // alwaysRemoveBrackets : full compatibility with JabRef 5.2: - // brackets are temporary, only exist between nrGetFillCursor - // and nrCleanFillCursor. + // alwaysRemoveBrackets : full compatibility with JabRef 5.2: brackets are temporary, only + // exist between nrGetFillCursor and nrCleanFillCursor. final boolean alwaysRemoveBrackets = false; - // removeBracketsFromEmpty is intended to force removal if we - // are working on an "Empty citation" (INVISIBLE_CIT). + // removeBracketsFromEmpty is intended to force removal if we are working on an "Empty citation" (INVISIBLE_CIT). final boolean removeBracketsFromEmpty = false; final String left = REFERENCE_MARK_LEFT_BRACKET; @@ -451,28 +409,24 @@ public void nrCleanFillCursor(XTextDocument doc) String name = this.nrGetRangeName(); - XTextCursor full = this.nrGetRawCursor(doc).orElseThrow(RuntimeException::new); + XTextCursor full = this.nrGetRawCursor(doc).orElseThrow(IllegalStateException::new); final String fullText = full.getString(); final int fullTextLength = fullText.length(); if (!fullText.startsWith(left)) { - String msg = String.format("nrCleanFillCursor:" - + " (%s) does not start with REFERENCE_MARK_LEFT_BRACKET", - name); - throw new RuntimeException(msg); + String msg = String.format("nrCleanFillCursor: (%s) does not start with REFERENCE_MARK_LEFT_BRACKET", name); + throw new IllegalStateException(msg); } if (!fullText.endsWith(right)) { - String msg = String.format("nrCleanFillCursor:" - + " (%s) does not end with REFERENCE_MARK_RIGHT_BRACKET", - name); - throw new RuntimeException(msg); + String msg = String.format("nrCleanFillCursor: (%s) does not end with REFERENCE_MARK_RIGHT_BRACKET", name); + throw new IllegalStateException(msg); } final int contentLength = (fullTextLength - (leftLength + rightLength)); if (contentLength < 0) { String msg = String.format("nrCleanFillCursor: length(%s) < 0", name); - throw new RuntimeException(msg); + throw new IllegalStateException(msg); } boolean removeRight = ((contentLength >= 1) diff --git a/src/main/java/org/jabref/logic/openoffice/frontend/OOFrontend.java b/src/main/java/org/jabref/logic/openoffice/frontend/OOFrontend.java index fd0523a4bf8..6a4f741b793 100644 --- a/src/main/java/org/jabref/logic/openoffice/frontend/OOFrontend.java +++ b/src/main/java/org/jabref/logic/openoffice/frontend/OOFrontend.java @@ -57,9 +57,7 @@ public OOFrontend(XTextDocument doc) NoDocumentException, WrappedTargetException { - // TODO: dataModel should come from looking at the - // document and preferences. - // + // TODO: dataModel should come from looking at the document and preferences. this.backend = new Backend52(); // Get the citationGroupNames @@ -98,24 +96,19 @@ public Optional healthReport(XTextDocument doc) } /** - * Creates a list of {@code RangeSortable} values for - * our {@code CitationGroup} values. Originally designed to be - * passed to {@code visualSort}. + * Creates a list of {@code RangeSortable} values for our {@code CitationGroup} + * values. Originally designed to be passed to {@code visualSort}. * - * The elements of the returned list are actually of type - * {@code RangeSortEntry}. + * The elements of the returned list are actually of type {@code RangeSortEntry}. * - * The result is sorted within {@code XTextRange.getText()} - * partitions of the citation groups according to their {@code XTextRange} - * (before mapping to footnote marks). + * The result is sorted within {@code XTextRange.getText()} partitions of the citation groups + * according to their {@code XTextRange} (before mapping to footnote marks). * - * In the result, RangeSortable.getIndexInPosition() contains - * unique indexes within the original partition (not after - * mapFootnotesToFootnoteMarks). + * In the result, RangeSortable.getIndexInPosition() contains unique indexes within the original + * partition (not after mapFootnotesToFootnoteMarks). * - * @param mapFootnotesToFootnoteMarks If true, replace ranges in - * footnotes with the range of the corresponding footnote - * mark. This is used for numbering the citations. + * @param mapFootnotesToFootnoteMarks If true, replace ranges in footnotes with the range of the + * corresponding footnote mark. This is used for numbering the citations. * */ private List> @@ -128,27 +121,24 @@ public Optional healthReport(XTextDocument doc) for (CitationGroup cg : citationGroups.getCitationGroupsUnordered()) { XTextRange range = (this .getMarkRange(doc, cg) - .orElseThrow(RuntimeException::new)); + .orElseThrow(IllegalStateException::new)); sortables.add(new RangeSortEntry<>(range, 0, cg)); } /* * At this point we are almost ready to return sortables. * - * But we may want to number citations in a footnote - * as if it appeared where the footnote mark is. + * But we may want to number citations in a footnote as if it appeared where the footnote + * mark is. * - * The following code replaces ranges within footnotes with - * the range for the corresponding footnote mark. + * The following code replaces ranges within footnotes with the range for the corresponding + * footnote mark. * - * This brings further ambiguity if we have multiple - * citation groups within the same footnote: for the comparison - * they become indistinguishable. Numbering between them is - * not controlled. Also combineCiteMarkers will see them in - * the wrong order (if we use this comparison), and will not - * be able to merge. To avoid these, we sort textually within - * each .getText() partition and add indexInPosition - * accordingly. + * This brings further ambiguity if we have multiple citation groups within the same + * footnote: for the comparison they become indistinguishable. Numbering between them is + * not controlled. Also combineCiteMarkers will see them in the wrong order (if we use this + * comparison), and will not be able to merge. To avoid these, we sort textually within + * each .getText() partition and add indexInPosition accordingly. * */ @@ -174,53 +164,45 @@ public Optional healthReport(XTextDocument doc) } } result.add(sortable); - } + } } return result.stream().map(e -> e).collect(Collectors.toList()); } /** - * @param mapFootnotesToFootnoteMarks If true, sort reference - * marks in footnotes as if they appeared at the - * corresponding footnote mark. + * @param mapFootnotesToFootnoteMarks If true, sort reference marks in footnotes as if they + * appeared at the corresponding footnote mark. * * @return citation groups sorted by their visual positions. * - * Limitation: for two column layout visual (top-down, - * left-right) order does not match the expected (textual) - * order. + * Limitation: for two column layout visual (top-down, left-right) order does not match the + * expected (textual) order. * */ - private List - getVisuallySortedCitationGroups(XTextDocument doc, - boolean mapFootnotesToFootnoteMarks, - FunctionalTextViewCursor fcursor) + private List getVisuallySortedCitationGroups(XTextDocument doc, + boolean mapFootnotesToFootnoteMarks, + FunctionalTextViewCursor fcursor) throws WrappedTargetException, NoDocumentException, JabRefException { - List> sortables = - createVisualSortInput(doc, mapFootnotesToFootnoteMarks); + List> sortables = createVisualSortInput(doc, mapFootnotesToFootnoteMarks); - List> sorted = - RangeSortVisual.visualSort(sortables, - doc, - fcursor); + List> sorted = RangeSortVisual.visualSort(sortables, doc, fcursor); - List result = - (sorted.stream().map(e -> e.getContent()).collect(Collectors.toList())); + List result = (sorted.stream() + .map(RangeSortable::getContent) + .collect(Collectors.toList())); return result; } /** - * Return citation groups in visual order within (but not across) - * XText partitions. + * Return citation groups in visual order within (but not across) XText partitions. * - * This is (1) sufficient for combineCiteMarkers which looks for - * consecutive XTextRanges within each XText, (2) not confused by - * multicolumn layout or multipage display. + * This is (1) sufficient for combineCiteMarkers which looks for consecutive XTextRanges within + * each XText, (2) not confused by multicolumn layout or multipage display. */ public List getCitationGroupsSortedWithinPartitions(XTextDocument doc, boolean mapFootnotesToFootnoteMarks) @@ -236,19 +218,17 @@ public Optional healthReport(XTextDocument doc) } /** - * Create a citation group for the given citation keys, at the - * end of position. + * Create a citation group for the given citation keys, at the end of position. * - * On return {@code position} is collapsed, and is after the - * inserted space, or at the end of the reference mark. + * On return {@code position} is collapsed, and is after the inserted space, or at the end of + * the reference mark. * * @param citationKeys In storage order * @param pageInfos In storage order * @param citationType * @param position Collapsed to its end. - * @param insertSpaceAfter If true, we insert a space after the mark, that - * carries on format of characters from - * the original position. + * @param insertSpaceAfter If true, we insert a space after the mark, that carries on format of + * characters from the original position. */ public CitationGroup createCitationGroup(XTextDocument doc, List citationKeys, @@ -267,7 +247,7 @@ public CitationGroup createCitationGroup(XTextDocument doc, Objects.requireNonNull(pageInfos); if (pageInfos.size() != citationKeys.size()) { - throw new RuntimeException("pageInfos.size != citationKeys.size"); + throw new IllegalArgumentException("pageInfos.size != citationKeys.size"); } CitationGroup cg = backend.createCitationGroup(doc, citationKeys, @@ -357,8 +337,8 @@ public List> citationRanges(XTextDocument new ArrayList<>(citationGroups.numberOfCitationGroups()); for (CitationGroup cg : citationGroups.getCitationGroupsUnordered()) { - XTextRange range = this.getMarkRange(doc, cg).orElseThrow(RuntimeException::new); - String description = cg.cgid.citationGroupIdAsString(); // cg.cgRangeStorage.nrGetRangeName(); + XTextRange range = this.getMarkRange(doc, cg).orElseThrow(IllegalStateException::new); + String description = cg.cgid.citationGroupIdAsString(); result.add(new RangeForOverlapCheck<>(range, cg.cgid, RangeForOverlapCheck.REFERENCE_MARK_KIND, @@ -404,56 +384,88 @@ public List> viewCursorRanges(XTextDocumen } /** - * @return A range for each footnote mark where the footnote - * contains at least one citation group. + * @return A range for each footnote mark where the footnote contains at least one citation group. * - * Purpose: We do not want markers of footnotes containing - * reference marks to overlap with reference - * marks. Overwriting these footnote marks might kill our - * reference marks in the footnote. + * Purpose: We do not want markers of footnotes containing reference marks to overlap with + * reference marks. Overwriting these footnote marks might kill our reference marks in the + * footnote. * - * Note: Here we directly communicate to the document, not - * through the backend. This is because mapping ranges to - * footnote marks does not depend on how do we mark or - * structure those ranges. + * Note: Here we directly communicate to the document, not through the backend. This is because + * mapping ranges to footnote marks does not depend on how do we mark or structure those + * ranges. */ public List> - footnoteMarkRanges(XTextDocument doc, - List> citationRanges) + footnoteMarkRanges(XTextDocument doc, List> citationRanges) throws NoDocumentException, WrappedTargetException { - // Avoid inserting the same mark twice. - // Could use RangeSet if we had that. - RangeSet seen = new RangeSet(); + if (false) { + // Avoid inserting the same mark twice. + RangeSet seen = new RangeSet(); - List> result = new ArrayList<>(); + List> result = new ArrayList<>(); + + for (RangeForOverlapCheck citationRange : citationRanges) { - for (RangeForOverlapCheck citationRange : citationRanges) { + Optional footnoteMarkRange = UnoTextRange.getFootnoteMarkRange(citationRange.range); - Optional footnoteMarkRange = - UnoTextRange.getFootnoteMarkRange(citationRange.range); + if (footnoteMarkRange.isEmpty()) { + // not in footnote + continue; + } - if (footnoteMarkRange.isEmpty()) { - // not in footnote - continue; + boolean seenContains = seen.contains(footnoteMarkRange.get()); + if (!seenContains) { + seen.add(footnoteMarkRange.get()); + result.add(new RangeForOverlapCheck<>(footnoteMarkRange.get(), + citationRange.idWithinKind, + RangeForOverlapCheck.FOOTNOTE_MARK_KIND, + "FootnoteMark for " + citationRange.format())); + } } + return result; + } else { + + // RangeSet.add involves a few comparisons anf getText, which is probably costly. + // + // (On the other hand we only insert ranges of footnotes, which probably limits the sizes + // of its partitions) + // + // We can avoid using RangeSet by partitioning only and using a single range from + // each partition to get at the corresponding footnotemark range. + + List> result = new ArrayList<>(); + RangeSort.RangePartitions> partitions = + RangeSort.partitionRanges(citationRanges); + + // Now it is sufficient to check a single entry from each partition. + // Each partition corresponds to an XText, and each footnote has a single XText. + // (This latter ignores the possibility of XTextContents inserted into footnotes.) + // Also: different footnotes cannot share a footnotemark range, we are not creating duplicates. + for (List> partition : partitions.getPartitions()) { + if (partition.isEmpty()) { + continue; + } + RangeForOverlapCheck citationRange = partition.get(0); + + Optional footnoteMarkRange = UnoTextRange.getFootnoteMarkRange(citationRange.range); + + if (footnoteMarkRange.isEmpty()) { + // not in footnote + continue; + } - boolean seenContains = seen.contains(footnoteMarkRange.get()); - if (!seenContains) { - seen.add(footnoteMarkRange.get()); result.add(new RangeForOverlapCheck<>(footnoteMarkRange.get(), citationRange.idWithinKind, RangeForOverlapCheck.FOOTNOTE_MARK_KIND, "FootnoteMark for " + citationRange.format())); } + return result; } - return result; } - static String - rangeOverlapsToMessage(List>> overlaps) { + static String rangeOverlapsToMessage(List>> overlaps) { if (overlaps.size() == 0) { return "(*no overlaps*)"; @@ -481,6 +493,7 @@ public List> viewCursorRanges(XTextDocumen * Check for any overlap between userRanges and protected ranges. * * Assume userRanges is small (usually 1 elements for checking the cursor) + * * Returns on first problem found. */ public OOVoidResult @@ -500,11 +513,10 @@ public List> viewCursorRanges(XTextDocumen ranges.addAll(footnoteMarkRanges(doc, citationRanges)); List>> overlaps = - RangeOverlapBetween.findFirst( - doc, - userRanges, - ranges, - requireSeparation); + RangeOverlapBetween.findFirst(doc, + userRanges, + ranges, + requireSeparation); if (overlaps.size() == 0) { return OOVoidResult.ok(); @@ -518,11 +530,10 @@ public List> viewCursorRanges(XTextDocumen * @param reportAtMost Limit number of overlaps reported (0 for no limit) * */ - public OOVoidResult - checkRangeOverlaps(XTextDocument doc, - List> userRanges, - boolean requireSeparation, - int reportAtMost) + public OOVoidResult checkRangeOverlaps(XTextDocument doc, + List> userRanges, + boolean requireSeparation, + int reportAtMost) throws NoDocumentException, WrappedTargetException { @@ -550,33 +561,29 @@ public List> viewCursorRanges(XTextDocumen * * Called from: ManageCitationsDialogViewModel constructor. * - * @return A list with entries corresponding to citations in the - * text, in arbitrary order (same order as from - * getJabRefReferenceMarkNames). + * @return A list with entries corresponding to citations in the text, in arbitrary order (same + * order as from getJabRefReferenceMarkNames). * - * Note: visual or alphabetic order could be more - * manageable for the user. We could provide these - * here, but switching between them needs change on - * GUI (adding a toggle or selector). + * Note: visual or alphabetic order could be more manageable for the user. We + * could provide these here, but switching between them needs change on GUI + * (adding a toggle or selector). * - * Note: CitationEntry implements Comparable, where - * compareTo() and equals() are based on refMarkName. - * The order used in the "Manage citations" dialog + * Note: CitationEntry implements Comparable, where compareTo() and equals() are + * based on refMarkName. The order used in the "Manage citations" dialog * does not seem to use that. * - * The 1st is labeled "Citation" (show citation in bold, - * and some context around it). + * The 1st is labeled "Citation" (show citation in bold, and some context + * around it). * - * The columns can be sorted by clicking on the column title. - * For the "Citation" column, the sorting is based on the content, - * (the context before the citation), not on the citation itself. + * The columns can be sorted by clicking on the column title. For the + * "Citation" column, the sorting is based on the content, (the context + * before the citation), not on the citation itself. * - * In the "Extra information ..." column some visual indication - * of the editable part could be helpful. + * In the "Extra information ..." column some visual indication of the + * editable part could be helpful. * - * Wish: selecting an entry (or a button in the line) in - * the GUI could move the cursor in the document to - * the entry. + * Wish: selecting an entry (or a button in the line) in the GUI could move the cursor + * in the document to the entry. */ public List getCitationEntries(XTextDocument doc) throws @@ -608,8 +615,7 @@ public void imposeGlobalOrder(XTextDocument doc, FunctionalTextViewCursor fcurso boolean mapFootnotesToFootnoteMarks = true; List sortedCitationGroups = getVisuallySortedCitationGroups(doc, mapFootnotesToFootnoteMarks, fcursor); - List sortedCitationGroupIds = - OOListUtil.map(sortedCitationGroups, cg -> cg.cgid); + List sortedCitationGroupIds = OOListUtil.map(sortedCitationGroups, cg -> cg.cgid); citationGroups.setGlobalOrder(sortedCitationGroupIds); } } diff --git a/src/main/java/org/jabref/logic/openoffice/frontend/UpdateBibliography.java b/src/main/java/org/jabref/logic/openoffice/frontend/UpdateBibliography.java index ef1a4c42dc5..2d0de320ca6 100644 --- a/src/main/java/org/jabref/logic/openoffice/frontend/UpdateBibliography.java +++ b/src/main/java/org/jabref/logic/openoffice/frontend/UpdateBibliography.java @@ -47,7 +47,6 @@ public static void rebuildBibTextSection(XTextDocument doc, throws NoSuchElementException, WrappedTargetException, - IllegalArgumentException, CreationException, PropertyVetoException, UnknownPropertyException, @@ -69,8 +68,7 @@ public static void rebuildBibTextSection(XTextDocument doc, */ private static void createBibTextSection2(XTextDocument doc) throws - CreationException, - IllegalArgumentException { + CreationException { // Always creating at the end of the document. // Alternatively, we could receive a cursor. @@ -89,7 +87,6 @@ private static void createBibTextSection2(XTextDocument doc) private static void clearBibTextSectionContent2(XTextDocument doc) throws CreationException, - IllegalArgumentException, NoDocumentException, WrappedTargetException { @@ -124,7 +121,7 @@ private static void populateBibTextSection(XTextDocument doc, UnknownPropertyException, WrappedTargetException { - XTextRange sectionRange = getBibliographyRange(doc).orElseThrow(RuntimeException::new); + XTextRange sectionRange = getBibliographyRange(doc).orElseThrow(IllegalStateException::new); XTextCursor cursor = doc.getText().createTextCursorByRange(sectionRange); @@ -138,13 +135,13 @@ private static void populateBibTextSection(XTextDocument doc, cursor.collapseToEnd(); // remove the inital empty paragraph from the section. - sectionRange = getBibliographyRange(doc).orElseThrow(RuntimeException::new); + sectionRange = getBibliographyRange(doc).orElseThrow(IllegalStateException::new); XTextCursor initialParagraph = doc.getText().createTextCursorByRange(sectionRange); initialParagraph.collapseToStart(); initialParagraph.goRight((short) 1, true); initialParagraph.setString(""); - UnoBookmark.remove(doc, BIB_SECTION_END_NAME); + UnoBookmark.removeIfExists(doc, BIB_SECTION_END_NAME); UnoBookmark.create(doc, BIB_SECTION_END_NAME, cursor, true); cursor.collapseToEnd(); diff --git a/src/main/java/org/jabref/logic/openoffice/frontend/UpdateCitationMarkers.java b/src/main/java/org/jabref/logic/openoffice/frontend/UpdateCitationMarkers.java index 7d864ab1063..b3f90d380a7 100644 --- a/src/main/java/org/jabref/logic/openoffice/frontend/UpdateCitationMarkers.java +++ b/src/main/java/org/jabref/logic/openoffice/frontend/UpdateCitationMarkers.java @@ -102,7 +102,7 @@ public static void fillCitationMarkInCursor(XTextDocument doc, OOText citationText2 = style.decorateCitationMarker(citationText); // inject a ZERO_WIDTH_SPACE to hold the initial character format final String ZERO_WIDTH_SPACE = "\u200b"; - citationText2 = OOText.fromString(ZERO_WIDTH_SPACE + citationText2.asString()); + citationText2 = OOText.fromString(ZERO_WIDTH_SPACE + citationText2.toString()); OOTextIntoOO.write(doc, cursor, citationText2); } else { cursor.setString(""); @@ -150,7 +150,7 @@ public static void createAndFillCitationGroup(OOFrontend fr, Objects.requireNonNull(pageInfos); if (pageInfos.size() != citationKeys.size()) { - throw new RuntimeException("pageInfos.size != citationKeys.size"); + throw new IllegalArgumentException("pageInfos.size != citationKeys.size"); } CitationGroup cg = fr.createCitationGroup(doc, citationKeys, diff --git a/src/main/java/org/jabref/logic/openoffice/style/OOBibStyle.java b/src/main/java/org/jabref/logic/openoffice/style/OOBibStyle.java index 113cef246e2..feccb727f53 100644 --- a/src/main/java/org/jabref/logic/openoffice/style/OOBibStyle.java +++ b/src/main/java/org/jabref/logic/openoffice/style/OOBibStyle.java @@ -517,20 +517,6 @@ public String getNumCitationMarker(List number, int minGroupingCount, b /* end_old */ /* begin_old */ - /** - * Format the marker for the in-text citation according to this BIB style. Uniquefier letters are added as - * provided by the uniquefiers argument. If successive entries within the citation are uniquefied from each other, - * this method will perform a grouping of these entries. - * - * @param entries The list of JabRef BibEntry providing the data. - * @param database A map of BibEntry-BibDatabase pairs. - * @param inParenthesis Signals whether a parenthesized citation or an in-text citation is wanted. - * @param uniquefiers Strings to add behind the year for each entry in case it's needed to separate similar - * entries. - * @param unlimAuthors Boolean for each entry. If true, we should not use "et al" formatting regardless - * of the number of authors. Can be null to indicate that no entries should have unlimited names. - * @return The formatted citation. - */ public String getCitationMarker(List entries, Map database, boolean inParenthesis, String[] uniquefiers, int[] unlimAuthors) { // Look for groups of uniquefied entries that should be combined in the output. @@ -1115,7 +1101,7 @@ public OOText getNumCitationMarkerForBibliography(CitationMarkerNumericBibEntry // not null, check size if (pageInfos.size() != nCitations) { - throw new RuntimeException("normalizePageInfos: pageInfos.size() != nCitations"); + throw new IllegalArgumentException("normalizePageInfos: pageInfos.size() != nCitations"); } // not null, normalize elementwise diff --git a/src/main/java/org/jabref/logic/openoffice/style/OOBibStyleGetCitationMarker.java b/src/main/java/org/jabref/logic/openoffice/style/OOBibStyleGetCitationMarker.java index ee2bba27e7e..e501cd4aaaf 100644 --- a/src/main/java/org/jabref/logic/openoffice/style/OOBibStyleGetCitationMarker.java +++ b/src/main/java/org/jabref/logic/openoffice/style/OOBibStyleGetCitationMarker.java @@ -65,10 +65,10 @@ private static String markupAuthorName(OOBibStyle style, String name) { * to mark their omission. * Set to -1 to write out all authors. * - * maxAuthors=0 is pointless, now throws RuntimeException + * maxAuthors=0 is pointless, now throws IllegalArgumentException * (Earlier it behaved as maxAuthors=1) * - * maxAuthors less than -1 : throw RuntimeException + * maxAuthors less than -1 : throw IllegalArgumentException * * @param andString For "A, B[ and ]C" * @@ -129,10 +129,10 @@ private static String formatAuthorList(OOBibStyle style, // To reduce ambiguity, throw on unexpected values of maxAuthors if (maxAuthors == 0 && nAuthors != 0) { - throw new RuntimeException("maxAuthors = 0 in formatAuthorList"); + throw new IllegalArgumentException("maxAuthors = 0 in formatAuthorList"); } if (maxAuthors < -1) { - throw new RuntimeException("maxAuthors < -1 in formatAuthorList"); + throw new IllegalArgumentException("maxAuthors < -1 in formatAuthorList"); } // emitAllAuthors == false means use "et al." @@ -724,8 +724,8 @@ static OOText getNormalizedCitationMarker(OOBibStyle style, if (uniqueLetterDoesNotMakeUnique && nonUniqueCitationMarkerHandling.equals(NonUniqueCitationMarker.THROWS)) { - throw new RuntimeException("different citation keys," - + " but same normalizedMarker and uniqueLetter"); + throw new IllegalArgumentException("different citation keys," + + " but same normalizedMarker and uniqueLetter"); } final boolean pageInfoInhibitsJoin = (bothPageInfosAreEmpty diff --git a/src/main/java/org/jabref/logic/openoffice/style/OOBibStyleGetNumCitationMarker.java b/src/main/java/org/jabref/logic/openoffice/style/OOBibStyleGetNumCitationMarker.java index 376c4478421..8d8422fbc51 100644 --- a/src/main/java/org/jabref/logic/openoffice/style/OOBibStyleGetNumCitationMarker.java +++ b/src/main/java/org/jabref/logic/openoffice/style/OOBibStyleGetNumCitationMarker.java @@ -91,7 +91,7 @@ private static void emitBlock(List block, final int blockSize = block.size(); if (blockSize == 0) { - throw new RuntimeException("The block is empty"); + throw new IllegalArgumentException("The block is empty"); } if (blockSize == 1) { @@ -117,17 +117,16 @@ private static void emitBlock(List block, */ if (block.stream().anyMatch(x -> x.getPageInfo().isPresent())) { - throw new RuntimeException("Found pageInfo in a block with more than one elements"); + throw new IllegalArgumentException("Found pageInfo in a block with more than one elements"); } if (block.stream().anyMatch(x -> x.getNumber().isEmpty())) { - throw new RuntimeException("Found unresolved entry" - + " in a block with more than one elements"); + throw new IllegalArgumentException("Found unresolved entry in a block with more than one elements"); } for (int j = 1; j < blockSize; j++) { if ((block.get(j).getNumber().get() - block.get(j - 1).getNumber().get()) != 1) { - throw new RuntimeException("Numbers are not consecutive"); + throw new IllegalArgumentException("Numbers are not consecutive"); } } @@ -139,7 +138,7 @@ private static void emitBlock(List block, int first = block.get(0).getNumber().get(); int last = block.get(blockSize - 1).getNumber().get(); if (last != (first + blockSize - 1)) { - throw new RuntimeException("blockSize and length of num range differ"); + throw new IllegalArgumentException("blockSize and length of num range differ"); } // Emit: "first-last" @@ -221,7 +220,7 @@ public static OOText getNumCitationMarker2(OOBibStyle style, final CitationMarkerNumericEntry current = sorted.get(i); if (current.getNumber().isPresent() && current.getNumber().get() < 0) { - throw new RuntimeException("getNumCitationMarker2: found negative value"); + throw new IllegalArgumentException("getNumCitationMarker2: found negative number"); } if (currentBlock.size() == 0) { @@ -259,7 +258,7 @@ public static OOText getNumCitationMarker2(OOBibStyle style, } if (nextBlock.size() != 0) { - throw new RuntimeException("impossible: (nextBlock.size() != 0) after loop"); + throw new IllegalStateException("impossible: (nextBlock.size() != 0) after loop"); } if (currentBlock.size() > 0) { diff --git a/src/main/java/org/jabref/logic/openoffice/style/OOFormatBibliography.java b/src/main/java/org/jabref/logic/openoffice/style/OOFormatBibliography.java index c9ae527dcec..6c22c8abb09 100644 --- a/src/main/java/org/jabref/logic/openoffice/style/OOFormatBibliography.java +++ b/src/main/java/org/jabref/logic/openoffice/style/OOFormatBibliography.java @@ -36,7 +36,7 @@ public static OOText formatBibliography(CitationGroups cgs, OOText title = style.getFormattedBibliographyTitle(); OOText body = formatBibliographyBody(cgs, bibliography, style, alwaysAddCitedOnPages); - return OOText.fromString(title.asString() + body.asString()); + return OOText.fromString(title.toString() + body.toString()); } /** @@ -51,7 +51,7 @@ public static OOText formatBibliographyBody(CitationGroups cgs, for (CitedKey ck : bibliography.values()) { OOText entryText = formatBibliographyEntry(cgs, ck, style, alwaysAddCitedOnPages); - stringBuilder.append(entryText.asString()); + stringBuilder.append(entryText.toString()); } return OOText.fromString(stringBuilder.toString()); @@ -68,18 +68,18 @@ public static OOText formatBibliographyEntry(CitationGroups cgs, // insert marker "[1]" if (style.isNumberEntries()) { - sb.append(style.getNumCitationMarkerForBibliography(ck).asString()); + sb.append(style.getNumCitationMarkerForBibliography(ck).toString()); } else { // !style.isNumberEntries() : emit no prefix // Note: We might want [citationKey] prefix for style.isCitationKeyCiteMarkers(); } // Add entry body - sb.append(formatBibliographyEntryBody(ck, style).asString()); + sb.append(formatBibliographyEntryBody(ck, style).toString()); // Add "Cited on pages" if (ck.getLookupResult().isEmpty() || alwaysAddCitedOnPages) { - sb.append(formatCitedOnPages(cgs, ck).asString()); + sb.append(formatCitedOnPages(cgs, ck).toString()); } // Add paragraph @@ -169,14 +169,17 @@ private static OOText formatCitedOnPages(CitationGroups cgs, CitedKey ck) { List citationGroups = new ArrayList<>(); for (CitationPath p : ck.getCitationPaths()) { CitationGroupId cgid = p.group; - CitationGroup cg = cgs.getCitationGroupOrThrow(cgid); - citationGroups.add(cg); + Optional cg = cgs.getCitationGroup(cgid); + if (cg.isEmpty()) { + throw new IllegalStateException(); + } + citationGroups.add(cg.get()); } // sort the citationGroups according to their indexInGlobalOrder citationGroups.sort((a, b) -> { - Integer aa = a.getIndexInGlobalOrder().orElseThrow(RuntimeException::new); - Integer bb = b.getIndexInGlobalOrder().orElseThrow(RuntimeException::new); + Integer aa = a.getIndexInGlobalOrder().orElseThrow(IllegalStateException::new); + Integer bb = b.getIndexInGlobalOrder().orElseThrow(IllegalStateException::new); return (aa.compareTo(bb)); }); @@ -185,9 +188,9 @@ private static OOText formatCitedOnPages(CitationGroups cgs, CitedKey ck) { if (i > 0) { sb.append(", "); } - String markName = cg.getReferenceMarkNameForLinking().orElseThrow(RuntimeException::new); + String markName = cg.getReferenceMarkNameForLinking().orElseThrow(IllegalStateException::new); OOText xref = OOFormat.formatReferenceToPageNumberOfReferenceMark(markName); - sb.append(xref.asString()); + sb.append(xref.toString()); i++; } sb.append(suffix); diff --git a/src/main/java/org/jabref/logic/openoffice/style/OOProcess.java b/src/main/java/org/jabref/logic/openoffice/style/OOProcess.java index 6f7709aac10..05475fcdfac 100644 --- a/src/main/java/org/jabref/logic/openoffice/style/OOProcess.java +++ b/src/main/java/org/jabref/logic/openoffice/style/OOProcess.java @@ -66,12 +66,10 @@ public static Comparator comparatorForMulticite(OOBibStyle style) { * Fill cgs.bibliography and cgs.citationGroupsUnordered//CitationMarker * according to style. */ - public static void produceCitationMarkers(CitationGroups cgs, - List databases, - OOBibStyle style) { + public static void produceCitationMarkers(CitationGroups cgs, List databases, OOBibStyle style) { if (!cgs.hasGlobalOrder()) { - throw new RuntimeException("produceCitationMarkers: globalOrder is misssing in cgs"); + throw new IllegalStateException("produceCitationMarkers: globalOrder is misssing in cgs"); } cgs.lookupCitations(databases); diff --git a/src/main/java/org/jabref/model/openoffice/backend/NamedRange.java b/src/main/java/org/jabref/model/openoffice/backend/NamedRange.java index e5c54773b07..d8e1832b6ef 100644 --- a/src/main/java/org/jabref/model/openoffice/backend/NamedRange.java +++ b/src/main/java/org/jabref/model/openoffice/backend/NamedRange.java @@ -24,8 +24,8 @@ public Optional nrGetMarkRange(XTextDocument doc) WrappedTargetException; /** - * Cursor for the reference marks as is, not prepared for filling, - * but does not need nrCleanFillCursor either. + * Cursor for the reference marks as is, not prepared for filling, but does not need + * nrCleanFillCursor either. */ public Optional nrGetRawCursor(XTextDocument doc) throws @@ -44,8 +44,8 @@ public XTextCursor nrGetFillCursor(XTextDocument doc) CreationException; /** - * Remove brackets, but if the result would become empty, leave - * them; if the result would be a single characer, leave the left bracket. + * Remove brackets, but if the result would become empty, leave them; if the result would be a + * single characer, leave the left bracket. * */ public void nrCleanFillCursor(XTextDocument doc) diff --git a/src/main/java/org/jabref/model/openoffice/ootext/OOFormat.java b/src/main/java/org/jabref/model/openoffice/ootext/OOFormat.java index 472ca519161..be5ee399e55 100644 --- a/src/main/java/org/jabref/model/openoffice/ootext/OOFormat.java +++ b/src/main/java/org/jabref/model/openoffice/ootext/OOFormat.java @@ -1,9 +1,16 @@ package org.jabref.model.openoffice.ootext; +/** + * Helper functions to produce some of the markup as understood by OOTextIntoOO.write + * + * These do not cover all tags, only those needed to embed markup + * from Layout and citation marker formatters into citation markers and + * bibliography. + */ public class OOFormat { /** - * Mark {@code s} as using a character locale known to OO. + * Mark {@code ootext} as using a character locale known to OO. * * @param locale language[-country[-territory]] * @@ -11,63 +18,61 @@ public class OOFormat { * * The country part is optional. * - * The territory part is not only optional, the allowed "codes are - * vendor and browser-specific", so probably best to avoid them if possible. + * The territory part is not only optional, the allowed "codes are vendor and browser-specific", + * so probably best to avoid them if possible. * */ - public static OOText setLocale(OOText s, String locale) { - return OOText.fromString(String.format("", locale) + s.asString() + ""); + public static OOText setLocale(OOText ootext, String locale) { + return OOText.fromString(String.format("", locale) + ootext.toString() + ""); } /** - * Mark {@code s} as using the character locale "zxx", which means - * "no language", "no linguistic content". + * Mark {@code ootext} as using the character locale "zxx", which means "no language", "no + * linguistic content". * * Used around citation marks, probably to turn off spellchecking. * */ - public static OOText setLocaleNone(OOText s) { - return OOFormat.setLocale(s, "zxx"); + public static OOText setLocaleNone(OOText ootext) { + return OOFormat.setLocale(ootext, "zxx"); } /** - * Mark {@code s} using a character style {@code charStyle} + * Mark {@code ootext} using a character style {@code charStyle} * - * @param charStyle Name of a character style known to OO. May be - * empty for "Standard", which in turn means do not override any properties. + * @param charStyle Name of a character style known to OO. May be empty for "Standard", which in + * turn means do not override any properties. * */ - public static OOText setCharStyle(OOText s, String charStyle) { + public static OOText setCharStyle(OOText ootext, String charStyle) { return OOText.fromString(String.format("", charStyle) - + s.asString() - + ""); + + ootext.toString() + + ""); } /** - * Mark {@code s} as part of a paragraph with style {@code paraStyle} + * Mark {@code ootext} as part of a paragraph with style {@code paraStyle} */ - public static OOText paragraph(OOText s, String paraStyle) { + public static OOText paragraph(OOText ootext, String paraStyle) { if (paraStyle == null || "".equals(paraStyle)) { - return paragraph(s); + return paragraph(ootext); } String startTag = String.format("

", paraStyle); - return OOText.fromString(startTag + s.asString() + "

"); + return OOText.fromString(startTag + ootext.toString() + "

"); } /** - * Mark {@code s} as part of a paragraph. + * Mark {@code ootext} as part of a paragraph. */ - public static OOText paragraph(OOText s) { - return OOText.fromString("

" + s.asString() + "

"); + public static OOText paragraph(OOText ootext) { + return OOText.fromString("

" + ootext.toString() + "

"); } /** - * Format an OO cross-reference showing the target's page number - * as label to a reference mark. + * Format an OO cross-reference showing the target's page number as label to a reference mark. */ public static OOText formatReferenceToPageNumberOfReferenceMark(String referencMarkName) { - String s = String.format("", - referencMarkName); - return OOText.fromString(s); + String string = String.format("", referencMarkName); + return OOText.fromString(string); } } diff --git a/src/main/java/org/jabref/model/openoffice/ootext/OOText.java b/src/main/java/org/jabref/model/openoffice/ootext/OOText.java index a568a7c6e8a..ad0b9fb448d 100644 --- a/src/main/java/org/jabref/model/openoffice/ootext/OOText.java +++ b/src/main/java/org/jabref/model/openoffice/ootext/OOText.java @@ -5,9 +5,8 @@ /** * Text with HTML-like markup as understood by OOTextIntoOO.write * - * Some of the tags can be added using OOFormat methods. Others come - * from the layout engine, either by interpreting LaTeX markup or from - * settings in the jstyle file. + * Some of the tags can be added using OOFormat methods. Others come from the layout engine, either + * by interpreting LaTeX markup or from settings in the jstyle file. */ public class OOText { @@ -19,38 +18,39 @@ private OOText(String data) { } /* null input is passed through */ - public static OOText fromString(String s) { - if (s == null) { + public static OOText fromString(String string) { + if (string == null) { return null; } - return new OOText(s); + return new OOText(string); } /* null input is passed through */ - public static String toString(OOText s) { - if (s == null) { + public static String toString(OOText ootext) { + if (ootext == null) { return null; } - return s.data; + return ootext.data; } - public String asString() { + @Override + public String toString() { return data; } /* Object.equals */ @Override - public boolean equals(Object o) { + public boolean equals(Object object) { - if (o == this) { + if (object == this) { return true; } - if (!(o instanceof OOText)) { + if (!(object instanceof OOText)) { return false; } - OOText c = (OOText) o; + OOText c = (OOText) object; return data.equals(c.data); } diff --git a/src/main/java/org/jabref/model/openoffice/ootext/OOTextIntoOO.java b/src/main/java/org/jabref/model/openoffice/ootext/OOTextIntoOO.java index 298329f3a43..0758d515446 100644 --- a/src/main/java/org/jabref/model/openoffice/ootext/OOTextIntoOO.java +++ b/src/main/java/org/jabref/model/openoffice/ootext/OOTextIntoOO.java @@ -32,7 +32,6 @@ import com.sun.star.beans.XPropertySet; import com.sun.star.beans.XPropertySetInfo; import com.sun.star.beans.XPropertyState; -import com.sun.star.container.NoSuchElementException; import com.sun.star.lang.Locale; import com.sun.star.lang.WrappedTargetException; import com.sun.star.style.CaseMap; @@ -45,8 +44,7 @@ import org.slf4j.LoggerFactory; /** - * Interpret OOText into an OpenOffice or LibreOffice writer - * document. + * Interpret OOText into an OpenOffice or LibreOffice writer document. */ @AllowedToUseAwt("Requires AWT for changing document properties") public class OOTextIntoOO { @@ -148,24 +146,17 @@ private OOTextIntoOO() { */ public static void write(XTextDocument doc, XTextCursor position, OOText ootext) throws - UnknownPropertyException, PropertyVetoException, WrappedTargetException, - IllegalArgumentException, - NoSuchElementException, CreationException { - final boolean debugThisFun = false; - Objects.requireNonNull(doc); Objects.requireNonNull(ootext); Objects.requireNonNull(position); String lText = OOText.toString(ootext); - if (debugThisFun) { - System.out.println(lText); - } + LOGGER.debug(lText); XText text = position.getText(); XTextCursor cursor = text.createTextCursorByRange(position); @@ -242,15 +233,11 @@ public static void write(XTextDocument doc, XTextCursor position, OOText ootext) //

if (value != null && !value.equals("")) { if (setParagraphStyle(cursor, value)) { - if (debugThisFun) { - // Presumably tested already: - LOGGER.warn(String.format("oo:ParaStyleName=\"%s\" failed", value)); - } + // Presumably tested already: + LOGGER.debug(String.format("oo:ParaStyleName=\"%s\" failed", value)); } } else { - if (debugThisFun) { - LOGGER.warn(String.format("oo:ParaStyleName inherited")); - } + LOGGER.debug(String.format("oo:ParaStyleName inherited")); } break; default: @@ -359,10 +346,10 @@ public static void write(XTextDocument doc, XTextCursor position, OOText ootext) */ public static void removeDirectFormatting(XTextCursor cursor) { - XMultiPropertyStates mpss = UnoCast.unoQI(XMultiPropertyStates.class, cursor); + XMultiPropertyStates mpss = UnoCast.cast(XMultiPropertyStates.class, cursor).get(); - XPropertySet propertySet = UnoCast.unoQI(XPropertySet.class, cursor); - XPropertyState xPropertyState = UnoCast.unoQI(XPropertyState.class, cursor); + XPropertySet propertySet = UnoCast.cast(XPropertySet.class, cursor).get(); + XPropertyState xPropertyState = UnoCast.cast(XPropertyState.class, cursor).get(); try { // Special handling @@ -370,7 +357,6 @@ public static void removeDirectFormatting(XTextCursor cursor) { xPropertyState.setPropertyToDefault("CharCaseMap"); } catch (UnknownPropertyException | PropertyVetoException | - IllegalArgumentException | WrappedTargetException ex) { LOGGER.warn("exception caught", ex); } @@ -378,8 +364,8 @@ public static void removeDirectFormatting(XTextCursor cursor) { mpss.setAllPropertiesToDefault(); /* - * Now that we have called setAllPropertiesToDefault, check which properties - * are not set to default and try to correct what we can and seem necessary. + * Now that we have called setAllPropertiesToDefault, check which properties are not set to + * default and try to correct what we can and seem necessary. * * Note: tested with LibreOffice : 6.4.6.2 */ @@ -396,7 +382,7 @@ public static void removeDirectFormatting(XTextCursor cursor) { "ParaStyleName"); // query again, just in case it matters - propertySet = UnoCast.unoQI(XPropertySet.class, cursor); + propertySet = UnoCast.cast(XPropertySet.class, cursor).get(); XPropertySetInfo propertySetInfo = propertySet.getPropertySetInfo(); // check the result @@ -409,7 +395,7 @@ public static void removeDirectFormatting(XTextCursor cursor) { continue; } } catch (UnknownPropertyException ex) { - throw new RuntimeException("Unexpected UnknownPropertyException"); + throw new java.lang.IllegalStateException("Unexpected UnknownPropertyException"); } if (knownToFail.contains(p.Name)) { continue; @@ -421,8 +407,8 @@ public static void removeDirectFormatting(XTextCursor cursor) { static class MyPropertyStack { /* - * We only try to control these. Should include all character - * properties we set, and maybe their interdependencies. + * We only try to control these. Should include all character properties we set, and maybe + * their interdependencies. * * For a list of properties see: * https://www.openoffice.org/api/docs/common/ref/com/sun/star/style/CharacterProperties.html @@ -479,17 +465,15 @@ static class MyPropertyStack { final String[] goodNames; /** - * Maintain a stack of layers, each containing a description - * of the desired state of properties. Each description is an - * ArrayList of property values, Optional.empty() encoding - * "not directly set". + * Maintain a stack of layers, each containing a description of the desired state of + * properties. Each description is an ArrayList of property values, Optional.empty() + * encoding "not directly set". */ final Stack>> layers; - MyPropertyStack(XTextCursor cursor) - throws UnknownPropertyException { + MyPropertyStack(XTextCursor cursor) { - XPropertySet propertySet = UnoCast.unoQI(XPropertySet.class, cursor); + XPropertySet propertySet = UnoCast.cast(XPropertySet.class, cursor).get(); XPropertySetInfo propertySetInfo = propertySet.getPropertySetInfo(); /* @@ -515,8 +499,7 @@ static class MyPropertyStack { goodNames[ entry.getValue() ] = entry.getKey(); } - // XMultiPropertySet.setPropertyValues() - // requires alphabetically sorted property names. + // XMultiPropertySet.setPropertyValues() requires alphabetically sorted property names. // We adjust here: Arrays.sort(goodNames); for (int i = 0; i < goodSize; i++) { @@ -524,12 +507,17 @@ static class MyPropertyStack { } /* - * Get the initial state of the properties and add add the first layer. + * Get the initial state of the properties and add the first layer. */ - XMultiPropertyStates mpss = UnoCast.unoQI(XMultiPropertyStates.class, cursor); - PropertyState[] propertyStates = mpss.getPropertyStates(goodNames); + XMultiPropertyStates mpss = UnoCast.cast(XMultiPropertyStates.class, cursor).get(); + PropertyState[] propertyStates = null; + try { + propertyStates = mpss.getPropertyStates(goodNames); + } catch (UnknownPropertyException ex) { + throw new java.lang.IllegalStateException("Caught unexpected UnknownPropertyException"); + } - XMultiPropertySet mps = UnoCast.unoQI(XMultiPropertySet.class, cursor); + XMultiPropertySet mps = UnoCast.cast(XMultiPropertySet.class, cursor).get(); Object[] initialValues = mps.getPropertyValues(goodNames); ArrayList> initialValuesOpt = new ArrayList<>(goodSize); @@ -547,9 +535,8 @@ static class MyPropertyStack { } /** - * Given a list of property name, property value pairs, - * construct and push a new layer describing the intended - * state after these have been applied. + * Given a list of property name, property value pairs, construct and push a new layer + * describing the intended state after these have been applied. * * Opening tags usually call this. */ @@ -582,11 +569,12 @@ void popLayer() { /** * Apply the current desired formatting state to a cursor. + * * The idea is to minimize the number of calls to OpenOffice. */ void apply(XTextCursor cursor) { - XMultiPropertySet mps = UnoCast.unoQI(XMultiPropertySet.class, cursor); - XMultiPropertyStates mpss = UnoCast.unoQI(XMultiPropertyStates.class, cursor); + XMultiPropertySet mps = UnoCast.cast(XMultiPropertySet.class, cursor).get(); + XMultiPropertyStates mpss = UnoCast.cast(XMultiPropertyStates.class, cursor).get(); ArrayList> topLayer = layers.peek(); try { // select values to be set @@ -608,11 +596,9 @@ void apply(XTextCursor cursor) { mpss.setPropertiesToDefault(delNamesArray); mps.setPropertyValues(namesArray, values.toArray()); } catch (UnknownPropertyException ex) { - LOGGER.warn("UnknownPropertyException in MyPropertyStack.apply"); + LOGGER.warn("UnknownPropertyException in MyPropertyStack.apply", ex); } catch (PropertyVetoException ex) { LOGGER.warn("PropertyVetoException in MyPropertyStack.apply"); - } catch (IllegalArgumentException ex) { - LOGGER.warn("IllegalArgumentException in MyPropertyStack.apply"); } catch (WrappedTargetException ex) { LOGGER.warn("WrappedTargetException in MyPropertyStack.apply"); } @@ -648,18 +634,18 @@ private static List> parseAttributes(String s) { } /* - * We rely on property values being either DIRECT_VALUE or - * DEFAULT_VALUE (not AMBIGUOUS_VALUE). If the cursor covers a homogeneous region, - * or is collapsed, then this is true. + * We rely on property values being either DIRECT_VALUE or DEFAULT_VALUE (not + * AMBIGUOUS_VALUE). If the cursor covers a homogeneous region, or is collapsed, then this is + * true. */ private static boolean isPropertyDefault(XTextCursor cursor, String propertyName) throws UnknownPropertyException { - XPropertyState xPropertyState = UnoCast.unoQI(XPropertyState.class, cursor); + XPropertyState xPropertyState = UnoCast.cast(XPropertyState.class, cursor).get(); PropertyState state = xPropertyState.getPropertyState(propertyName); if (state == PropertyState.AMBIGUOUS_VALUE) { - throw new RuntimeException("PropertyState.AMBIGUOUS_VALUE" - + " (expected properties for a homogeneous cursor)"); + throw new java.lang.IllegalArgumentException("PropertyState.AMBIGUOUS_VALUE" + + " (expected properties for a homogeneous cursor)"); } return state == PropertyState.DEFAULT_VALUE; } @@ -723,7 +709,7 @@ private static List> setCharLocale(Locale value) { */ private static List> setCharLocale(String value) { if (value == null || "".equals(value)) { - throw new RuntimeException("setCharLocale \"\" or null"); + throw new java.lang.IllegalArgumentException("setCharLocale \"\" or null"); } String[] parts = value.split("-"); String language = (parts.length > 0) ? parts[0] : ""; @@ -735,8 +721,8 @@ private static List> setCharLocale(String value) { /* * SuperScript and SubScript. * - * @param relative If true, calculate the new values relative to - * the current values. This allows subscript-in-superscript. + * @param relative If true, calculate the new values relative to the current values. This allows + * subscript-in-superscript. */ private static List> setCharEscapement(Optional value, Optional height, @@ -796,22 +782,28 @@ public static boolean setParagraphStyle(XTextCursor cursor, String paragraphStyl final boolean FAIL = true; final boolean PASS = false; - XParagraphCursor paragraphCursor = UnoCast.unoQI(XParagraphCursor.class, cursor); - XPropertySet propertySet = UnoCast.unoQI(XPropertySet.class, paragraphCursor); + XParagraphCursor paragraphCursor = UnoCast.cast(XParagraphCursor.class, cursor).get(); + XPropertySet propertySet = UnoCast.cast(XPropertySet.class, paragraphCursor).get(); try { propertySet.setPropertyValue(PARA_STYLE_NAME, paragraphStyle); return PASS; } catch (UnknownPropertyException | PropertyVetoException - | IllegalArgumentException + | com.sun.star.lang.IllegalArgumentException | WrappedTargetException ex) { return FAIL; } } - private static void insertParagraphBreak(XText text, XTextCursor cursor) - throws IllegalArgumentException { - text.insertControlCharacter(cursor, ControlCharacter.PARAGRAPH_BREAK, true); + private static void insertParagraphBreak(XText text, XTextCursor cursor) { + try { + text.insertControlCharacter(cursor, ControlCharacter.PARAGRAPH_BREAK, true); + } catch (com.sun.star.lang.IllegalArgumentException ex) { + // Assuming it means wrong code for ControlCharacter. + // https://api.libreoffice.org/docs/idl/ref/ does not tell. + // If my assumption is correct, we never get here. + throw new java.lang.IllegalArgumentException("Caught unexpected com.sun.star.lang.IllegalArgumentException", ex); + } } } diff --git a/src/main/java/org/jabref/model/openoffice/rangesort/FunctionalTextViewCursor.java b/src/main/java/org/jabref/model/openoffice/rangesort/FunctionalTextViewCursor.java index 3f780274b0f..e08a7f4a2ad 100644 --- a/src/main/java/org/jabref/model/openoffice/rangesort/FunctionalTextViewCursor.java +++ b/src/main/java/org/jabref/model/openoffice/rangesort/FunctionalTextViewCursor.java @@ -13,23 +13,21 @@ import com.sun.star.text.XTextViewCursor; /* - * A problem with XTextViewCursor: if it is not in text, then we get a - * crippled version that does not support viewCursor.getStart() or - * viewCursor.gotoRange(range,false), and will throw an exception - * instead. + * A problem with XTextViewCursor: if it is not in text, then we get a crippled version that does + * not support viewCursor.getStart() or viewCursor.gotoRange(range,false), and will throw an + * exception instead. * - * Here we manipulate the cursor via XSelectionSupplier.getSelection and - * XSelectionSupplier.select to move it to the text. + * Here we manipulate the cursor via XSelectionSupplier.getSelection and XSelectionSupplier.select + * to move it to the text. * * Seems to work when the user selected a frame or image. * In these cases restoring the selection works, too. * - * When the cursor is in a comment (referred to as "annotation" in OO - * API) then initialSelection is null, and select() fails to - * get a functional viewCursor. + * When the cursor is in a comment (referred to as "annotation" in OO API) then initialSelection is + * null, and select() fails to get a functional viewCursor. * - * If FunctionalTextViewCursor.get() reports error, we have to ask the - * user to move the cursor into the text part of the document. + * If FunctionalTextViewCursor.get() reports error, we have to ask the user to move the cursor into + * the text part of the document. * * Usage: * @@ -45,19 +43,13 @@ */ public class FunctionalTextViewCursor { - /* - * The initial position of the cursor or null. - */ + /* The initial position of the cursor or null. */ private XTextRange initialPosition; - /* - * The initial selection in the document or null. - */ + /* The initial selection in the document or null. */ private XServiceInfo initialSelection; - /* - * The view cursor, potentially moved from its original location. - */ + /* The view cursor, potentially moved from its original location. */ private XTextViewCursor viewCursor; private FunctionalTextViewCursor(XTextRange initialPosition, @@ -71,12 +63,10 @@ private FunctionalTextViewCursor(XTextRange initialPosition, /* * Get a functional XTextViewCursor or an error message. * - * The cursor position may differ from the location - * provided by the user. + * The cursor position may differ from the location provided by the user. * - * On failure the constructor restores the selection. On success, - * the caller may want to call instance.restore() after finished - * using the cursor. + * On failure the constructor restores the selection. On success, the caller may want to call + * instance.restore() after finished using the cursor. */ public static OOResult get(XTextDocument doc) { @@ -89,9 +79,7 @@ public static OOResult get(XTextDocument doc) try { initialPosition = UnoCursor.createTextCursorByRange(viewCursor); viewCursor.getStart(); - return OOResult.ok(new FunctionalTextViewCursor(initialPosition, - initialSelection, - viewCursor)); + return OOResult.ok(new FunctionalTextViewCursor(initialPosition, initialSelection, viewCursor)); } catch (com.sun.star.uno.RuntimeException ex) { // bad cursor viewCursor = null; @@ -100,8 +88,7 @@ public static OOResult get(XTextDocument doc) } if (initialSelection == null) { - String errorMessage = ("Selection is not available:" - + " cannot provide a functional view cursor"); + String errorMessage = ("Selection is not available: cannot provide a functional view cursor"); return OOResult.error(errorMessage); } else if (!Arrays.stream(initialSelection.getSupportedServiceNames()) .anyMatch("com.sun.star.text.TextRanges"::equals)) { @@ -149,10 +136,7 @@ private static void restore(XTextDocument doc, } } - /* - * Restore initial state of viewCursor (possibly by restoring - * selection) if possible. - */ + /* Restore initial state of viewCursor (possibly by restoring selection) if possible. */ public void restore(XTextDocument doc) { FunctionalTextViewCursor.restore(doc, initialPosition, initialSelection); } diff --git a/src/main/java/org/jabref/model/openoffice/rangesort/RangeOverlapBetween.java b/src/main/java/org/jabref/model/openoffice/rangesort/RangeOverlapBetween.java index 35d2d006fab..9a3ef1c9968 100644 --- a/src/main/java/org/jabref/model/openoffice/rangesort/RangeOverlapBetween.java +++ b/src/main/java/org/jabref/model/openoffice/rangesort/RangeOverlapBetween.java @@ -19,7 +19,8 @@ private RangeOverlapBetween() { } /** * Check for any overlap between two sets of XTextRange values. * - * Assume fewHolders is small (usually 1 elements for checking the cursor) + * Assume fewHolders is small (usually a single element, for checking the cursor) + * * Returns on first problem found. */ public static @@ -34,16 +35,22 @@ List> findFirst(XTextDocument doc, return result; } - List> fewTuples = - new ArrayList<>(fewHolders.size()); - - for (V aHolder : fewHolders) { - XText aText = aHolder.getRange().getText(); - fewTuples.add(new OOTuple3<>(aText, - UnoCast.unoQI(XTextRangeCompare.class, aText), - aHolder)); + /* + * Cache all we need to know about fewHolders. We are trying to minimize the number of calls + * to LO. + */ + List> fewTuples = new ArrayList<>(fewHolders.size()); + + for (V aHolder : fewHolders) { + XText aText = aHolder.getRange().getText(); + fewTuples.add(new OOTuple3<>(aText, + UnoCast.cast(XTextRangeCompare.class, aText).get(), + aHolder)); } + /* + * We only go through manyHolders once: fewTuples is in the inner loop. + */ for (V bHolder : manyHolders) { XTextRange bRange = bHolder.getRange(); XText bText = bRange.getText(); @@ -58,25 +65,24 @@ List> findFirst(XTextDocument doc, if (aText != bText) { continue; } - int abEndToStart = -1 * cmp.compareRegionStarts(aRange.getEnd(), bRangeStart); + int abEndToStart = UnoTextRange.compareStartsUnsafe(cmp, aRange.getEnd(), bRangeStart); if (abEndToStart < 0 || (!includeTouching && (abEndToStart == 0))) { continue; } - int baEndToStart = -1 * cmp.compareRegionStarts(bRangeEnd, aRange.getStart()); + int baEndToStart = UnoTextRange.compareStartsUnsafe(cmp, bRangeEnd, aRange.getStart()); if (baEndToStart < 0 || (!includeTouching && (baEndToStart == 0))) { continue; } - boolean equal = UnoTextRange.compareStartsThenEnds(aRange, bRange) == 0; + boolean equal = UnoTextRange.compareStartsThenEndsUnsafe(cmp, aRange, bRange) == 0; boolean touching = (abEndToStart == 0 || baEndToStart == 0); - // In case of two equal collapsed ranges there is an - // ambiguity : TOUCH or EQUAL_RANGE ? + // In case of two equal collapsed ranges there is an ambiguity : TOUCH or EQUAL_RANGE ? + // // We return EQUAL_RANGE - RangeOverlapKind kind = - (equal ? RangeOverlapKind.EQUAL_RANGE - : (touching ? RangeOverlapKind.TOUCH - : RangeOverlapKind.OVERLAP)); + RangeOverlapKind kind = (equal ? RangeOverlapKind.EQUAL_RANGE + : (touching ? RangeOverlapKind.TOUCH + : RangeOverlapKind.OVERLAP)); List valuesForOverlappingRanges = new ArrayList<>(); valuesForOverlappingRanges.add(aHolder); diff --git a/src/main/java/org/jabref/model/openoffice/rangesort/RangeOverlapWithin.java b/src/main/java/org/jabref/model/openoffice/rangesort/RangeOverlapWithin.java index 62ee6d82ca0..e4df2d8278c 100644 --- a/src/main/java/org/jabref/model/openoffice/rangesort/RangeOverlapWithin.java +++ b/src/main/java/org/jabref/model/openoffice/rangesort/RangeOverlapWithin.java @@ -15,17 +15,21 @@ public class RangeOverlapWithin { private RangeOverlapWithin() { } /** - * Report identical, overlapping or touching ranges between - * elements of rangeHolders. + * Report identical, overlapping or touching ranges between elements of rangeHolders. * - * For overlapping and touching, only report consecutive ranges - * and only with a single sample of otherwise identical ranges. + * For overlapping and touching, only report consecutive ranges and only with a single sample of + * otherwise identical ranges. + * + * @param rangeHolders represent the ranges to be checked. + * + * Note: for each rangeHolder, rangeHolder.getRange() is called multiple times. + * To avoid repeated work, they should keep a copy of the range instead of + * getting it each time from the document. * * @param reportAtMost Limit the number of records returned to atMost. * Zero {@code reportAtMost} means no limit. * - * @param includeTouching Should the result contain ranges - * sharing only a boundary? + * @param includeTouching Should the result contain ranges sharing only a boundary? */ public static List> findOverlappingRanges(XTextDocument doc, @@ -43,14 +47,13 @@ List> findOverlappingRanges(XTextDocument doc, /** * Report identical, overlapping or touching ranges. * - * For overlapping and touching, only report consecutive ranges - * and only with a single sample of otherwise identical ranges. + * For overlapping and touching, only report consecutive ranges and only with a single sample of + * otherwise identical ranges. * * @param atMost Limit the number of records returned to atMost. * Zero {@code atMost} means no limit. * - * @param includeTouching Should the result contain ranges - * sharing only a boundary? + * @param includeTouching Should the result contain ranges sharing only a boundary? */ public static List> findOverlappingRanges(RangeSort.RangePartitions input, @@ -64,8 +67,8 @@ List> findOverlappingRanges(RangeSort.RangePartitions input, if (partition.size() == 0) { continue; } - XTextRangeCompare cmp = UnoCast.unoQI(XTextRangeCompare.class, - partition.get(0).getRange().getText()); + XTextRangeCompare cmp = UnoCast.cast(XTextRangeCompare.class, + partition.get(0).getRange().getText()).get(); for (int i = 0; i < (partition.size() - 1); i++) { V aHolder = partition.get(i); diff --git a/src/main/java/org/jabref/model/openoffice/rangesort/RangeSet.java b/src/main/java/org/jabref/model/openoffice/rangesort/RangeSet.java index f7fbe37ba25..e389c902b90 100644 --- a/src/main/java/org/jabref/model/openoffice/rangesort/RangeSet.java +++ b/src/main/java/org/jabref/model/openoffice/rangesort/RangeSet.java @@ -29,7 +29,12 @@ public boolean contains(XTextRange range) { return partitions.get(partitionKey).contains(range); } - /* return false if already contained */ + /* + * return false if already contained + * + * Beware: using UnoTextRange::compareStartsThenEnds as comparator involves range.getText() + * twice on each comparison. This makes it costly to use this class. + */ public boolean add(XTextRange range) { TreeSet partition = partitions.get(range.getText()); if (partition == null) { diff --git a/src/main/java/org/jabref/model/openoffice/rangesort/RangeSort.java b/src/main/java/org/jabref/model/openoffice/rangesort/RangeSort.java index 2f5daa0e1aa..1b4fc327598 100644 --- a/src/main/java/org/jabref/model/openoffice/rangesort/RangeSort.java +++ b/src/main/java/org/jabref/model/openoffice/rangesort/RangeSort.java @@ -23,7 +23,7 @@ public static class HolderComparatorWithinPartition implements Comparator - void sortWithinPartition(List rangeHolders) { + /* + * Sort a list of RangeHolder values known to share the same getText(). + * + * Note: RangeHolder.getRange() is called many times. + */ + public static void sortWithinPartition(List rangeHolders) { if (rangeHolders.isEmpty()) { return; } @@ -70,8 +74,7 @@ public List> getPartitions() { } } - public static - RangePartitions partitionRanges(List holders) { + public static RangePartitions partitionRanges(List holders) { RangePartitions result = new RangePartitions<>(); for (V holder : holders) { result.add(holder); @@ -79,8 +82,10 @@ RangePartitions partitionRanges(List holders) { return result; } - public static - RangePartitions partitionAndSortRanges(List holders) { + /* + * Note: RangeHolder.getRange() is called many times. + */ + public static RangePartitions partitionAndSortRanges(List holders) { RangePartitions result = partitionRanges(holders); for (List partition : result.getPartitions()) { sortWithinPartition(partition); diff --git a/src/main/java/org/jabref/model/openoffice/rangesort/RangeSortVisual.java b/src/main/java/org/jabref/model/openoffice/rangesort/RangeSortVisual.java index 2131c3cecb9..969f9fed44e 100644 --- a/src/main/java/org/jabref/model/openoffice/rangesort/RangeSortVisual.java +++ b/src/main/java/org/jabref/model/openoffice/rangesort/RangeSortVisual.java @@ -21,11 +21,10 @@ * * Requires functional XTextViewCursor. * - * Problem: for multicolumn layout and when viewing pages side-by-side - * in LO, the (top-down,left-to-right) order interpreted - * as-on-the-screen: an XTextRange at the top of the second - * column or second page is sorted before one at the bottom - * of the first column of the first page. + * Problem: for multicolumn layout and when viewing pages side-by-side in LO, the + * (top-down,left-to-right) order interpreted as-on-the-screen: an XTextRange at the top of + * the second column or second page is sorted before an XTextRange at the bottom of the + * first column of the first page. */ public class RangeSortVisual { @@ -36,8 +35,7 @@ public class RangeSortVisual { * * Requires a functional {@code XTextViewCursor}. * - * @return The input, sorted by the elements XTextRange and - * getIndexInPosition. + * @return The input, sorted by the elements XTextRange and getIndexInPosition. */ public static List> visualSort(List> inputs, XTextDocument doc, @@ -49,12 +47,9 @@ public static List> visualSort(List> input final int inputSize = inputs.size(); if (UnoScreenRefresh.hasControllersLocked(doc)) { - LOGGER.warn("visualSort:" - + " with ControllersLocked, viewCursor.gotoRange" - + " is probably useless"); - throw new RuntimeException("visualSort:" - + " with ControllersLocked, viewCursor.gotoRange" - + " is probably useless"); + final String msg = "visualSort: with ControllersLocked, viewCursor.gotoRange is probably useless"; + LOGGER.warn(msg); + throw new IllegalStateException(msg); } XTextViewCursor viewCursor = fcursor.getViewCursor(); @@ -62,13 +57,12 @@ public static List> visualSort(List> input // find coordinates List positions = new ArrayList<>(inputSize); for (RangeSortable v : inputs) { - positions.add(findPositionOfTextRange(v.getRange(), - viewCursor)); + positions.add(findPositionOfTextRange(v.getRange(), viewCursor)); } fcursor.restore(doc); if (positions.size() != inputSize) { - throw new RuntimeException("visualSort: positions.size() != inputSize"); + throw new IllegalStateException("visualSort: positions.size() != inputSize"); } // order by position @@ -82,7 +76,7 @@ public static List> visualSort(List> input Collections.sort(set); if (set.size() != inputSize) { - throw new RuntimeException("visualSort: set.size() != inputSize"); + throw new IllegalStateException("visualSort: set.size() != inputSize"); } // collect ordered result @@ -92,26 +86,24 @@ public static List> visualSort(List> input } if (result.size() != inputSize) { - throw new RuntimeException("visualSort: result.size() != inputSize"); + throw new IllegalStateException("visualSort: result.size() != inputSize"); } return result; } /** - * Given a location, return its position: coordinates relative to - * the top left position of the first page of the document. + * Given a location, return its position: coordinates relative to the top left position of the + * first page of the document. * - * Note: for text layouts with two or more columns, this gives the - * wrong order: top-down/left-to-right does not match - * reading order. + * Note: for text layouts with two or more columns, this gives the wrong order: + * top-down/left-to-right does not match reading order. * - * Note: The "relative to the top left position of the first page" - * is meant "as it appears on the screen". + * Note: The "relative to the top left position of the first page" is meant "as it appears on + * the screen". * - * In particular: when viewing pages side-by-side, the top - * half of the right page is higher than the lower half of - * the left page. Again, top-down/left-to-right does not + * In particular: when viewing pages side-by-side, the top half of the right page is + * higher than the lower half of the left page. Again, top-down/left-to-right does not * match reading order. * * @param range Location. @@ -126,8 +118,8 @@ private static Point findPositionOfTextRange(XTextRange range, XTextViewCursor c /** * A reference mark name paired with its visual position. * - * Comparison is based on (Y,X,indexInPosition): vertical compared - * first, horizontal second, indexInPosition third. + * Comparison is based on (Y,X,indexInPosition): vertical compared first, horizontal second, + * indexInPosition third. * * Used for sorting reference marks by their visual positions. */ diff --git a/src/main/java/org/jabref/model/openoffice/rangesort/RangeSortable.java b/src/main/java/org/jabref/model/openoffice/rangesort/RangeSortable.java index 66e17a5b35b..c3eab225889 100644 --- a/src/main/java/org/jabref/model/openoffice/rangesort/RangeSortable.java +++ b/src/main/java/org/jabref/model/openoffice/rangesort/RangeSortable.java @@ -9,14 +9,12 @@ public interface RangeSortable extends RangeHolder { /** The XTextRange * - * For citation marks in footnotes this may be the range of the - * footnote mark. + * For citation marks in footnotes this may be the range of the footnote mark. */ public XTextRange getRange(); /** - * For citation marks in footnotes this may provide order within - * the footnote. + * For citation marks in footnotes this may provide order within the footnote. */ public int getIndexInPosition(); diff --git a/src/main/java/org/jabref/model/openoffice/style/Citation.java b/src/main/java/org/jabref/model/openoffice/style/Citation.java index f0b732a9240..3f949237e07 100644 --- a/src/main/java/org/jabref/model/openoffice/style/Citation.java +++ b/src/main/java/org/jabref/model/openoffice/style/Citation.java @@ -112,7 +112,7 @@ public void setUniqueLetter(Optional uniqueLetter) { public void setPageInfo(Optional v) { Optional vv = PageInfo.normalizePageInfo(v); if (!vv.equals(v)) { - throw new RuntimeException("setPageInfo argument is not normalized"); + throw new IllegalArgumentException("setPageInfo argument is not normalized"); } this.pageInfo = vv; } diff --git a/src/main/java/org/jabref/model/openoffice/style/CitationGroup.java b/src/main/java/org/jabref/model/openoffice/style/CitationGroup.java index 075c55cc2ef..2986a6f4501 100644 --- a/src/main/java/org/jabref/model/openoffice/style/CitationGroup.java +++ b/src/main/java/org/jabref/model/openoffice/style/CitationGroup.java @@ -36,15 +36,14 @@ public class CitationGroup { * A name of a reference mark to link to by formatCitedOnPages. * May be initially empty, if backend does not use reference marks. * - * produceCitationMarkers might want fill it to support - * cross-references to citation groups from the bibliography. + * produceCitationMarkers might want fill it to support cross-references to citation groups from + * the bibliography. */ private Optional referenceMarkNameForLinking; /* - * Indices into citations: citations[localOrder[i]] provides ith - * citation according to the currently imposed local order for - * presentation. + * Indices into citations: citations[localOrder[i]] provides ith citation according to the + * currently imposed local order for presentation. * * Initialized to (0..(nCitations-1)) in the constructor. */ diff --git a/src/main/java/org/jabref/model/openoffice/style/CitationGroupId.java b/src/main/java/org/jabref/model/openoffice/style/CitationGroupId.java index bac1e3c1387..6ba6b760cb6 100644 --- a/src/main/java/org/jabref/model/openoffice/style/CitationGroupId.java +++ b/src/main/java/org/jabref/model/openoffice/style/CitationGroupId.java @@ -10,8 +10,7 @@ public CitationGroupId(String id) { } /** - * CitationEntry needs some string identifying the group - * that it can pass back later. + * CitationEntry needs some string identifying the group that it can pass back later. */ public String citationGroupIdAsString() { return id; diff --git a/src/main/java/org/jabref/model/openoffice/style/CitationGroups.java b/src/main/java/org/jabref/model/openoffice/style/CitationGroups.java index f68aa4f115c..bd0cca64e13 100644 --- a/src/main/java/org/jabref/model/openoffice/style/CitationGroups.java +++ b/src/main/java/org/jabref/model/openoffice/style/CitationGroups.java @@ -20,8 +20,7 @@ /** * CitationGroups : the set of citation groups in the document. * - * This is the main input (as well as output) for creating citation - * markers and bibliography. + * This is the main input (as well as output) for creating citation markers and bibliography. * */ public class CitationGroups { @@ -56,8 +55,7 @@ public int numberOfCitationGroups() { } /** - * For each citation in {@code where} - * call {@code fun.accept(new Pair(citation, value));} + * For each citation in {@code where} call {@code fun.accept(new Pair(citation, value));} */ public void distributeToCitations(List where, Consumer> fun, @@ -84,21 +82,19 @@ public void lookupCitations(List databases) { if (true) { // collect-lookup-distribute // - // CitationDatabaseLookupResult for the same citation key - // is the same object. Until we insert a new citation from the GUI. + // CitationDatabaseLookupResult for the same citation key is the same object. Until we + // insert a new citation from the GUI. CitedKeys cks = getCitedKeysUnordered(); cks.lookupInDatabases(databases); cks.distributeLookupResults(this); } else { // lookup each citation directly // - // CitationDatabaseLookupResult for the same citation key - // may be a different object: CitedKey.addPath has to use equals, - // so CitationDatabaseLookupResult has to override Object.equals, - // which depends on BibEntry.equals and BibDatabase.equals - // doing the right thing. Seems to work. But what we gained - // from avoiding collect-and-distribute may be lost in more - // complicated consistency checking in addPath. + // CitationDatabaseLookupResult for the same citation key may be a different object: + // CitedKey.addPath has to use equals, so CitationDatabaseLookupResult has to override + // Object.equals, which depends on BibEntry.equals and BibDatabase.equals doing the + // right thing. Seems to work. But what we gained from avoiding collect-and-distribute + // may be lost in more complicated consistency checking in addPath. // for (CitationGroup cg : getCitationGroupsUnordered()) { for (Citation cit : cg.citationsInStorageOrder) { @@ -117,22 +113,21 @@ public List getCitationGroupsUnordered() { */ public List getCitationGroupsInGlobalOrder() { if (globalOrder.isEmpty()) { - throw new RuntimeException("getCitationGroupsInGlobalOrder: not ordered yet"); + throw new IllegalStateException("getCitationGroupsInGlobalOrder: not ordered yet"); } return OOListUtil.map(globalOrder.get(), cgid -> citationGroupsUnordered.get(cgid)); } /** - * Impose an order of citation groups by providing the order - * of their citation group idendifiers. + * Impose an order of citation groups by providing the order of their citation group + * idendifiers. * * Also set indexInGlobalOrder for each citation group. */ public void setGlobalOrder(List globalOrder) { Objects.requireNonNull(globalOrder); if (globalOrder.size() != numberOfCitationGroups()) { - throw new RuntimeException("setGlobalOrder:" - + " globalOrder.size() != numberOfCitationGroups()"); + throw new IllegalStateException("setGlobalOrder: globalOrder.size() != numberOfCitationGroups()"); } this.globalOrder = Optional.of(globalOrder); @@ -158,8 +153,8 @@ public void imposeLocalOrder(Comparator entryComparator) { } /** - * Collect citations into a list of cited sources using neither - * CitationGroup.globalOrder or Citation.localOrder + * Collect citations into a list of cited sources using neither CitationGroup.globalOrder or + * Citation.localOrder */ public CitedKeys getCitedKeysUnordered() { LinkedHashMap res = new LinkedHashMap<>(); @@ -185,7 +180,7 @@ public CitedKeys getCitedKeysUnordered() { public CitedKeys getCitedKeysSortedInOrderOfAppearance() { LinkedHashMap res = new LinkedHashMap<>(); if (!hasGlobalOrder()) { - throw new RuntimeException("getSortedCitedKeys: no globalOrder"); + throw new IllegalStateException("getSortedCitedKeys: no globalOrder"); } for (CitationGroup cg : getCitationGroupsInGlobalOrder()) { for (int i : cg.getLocalOrder()) { @@ -224,8 +219,8 @@ public List getUnresolvedKeys() { public void createNumberedBibliographySortedInOrderOfAppearance() { if (!bibliography.isEmpty()) { - throw new RuntimeException("createNumberedBibliographySortedInOrderOfAppearance:" - + " already have a bibliography"); + throw new IllegalStateException("createNumberedBibliographySortedInOrderOfAppearance:" + + " already have a bibliography"); } CitedKeys citedKeys = getCitedKeysSortedInOrderOfAppearance(); citedKeys.numberCitedKeysInCurrentOrder(); @@ -238,8 +233,7 @@ public void createNumberedBibliographySortedInOrderOfAppearance() { */ public void createPlainBibliographySortedByComparator(Comparator entryComparator) { if (!bibliography.isEmpty()) { - throw new RuntimeException("createPlainBibliographySortedByComparator:" - + " already have a bibliography"); + throw new IllegalStateException("createPlainBibliographySortedByComparator: already have a bibliography"); } CitedKeys citedKeys = getCitedKeysUnordered(); citedKeys.sortByComparator(entryComparator); @@ -251,8 +245,7 @@ public void createPlainBibliographySortedByComparator(Comparator entry */ public void createNumberedBibliographySortedByComparator(Comparator entryComparator) { if (!bibliography.isEmpty()) { - throw new RuntimeException("createNumberedBibliographySortedByComparator:" - + " already have a bibliography"); + throw new IllegalStateException("createNumberedBibliographySortedByComparator: already have a bibliography"); } CitedKeys citedKeys = getCitedKeysUnordered(); citedKeys.sortByComparator(entryComparator); @@ -270,18 +263,6 @@ public Optional getCitationGroup(CitationGroupId cgid) { return Optional.ofNullable(cg); } - /** - * Call this when the citation group is unquestionably there. - */ - public CitationGroup getCitationGroupOrThrow(CitationGroupId cgid) { - CitationGroup cg = citationGroupsUnordered.get(cgid); - if (cg == null) { - throw new RuntimeException("getCitationGroupOrThrow:" - + " the requested CitationGroup is not available"); - } - return cg; - } - /* * @return true if all citation groups have referenceMarkNameForLinking */ diff --git a/src/main/java/org/jabref/model/openoffice/style/CitationLookupResult.java b/src/main/java/org/jabref/model/openoffice/style/CitationLookupResult.java index 866a0de34c3..b17ec100ee2 100644 --- a/src/main/java/org/jabref/model/openoffice/style/CitationLookupResult.java +++ b/src/main/java/org/jabref/model/openoffice/style/CitationLookupResult.java @@ -23,8 +23,7 @@ public CitationLookupResult(BibEntry entry, BibDatabase database) { * Consequently, {@code this.database.equals(that.database)} below * is equivalent to {@code this.database == that.database}. * - * Since within each GUI call we use a fixed list of - * databases, it is OK. + * Since within each GUI call we use a fixed list of databases, it is OK. */ @Override public boolean equals(Object o) { diff --git a/src/main/java/org/jabref/model/openoffice/style/CitationMarkerEntry.java b/src/main/java/org/jabref/model/openoffice/style/CitationMarkerEntry.java index 4ab2c626b68..5fcb9bcc1a8 100644 --- a/src/main/java/org/jabref/model/openoffice/style/CitationMarkerEntry.java +++ b/src/main/java/org/jabref/model/openoffice/style/CitationMarkerEntry.java @@ -5,8 +5,7 @@ import org.jabref.model.openoffice.ootext.OOText; /** - * This is what we need for createCitationMarker to produce author-year - * citation markers. + * This is what we need for createCitationMarker to produce author-year citation markers. */ public interface CitationMarkerEntry extends CitationMarkerNormEntry { @@ -17,14 +16,13 @@ public interface CitationMarkerEntry extends CitationMarkerNormEntry { /** * pageInfo for this citation, provided by the user. - * May be empty, for none. + * May be empty, for none. */ Optional getPageInfo(); /** - * @return true if this citation is the first appearance of the - * source cited. Some styles use different limit on the number of - * authors shown in this case. + * @return true if this citation is the first appearance of the source cited. Some styles use + * different limit on the number of authors shown in this case. */ boolean getIsFirstAppearanceOfSource(); } diff --git a/src/main/java/org/jabref/model/openoffice/style/CitationMarkerNormEntry.java b/src/main/java/org/jabref/model/openoffice/style/CitationMarkerNormEntry.java index e9df5ec961f..a2581b38b60 100644 --- a/src/main/java/org/jabref/model/openoffice/style/CitationMarkerNormEntry.java +++ b/src/main/java/org/jabref/model/openoffice/style/CitationMarkerNormEntry.java @@ -3,16 +3,13 @@ import java.util.Optional; /** - * This is what we need to produce normalized author-year citation - * markers. + * This is what we need to produce normalized author-year citation markers. */ public interface CitationMarkerNormEntry { /** Citation key. This is what we usually get from the document. * - * Used if getBibEntry() and/or getDatabase() returns - * empty, which indicates failure to lookup in the databases. - * + * Used if getLookupResult() returns empty, which indicates failure to lookup in the databases. */ String getCitationKey(); diff --git a/src/main/java/org/jabref/model/openoffice/style/CitationPath.java b/src/main/java/org/jabref/model/openoffice/style/CitationPath.java index 05aeb6547f6..0920ea20feb 100644 --- a/src/main/java/org/jabref/model/openoffice/style/CitationPath.java +++ b/src/main/java/org/jabref/model/openoffice/style/CitationPath.java @@ -1,8 +1,8 @@ package org.jabref.model.openoffice.style; /** - * Identifies a citation with the citation group containing it and - * its storage index within. + * Identifies a citation with the identifier of the citation group containing it and its storage + * index within. */ public class CitationPath { diff --git a/src/main/java/org/jabref/model/openoffice/style/CitedKey.java b/src/main/java/org/jabref/model/openoffice/style/CitedKey.java index 08b2ffb1bb8..f577867c539 100644 --- a/src/main/java/org/jabref/model/openoffice/style/CitedKey.java +++ b/src/main/java/org/jabref/model/openoffice/style/CitedKey.java @@ -11,9 +11,8 @@ /** * Cited keys are collected from the citations in citation groups. * - * They contain backreferences to the corresponding citations in - * {@code where}. This allows the extra information generated using - * CitedKeys to be distributed back to the in-text citations. + * They contain backreferences to the corresponding citations in {@code where}. This allows the + * extra information generated using CitedKeys to be distributed back to the in-text citations. */ public class CitedKey implements ComparableCitedKey, @@ -106,13 +105,13 @@ void addPath(CitationPath p, Citation cit) { // Check consistency if (!cit.getLookupResult().equals(this.db)) { - throw new RuntimeException("CitedKey.addPath: mismatch on cit.db"); + throw new IllegalStateException("CitedKey.addPath: mismatch on cit.db"); } if (!cit.getNumber().equals(this.number)) { - throw new RuntimeException("CitedKey.addPath: mismatch on cit.number"); + throw new IllegalStateException("CitedKey.addPath: mismatch on cit.number"); } if (!cit.getUniqueLetter().equals(this.uniqueLetter)) { - throw new RuntimeException("CitedKey.addPath: mismatch on cit.uniqueLetter"); + throw new IllegalStateException("CitedKey.addPath: mismatch on cit.uniqueLetter"); } } diff --git a/src/main/java/org/jabref/model/openoffice/style/CompareCitation.java b/src/main/java/org/jabref/model/openoffice/style/CompareCitation.java index 916f8ec610c..966715b11a8 100644 --- a/src/main/java/org/jabref/model/openoffice/style/CompareCitation.java +++ b/src/main/java/org/jabref/model/openoffice/style/CompareCitation.java @@ -5,8 +5,8 @@ import org.jabref.model.entry.BibEntry; /* - * Given a Comparator provide a Comparator - * that can handle unresolved citation keys and takes pageInfo into account. + * Given a Comparator provide a Comparator that can handle unresolved + * citation keys and takes pageInfo into account. */ public class CompareCitation implements Comparator { diff --git a/src/main/java/org/jabref/model/openoffice/style/CompareCitedKey.java b/src/main/java/org/jabref/model/openoffice/style/CompareCitedKey.java index 439d0556c2d..a5484560316 100644 --- a/src/main/java/org/jabref/model/openoffice/style/CompareCitedKey.java +++ b/src/main/java/org/jabref/model/openoffice/style/CompareCitedKey.java @@ -6,8 +6,8 @@ import org.jabref.model.entry.BibEntry; /* - * Given a Comparator provide a Comparator - * that also handles unresolved citation keys. + * Given a Comparator provide a Comparator that also handles + * unresolved citation keys. */ public class CompareCitedKey implements Comparator { diff --git a/src/main/java/org/jabref/model/openoffice/style/NonUniqueCitationMarker.java b/src/main/java/org/jabref/model/openoffice/style/NonUniqueCitationMarker.java index e7d988ee216..34300734d2f 100644 --- a/src/main/java/org/jabref/model/openoffice/style/NonUniqueCitationMarker.java +++ b/src/main/java/org/jabref/model/openoffice/style/NonUniqueCitationMarker.java @@ -1,15 +1,15 @@ package org.jabref.model.openoffice.style; /** - * What should createCitationMarker do if it discovers that - * uniqueLetters provided are not sufficient for unique presentation? + * What should createCitationMarker do if it discovers that uniqueLetters provided are not + * sufficient for unique presentation? */ public enum NonUniqueCitationMarker { /** Give an insufficient representation anyway. */ FORGIVEN, - /** Throw a RuntimeException */ + /** Throw an exception */ THROWS } diff --git a/src/main/java/org/jabref/model/openoffice/style/OODataModel.java b/src/main/java/org/jabref/model/openoffice/style/OODataModel.java index 50c3a9e4842..20b2365ebf2 100644 --- a/src/main/java/org/jabref/model/openoffice/style/OODataModel.java +++ b/src/main/java/org/jabref/model/openoffice/style/OODataModel.java @@ -9,19 +9,15 @@ /** What is the data stored? */ public enum OODataModel { - /** - * JabRef52: pageInfo belongs to CitationGroup, not Citation. - */ + /** JabRef52: pageInfo belongs to CitationGroup, not Citation. */ JabRef52, - /** - * JabRef53: pageInfo belongs to Citation. - */ - JabRef53; + /** JabRef60: pageInfo belongs to Citation. */ + JabRef60; /** * @param pageInfo Nullable. - * @return JabRef53 style pageInfo list with pageInfo in the last slot. + * @return JabRef60 style pageInfo list with pageInfo in the last slot. */ public static List> fakePageInfos(String pageInfo, int nCitations) { List> pageInfos = new ArrayList<>(nCitations); diff --git a/src/main/java/org/jabref/model/openoffice/style/PageInfo.java b/src/main/java/org/jabref/model/openoffice/style/PageInfo.java index 6807ecf0753..ace7eedae02 100644 --- a/src/main/java/org/jabref/model/openoffice/style/PageInfo.java +++ b/src/main/java/org/jabref/model/openoffice/style/PageInfo.java @@ -42,6 +42,6 @@ public static int comparePageInfo(Optional a, Optional b) { if (bb.isEmpty()) { return +1; } - return aa.get().asString().compareTo(bb.get().asString()); + return aa.get().toString().compareTo(bb.get().toString()); } } diff --git a/src/main/java/org/jabref/model/openoffice/uno/UnoBookmark.java b/src/main/java/org/jabref/model/openoffice/uno/UnoBookmark.java index b97618a3231..fdbbb0dd1c6 100644 --- a/src/main/java/org/jabref/model/openoffice/uno/UnoBookmark.java +++ b/src/main/java/org/jabref/model/openoffice/uno/UnoBookmark.java @@ -6,7 +6,6 @@ import com.sun.star.container.XNameAccess; import com.sun.star.container.XNamed; import com.sun.star.lang.DisposedException; -import com.sun.star.lang.IllegalArgumentException; import com.sun.star.lang.WrappedTargetException; import com.sun.star.text.XBookmarksSupplier; import com.sun.star.text.XTextContent; @@ -24,7 +23,7 @@ public static XNameAccess getNameAccess(XTextDocument doc) throws NoDocumentException { - XBookmarksSupplier supplier = UnoCast.unoQI(XBookmarksSupplier.class, doc); + XBookmarksSupplier supplier = UnoCast.cast(XBookmarksSupplier.class, doc).get(); try { return supplier.getBookmarks(); } catch (DisposedException ex) { @@ -44,45 +43,36 @@ public static Optional getAnchor(XTextDocument doc, String name) NoDocumentException { XNameAccess nameAccess = getNameAccess(doc); - return (UnoNameAccess.getTextContentByName(nameAccess, name) - .map(e -> e.getAnchor())); + return (UnoNameAccess.getTextContentByName(nameAccess, name).map(XTextContent::getAnchor)); } /** - * Insert a bookmark with the given name at the cursor provided, - * or with another name if the one we asked for is already in use. + * Insert a bookmark with the given name at the cursor provided, or with another name if the one + * we asked for is already in use. * * In LibreOffice the another name is in "{name}{number}" format. * * @param name For the bookmark. - * @param range Cursor marking the location or range for - * the bookmark. + * @param range Cursor marking the location or range for the bookmark. * @param absorb Shall we incorporate range? * * @return The XNamed interface of the bookmark. * - * result.getName() should be checked by the - * caller, because its name may differ from the one - * requested. + * result.getName() should be checked by the caller, because its name may differ from + * the one requested. */ public static XNamed create(XTextDocument doc, String name, XTextRange range, boolean absorb) throws - IllegalArgumentException, CreationException { - return UnoNamed.insertNamedTextContent(doc, - "com.sun.star.text.Bookmark", - name, - range, - absorb); + return UnoNamed.insertNamedTextContent(doc, "com.sun.star.text.Bookmark", name, range, absorb); } /** * Remove the named bookmark if it exists. */ - public static void remove(XTextDocument doc, String name) + public static void removeIfExists(XTextDocument doc, String name) throws NoDocumentException, - NoSuchElementException, WrappedTargetException { XNameAccess marks = UnoBookmark.getNameAccess(doc); @@ -92,7 +82,11 @@ public static void remove(XTextDocument doc, String name) if (mark.isEmpty()) { return; } - doc.getText().removeTextContent(mark.get()); + try { + doc.getText().removeTextContent(mark.get()); + } catch (NoSuchElementException ex) { + // The caller gets what it expects. + } } } } diff --git a/src/main/java/org/jabref/model/openoffice/uno/UnoCast.java b/src/main/java/org/jabref/model/openoffice/uno/UnoCast.java index df5be51db36..fcc1bcc3e12 100644 --- a/src/main/java/org/jabref/model/openoffice/uno/UnoCast.java +++ b/src/main/java/org/jabref/model/openoffice/uno/UnoCast.java @@ -9,16 +9,11 @@ public class UnoCast { private UnoCast() { } /** - * unoQI : short for UnoRuntime.queryInterface + * cast : short for Optional.ofNullable(UnoRuntime.queryInterface(...)) * - * @return A reference to the requested UNO interface type if available, - * otherwise null + * @return A reference to the requested UNO interface type if available, otherwise Optional.empty() */ - public static T unoQI(Class zInterface, Object object) { - return UnoRuntime.queryInterface(zInterface, object); - } - - public static Optional optUnoQI(Class zInterface, Object object) { + public static Optional cast(Class zInterface, Object object) { return Optional.ofNullable(UnoRuntime.queryInterface(zInterface, object)); } } diff --git a/src/main/java/org/jabref/model/openoffice/uno/UnoCrossRef.java b/src/main/java/org/jabref/model/openoffice/uno/UnoCrossRef.java index 0e8b84e8d22..37dc580a1fb 100644 --- a/src/main/java/org/jabref/model/openoffice/uno/UnoCrossRef.java +++ b/src/main/java/org/jabref/model/openoffice/uno/UnoCrossRef.java @@ -21,53 +21,67 @@ private UnoCrossRef() { } */ public static void refresh(XTextDocument doc) { // Refresh the document - XRefreshable xRefresh = UnoCast.unoQI(XRefreshable.class, doc); + XRefreshable xRefresh = UnoCast.cast(XRefreshable.class, doc).get(); xRefresh.refresh(); } /** - * Insert a clickable cross-reference to a reference mark, - * with a label containing the target's page number. + * Insert a clickable cross-reference to a reference mark, with a label containing the target's + * page number. * - * May need a documentConnection.refresh() after, to update - * the text shown. + * May need a documentConnection.refresh() after, to update the text shown. */ public static void insertReferenceToPageNumberOfReferenceMark(XTextDocument doc, String referenceMarkName, XTextRange cursor) throws CreationException, - UnknownPropertyException, - PropertyVetoException, WrappedTargetException { // based on: https://wiki.openoffice.org/wiki/Documentation/DevGuide/Text/Reference_Marks - XMultiServiceFactory msf = UnoCast.unoQI(XMultiServiceFactory.class, doc); + XMultiServiceFactory msf = UnoCast.cast(XMultiServiceFactory.class, doc).get(); // Create a 'GetReference' text field to refer to the reference mark we just inserted, // and get it's XPropertySet interface XPropertySet xFieldProps; try { String name = "com.sun.star.text.textfield.GetReference"; - xFieldProps = (XPropertySet) UnoCast.unoQI(XPropertySet.class, - msf.createInstance(name)); - } catch (Exception e) { + xFieldProps = UnoCast.cast(XPropertySet.class, msf.createInstance(name)).get(); + } catch (com.sun.star.uno.Exception e) { throw new CreationException(e.getMessage()); } - // Set the SourceName of the GetReference text field to the referenceMarkName - xFieldProps.setPropertyValue("SourceName", referenceMarkName); + try { + // Set the SourceName of the GetReference text field to the referenceMarkName + xFieldProps.setPropertyValue("SourceName", referenceMarkName); + } catch (UnknownPropertyException ex) { + throw new java.lang.IllegalStateException("The created GetReference does not have property 'SourceName'"); + } catch (PropertyVetoException ex) { + throw new java.lang.IllegalStateException("Caught PropertyVetoException on 'SourceName'"); + } - // specify that the source is a reference mark (could also be a footnote, - // bookmark or sequence field) - xFieldProps.setPropertyValue("ReferenceFieldSource", - new Short(ReferenceFieldSource.REFERENCE_MARK)); + try { + // specify that the source is a reference mark (could also be a footnote, + // bookmark or sequence field) + xFieldProps.setPropertyValue("ReferenceFieldSource", Short.valueOf​(ReferenceFieldSource.REFERENCE_MARK)); + } catch (UnknownPropertyException ex) { + throw new java.lang.IllegalStateException("The created GetReference does not have property" + + " 'ReferenceFieldSource'"); + } catch (PropertyVetoException ex) { + throw new java.lang.IllegalStateException("Caught PropertyVetoException on 'ReferenceFieldSource'"); + } - // We want the reference displayed as page number - xFieldProps.setPropertyValue("ReferenceFieldPart", - new Short(ReferenceFieldPart.PAGE)); + try { + // We want the reference displayed as page number + xFieldProps.setPropertyValue("ReferenceFieldPart", Short.valueOf​(ReferenceFieldPart.PAGE)); + } catch (UnknownPropertyException ex) { + throw new java.lang.IllegalStateException("The created GetReference does not have property" + + " 'ReferenceFieldPart'"); + } catch (PropertyVetoException ex) { + throw new java.lang.IllegalStateException("Caught PropertyVetoException on 'ReferenceFieldPart'"); + } // Get the XTextContent interface of the GetReference text field - XTextContent xRefContent = (XTextContent) UnoCast.unoQI(XTextContent.class, xFieldProps); + XTextContent xRefContent = UnoCast.cast(XTextContent.class, xFieldProps).get(); // Insert the text field cursor.getText().insertTextContent(cursor.getEnd(), xRefContent, false); diff --git a/src/main/java/org/jabref/model/openoffice/uno/UnoCursor.java b/src/main/java/org/jabref/model/openoffice/uno/UnoCursor.java index b7115feb8e1..1770b6957c5 100644 --- a/src/main/java/org/jabref/model/openoffice/uno/UnoCursor.java +++ b/src/main/java/org/jabref/model/openoffice/uno/UnoCursor.java @@ -18,8 +18,8 @@ private UnoCursor() { } */ public static Optional getViewCursor(XTextDocument doc) { return (UnoTextDocument.getCurrentController(doc) - .flatMap(e -> UnoCast.optUnoQI(XTextViewCursorSupplier.class, e)) - .map(e -> e.getViewCursor())); + .flatMap(e -> UnoCast.cast(XTextViewCursorSupplier.class, e)) + .map(XTextViewCursorSupplier::getViewCursor)); } /** diff --git a/src/main/java/org/jabref/model/openoffice/uno/UnoNameAccess.java b/src/main/java/org/jabref/model/openoffice/uno/UnoNameAccess.java index 3d172d11f14..243c136f382 100644 --- a/src/main/java/org/jabref/model/openoffice/uno/UnoNameAccess.java +++ b/src/main/java/org/jabref/model/openoffice/uno/UnoNameAccess.java @@ -12,14 +12,13 @@ public class UnoNameAccess { private UnoNameAccess() { } /** - * @return null if name not found, or if the result does not - * support the XTextContent interface. + * @return null if name not found, or if the result does not support the XTextContent interface. */ public static Optional getTextContentByName(XNameAccess nameAccess, String name) throws WrappedTargetException { try { - return UnoCast.optUnoQI(XTextContent.class, nameAccess.getByName(name)); + return UnoCast.cast(XTextContent.class, nameAccess.getByName(name)); } catch (NoSuchElementException ex) { return Optional.empty(); } diff --git a/src/main/java/org/jabref/model/openoffice/uno/UnoNamed.java b/src/main/java/org/jabref/model/openoffice/uno/UnoNamed.java index d99b6702153..5f1f50c873d 100644 --- a/src/main/java/org/jabref/model/openoffice/uno/UnoNamed.java +++ b/src/main/java/org/jabref/model/openoffice/uno/UnoNamed.java @@ -11,30 +11,24 @@ public class UnoNamed { private UnoNamed() { } /** - * Insert a new instance of a service at the provided cursor - * position. + * Insert a new instance of a service at the provided cursor position. * * @param service For example - * "com.sun.star.text.ReferenceMark", - * "com.sun.star.text.Bookmark" or - * "com.sun.star.text.TextSection". + * "com.sun.star.text.ReferenceMark", + * "com.sun.star.text.Bookmark" or + * "com.sun.star.text.TextSection". * - * Passed to this.asXMultiServiceFactory().createInstance(service) - * The result is expected to support the - * XNamed and XTextContent interfaces. + * Passed to this.asXMultiServiceFactory().createInstance(service) + * The result is expected to support the XNamed and XTextContent interfaces. * - * @param name For the ReferenceMark, Bookmark, TextSection. - * If the name is already in use, LibreOffice - * may change the name. + * @param name For the ReferenceMark, Bookmark, TextSection. + * If the name is already in use, LibreOffice may change the name. * - * @param range Marks the location or range for - * the thing to be inserted. + * @param range Marks the location or range for the thing to be inserted. * - * @param absorb ReferenceMark, Bookmark and TextSection can - * incorporate a text range. If absorb is true, - * the text in the range becomes part of the thing. - * If absorb is false, the thing is - * inserted at the end of the range. + * @param absorb ReferenceMark, Bookmark and TextSection can incorporate a text range. + * If absorb is true, the text in the range becomes part of the thing. + * If absorb is false, the thing is inserted at the end of the range. * * @return The XNamed interface, in case we need to check the actual name. * @@ -47,20 +41,22 @@ static XNamed insertNamedTextContent(XTextDocument doc, throws CreationException { - XMultiServiceFactory msf = UnoCast.unoQI(XMultiServiceFactory.class, doc); + XMultiServiceFactory msf = UnoCast.cast(XMultiServiceFactory.class, doc).get(); Object xObject; try { xObject = msf.createInstance(service); - } catch (Exception e) { + } catch (com.sun.star.uno.Exception e) { throw new CreationException(e.getMessage()); } - XNamed xNamed = UnoCast.unoQI(XNamed.class, xObject); + XNamed xNamed = (UnoCast.cast(XNamed.class, xObject) + .orElseThrow(() -> new IllegalArgumentException("Service is not an XNamed"))); xNamed.setName(name); // get XTextContent interface - XTextContent xTextContent = UnoCast.unoQI(XTextContent.class, xObject); + XTextContent xTextContent = (UnoCast.cast(XTextContent.class, xObject) + .orElseThrow(() -> new IllegalArgumentException("Service is not an XTextContent"))); range.getText().insertTextContent(range, xTextContent, absorb); return xNamed; } diff --git a/src/main/java/org/jabref/model/openoffice/uno/UnoProperties.java b/src/main/java/org/jabref/model/openoffice/uno/UnoProperties.java index 07a6de1374d..3a47818cf8b 100644 --- a/src/main/java/org/jabref/model/openoffice/uno/UnoProperties.java +++ b/src/main/java/org/jabref/model/openoffice/uno/UnoProperties.java @@ -26,7 +26,7 @@ private UnoProperties() { } */ public static Optional asPropertySet(XPropertyContainer propertyContainer) { - return UnoCast.optUnoQI(XPropertySet.class, propertyContainer); + return UnoCast.cast(XPropertySet.class, propertyContainer); } /* diff --git a/src/main/java/org/jabref/model/openoffice/uno/UnoRedlines.java b/src/main/java/org/jabref/model/openoffice/uno/UnoRedlines.java index a5f5ce7c6d1..2c408648d6f 100644 --- a/src/main/java/org/jabref/model/openoffice/uno/UnoRedlines.java +++ b/src/main/java/org/jabref/model/openoffice/uno/UnoRedlines.java @@ -1,5 +1,7 @@ package org.jabref.model.openoffice.uno; +import java.util.Optional; + import com.sun.star.beans.UnknownPropertyException; import com.sun.star.beans.XPropertySet; import com.sun.star.container.NoSuchElementException; @@ -16,25 +18,30 @@ public class UnoRedlines { public static boolean getRecordChanges(XTextDocument doc) throws - UnknownPropertyException, WrappedTargetException { // https://wiki.openoffice.org/wiki/Documentation/DevGuide/Text/Settings // "Properties of com.sun.star.text.TextDocument" - XPropertySet propertySet = (UnoCast.optUnoQI(XPropertySet.class, doc) - .orElseThrow(RuntimeException::new)); + XPropertySet propertySet = UnoCast.cast(XPropertySet.class, doc).get(); - return (boolean) propertySet.getPropertyValue("RecordChanges"); + try { + return (boolean) propertySet.getPropertyValue("RecordChanges"); + } catch (UnknownPropertyException ex) { + throw new java.lang.IllegalStateException("Caught UnknownPropertyException on 'RecordChanges'"); + } } - private static XRedlinesSupplier getRedlinesSupplier(XTextDocument doc) { - return UnoCast.unoQI(XRedlinesSupplier.class, doc); + private static Optional getRedlinesSupplier(XTextDocument doc) { + return UnoCast.cast(XRedlinesSupplier.class, doc); } public static int countRedlines(XTextDocument doc) { - XRedlinesSupplier supplier = getRedlinesSupplier(doc); - XEnumerationAccess enumerationAccess = supplier.getRedlines(); + Optional supplier = getRedlinesSupplier(doc); + if (supplier.isEmpty()) { + return 0; + } + XEnumerationAccess enumerationAccess = supplier.get().getRedlines(); XEnumeration enumeration = enumerationAccess.createEnumeration(); if (enumeration == null) { return 0; diff --git a/src/main/java/org/jabref/model/openoffice/uno/UnoReferenceMark.java b/src/main/java/org/jabref/model/openoffice/uno/UnoReferenceMark.java index 8185c1ffbe3..7a719042d79 100644 --- a/src/main/java/org/jabref/model/openoffice/uno/UnoReferenceMark.java +++ b/src/main/java/org/jabref/model/openoffice/uno/UnoReferenceMark.java @@ -22,15 +22,14 @@ private UnoReferenceMark() { } /** * @throws NoDocumentException If cannot get reference marks * - * Note: also used by `isDocumentConnectionMissing` to test if - * we have a working connection. + * Note: also used by `isDocumentConnectionMissing` to test if we have a working connection. * */ public static XNameAccess getNameAccess(XTextDocument doc) throws NoDocumentException { - XReferenceMarksSupplier supplier = UnoCast.unoQI(XReferenceMarksSupplier.class, doc); + XReferenceMarksSupplier supplier = UnoCast.cast(XReferenceMarksSupplier.class, doc).get(); try { return supplier.getReferenceMarks(); @@ -60,11 +59,10 @@ public static List getListOfNames(XTextDocument doc) * * Removes both the text and the mark itself. */ - public static void remove(XTextDocument doc, String name) + public static void removeIfExists(XTextDocument doc, String name) throws WrappedTargetException, - NoDocumentException, - NoSuchElementException { + NoDocumentException { XNameAccess xReferenceMarks = UnoReferenceMark.getNameAccess(doc); @@ -73,7 +71,11 @@ public static void remove(XTextDocument doc, String name) if (mark.isEmpty()) { return; } - doc.getText().removeTextContent(mark.get()); + try { + doc.getText().removeTextContent(mark.get()); + } catch (NoSuchElementException ex) { + // The caller gets what it expects. + } } } @@ -97,37 +99,27 @@ public static Optional getAnchor(XTextDocument doc, String name) NoDocumentException, WrappedTargetException { return (UnoReferenceMark.getAsTextContent(doc, name) - .map(e -> e.getAnchor())); + .map(XTextContent::getAnchor)); } /** - * Insert a new reference mark at the provided cursor - * position. + * Insert a new reference mark at the provided cursor position. * - * If {@code absorb} is true, the text in the cursor range will become - * the text with gray background. + * If {@code absorb} is true, the text in the cursor range will become the text with gray + * background. * - * Note: LibreOffice 6.4.6.2 will create multiple reference marks - * with the same name without error or renaming. - * Its GUI does not allow this, - * but we can create them programmatically. - * In the GUI, clicking on any of those identical names - * will move the cursor to the same mark. + * Note: LibreOffice 6.4.6.2 will create multiple reference marks with the same name without + * error or renaming. + * Its GUI does not allow this, but we can create them programmatically. + * In the GUI, clicking on any of those identical names will move the cursor to the same + * mark. * * @param name For the reference mark. - * @param range Cursor marking the location or range for - * the reference mark. + * @param range Cursor marking the location or range for the reference mark. */ - public static XNamed create(XTextDocument doc, - String name, - XTextRange range, - boolean absorb) + public static XNamed create(XTextDocument doc, String name, XTextRange range, boolean absorb) throws CreationException { - return UnoNamed.insertNamedTextContent(doc, - "com.sun.star.text.ReferenceMark", - name, - range, - absorb); + return UnoNamed.insertNamedTextContent(doc, "com.sun.star.text.ReferenceMark", name, range, absorb); } } diff --git a/src/main/java/org/jabref/model/openoffice/uno/UnoSelection.java b/src/main/java/org/jabref/model/openoffice/uno/UnoSelection.java index 0ef750b9983..43d89ed157b 100644 --- a/src/main/java/org/jabref/model/openoffice/uno/UnoSelection.java +++ b/src/main/java/org/jabref/model/openoffice/uno/UnoSelection.java @@ -29,12 +29,12 @@ private static Optional getSelectionSupplier(XTextDocument d LOGGER.warn("UnoSelection.getSelectionSupplier: getCurrentController(doc) returned empty"); return Optional.empty(); } - XSelectionSupplier supplier = UnoCast.unoQI(XSelectionSupplier.class, controller.get()); - if (supplier == null) { - LOGGER.warn("UnoSelection.getSelectionSupplier: unoQI(XSelectionSupplier) returned null"); + Optional supplier = UnoCast.cast(XSelectionSupplier.class, controller.get()); + if (supplier.isEmpty()) { + LOGGER.warn("UnoSelection.getSelectionSupplier: cast to XSelectionSupplier returned empty"); return Optional.empty(); } - return Optional.of(supplier); + return supplier; } /** @@ -91,12 +91,12 @@ public static Optional getSelectionAsXServiceInfo(XTextDocument do if (selection == null) { return Optional.empty(); } - XServiceInfo result = UnoCast.unoQI(XServiceInfo.class, selection); - if (result == null) { - LOGGER.warn("unoQI(XServiceInfo) returned null"); + Optional result = UnoCast.cast(XServiceInfo.class, selection); + if (result.isEmpty()) { + LOGGER.warn("cast to XServiceInfo returned empty"); return Optional.empty(); } - return Optional.of(result); + return result; } /** diff --git a/src/main/java/org/jabref/model/openoffice/uno/UnoStyle.java b/src/main/java/org/jabref/model/openoffice/uno/UnoStyle.java index 86222872597..dece8978713 100644 --- a/src/main/java/org/jabref/model/openoffice/uno/UnoStyle.java +++ b/src/main/java/org/jabref/model/openoffice/uno/UnoStyle.java @@ -20,26 +20,23 @@ public class UnoStyle { private UnoStyle() { } - private static Optional getStyleFromFamily(XTextDocument doc, - String familyName, - String styleName) + private static Optional getStyleFromFamily(XTextDocument doc, String familyName, String styleName) throws WrappedTargetException { - XStyleFamiliesSupplier fss = UnoCast.unoQI(XStyleFamiliesSupplier.class, doc); - XNameAccess fs = UnoCast.unoQI(XNameAccess.class, fss.getStyleFamilies()); + XStyleFamiliesSupplier fss = UnoCast.cast(XStyleFamiliesSupplier.class, doc).get(); + XNameAccess fs = UnoCast.cast(XNameAccess.class, fss.getStyleFamilies()).get(); XNameContainer xFamily; try { - xFamily = UnoCast.unoQI(XNameContainer.class, fs.getByName(familyName)); + xFamily = UnoCast.cast(XNameContainer.class, fs.getByName(familyName)).get(); } catch (NoSuchElementException ex) { String msg = String.format("Style family name '%s' is not recognized", familyName); - throw new RuntimeException(msg, ex); + throw new java.lang.IllegalArgumentException(msg, ex); } try { - Object s = xFamily.getByName(styleName); - XStyle xs = (XStyle) UnoCast.unoQI(XStyle.class, s); - return Optional.ofNullable(xs); + Object style = xFamily.getByName(styleName); + return UnoCast.cast(XStyle.class, style); } catch (NoSuchElementException ex) { return Optional.empty(); } @@ -62,7 +59,7 @@ public static Optional getInternalNameOfStyle(XTextDocument doc, String throws WrappedTargetException { return (getStyleFromFamily(doc, familyName, name) - .map(e -> e.getName())); + .map(XStyle::getName)); } public static Optional getInternalNameOfParagraphStyle(XTextDocument doc, String name) diff --git a/src/main/java/org/jabref/model/openoffice/uno/UnoTextDocument.java b/src/main/java/org/jabref/model/openoffice/uno/UnoTextDocument.java index 188a092465a..c7e94e6bb4c 100644 --- a/src/main/java/org/jabref/model/openoffice/uno/UnoTextDocument.java +++ b/src/main/java/org/jabref/model/openoffice/uno/UnoTextDocument.java @@ -59,19 +59,18 @@ public static Optional getCurrentController(XTextDocument doc) { */ public static Optional getFrameTitle(XTextDocument doc) { - Optional frame = getCurrentController(doc).map(e -> e.getFrame()); + Optional frame = getCurrentController(doc).map(XController::getFrame); if (frame.isEmpty()) { return Optional.empty(); } - Optional propertySet = UnoCast.optUnoQI(XPropertySet.class, frame.get()); + Optional propertySet = UnoCast.cast(XPropertySet.class, frame.get()); if (propertySet.isEmpty()) { return Optional.empty(); } try { - Optional frameTitleObj = - UnoProperties.getValueAsObject(propertySet.get(), "Title"); + Optional frameTitleObj = UnoProperties.getValueAsObject(propertySet.get(), "Title"); if (frameTitleObj.isEmpty()) { return Optional.empty(); } @@ -85,8 +84,8 @@ public static Optional getFrameTitle(XTextDocument doc) { static Optional getDocumentProperties(XTextDocument doc) { return (Optional.ofNullable(doc) - .map(e -> UnoCast.unoQI(XDocumentPropertiesSupplier.class, e)) - .map(e -> e.getDocumentProperties())); + .flatMap(e -> UnoCast.cast(XDocumentPropertiesSupplier.class, e)) + .map(XDocumentPropertiesSupplier::getDocumentProperties)); } } diff --git a/src/main/java/org/jabref/model/openoffice/uno/UnoTextRange.java b/src/main/java/org/jabref/model/openoffice/uno/UnoTextRange.java index b44ed6ce38b..56cb1498e2a 100644 --- a/src/main/java/org/jabref/model/openoffice/uno/UnoTextRange.java +++ b/src/main/java/org/jabref/model/openoffice/uno/UnoTextRange.java @@ -17,12 +17,12 @@ private UnoTextRange() { } * Returns Optional.empty if not in a footnote. */ public static Optional getFootnoteMarkRange(XTextRange original) { - XFootnote footer = UnoCast.unoQI(XFootnote.class, original.getText()); - if (footer != null) { + Optional footer = UnoCast.cast(XFootnote.class, original.getText()); + if (footer.isPresent()) { // If we are inside a footnote, // find the linking footnote marker: // The footnote's anchor gives the correct position in the text: - return Optional.ofNullable(footer.getAnchor()); + return Optional.ofNullable(footer.get().getAnchor()); } return Optional.empty(); } @@ -46,9 +46,9 @@ public static int compareStartsUnsafe(XTextRangeCompare compare, XTextRange a, X public static int compareStarts(XTextRange a, XTextRange b) { if (!comparables(a, b)) { - throw new RuntimeException("compareStarts: got incomparable regions"); + throw new java.lang.IllegalArgumentException("compareStarts: got incomparable regions"); } - final XTextRangeCompare compare = UnoCast.unoQI(XTextRangeCompare.class, a.getText()); + final XTextRangeCompare compare = UnoCast.cast(XTextRangeCompare.class, a.getText()).get(); return compareStartsUnsafe(compare, a, b); } @@ -59,9 +59,9 @@ public static int compareStarts(XTextRange a, XTextRange b) { */ public static int compareEnds(XTextRange a, XTextRange b) { if (!comparables(a, b)) { - throw new RuntimeException("compareEnds: got incomparable regions"); + throw new java.lang.IllegalArgumentException("compareEnds: got incomparable regions"); } - final XTextRangeCompare compare = UnoCast.unoQI(XTextRangeCompare.class, a.getText()); + final XTextRangeCompare compare = UnoCast.cast(XTextRangeCompare.class, a.getText()).get(); return (-1) * compare.compareRegionEnds(a, b); } @@ -78,9 +78,9 @@ public static int compareStartsThenEndsUnsafe(XTextRangeCompare compare, XTextRa public static int compareStartsThenEnds(XTextRange a, XTextRange b) { if (!comparables(a, b)) { - throw new RuntimeException("compareStartsThenEnds: got incomparable regions"); + throw new java.lang.IllegalArgumentException("compareStartsThenEnds: got incomparable regions"); } - final XTextRangeCompare compare = UnoCast.unoQI(XTextRangeCompare.class, a.getText()); + final XTextRangeCompare compare = UnoCast.cast(XTextRangeCompare.class, a.getText()).get(); return compareStartsThenEndsUnsafe(compare, a, b); } } diff --git a/src/main/java/org/jabref/model/openoffice/uno/UnoTextSection.java b/src/main/java/org/jabref/model/openoffice/uno/UnoTextSection.java index 107e7a0e018..3b9797f553e 100644 --- a/src/main/java/org/jabref/model/openoffice/uno/UnoTextSection.java +++ b/src/main/java/org/jabref/model/openoffice/uno/UnoTextSection.java @@ -6,8 +6,8 @@ import com.sun.star.container.XNameAccess; import com.sun.star.container.XNamed; import com.sun.star.lang.DisposedException; -import com.sun.star.lang.IllegalArgumentException; import com.sun.star.lang.WrappedTargetException; +import com.sun.star.text.XTextContent; import com.sun.star.text.XTextDocument; import com.sun.star.text.XTextRange; import com.sun.star.text.XTextSection; @@ -23,7 +23,7 @@ public static XNameAccess getNameAccess(XTextDocument doc) throws NoDocumentException { - XTextSectionsSupplier supplier = UnoCast.unoQI(XTextSectionsSupplier.class, doc); + XTextSectionsSupplier supplier = UnoCast.cast(XTextSectionsSupplier.class, doc).get(); try { return supplier.getTextSections(); } catch (DisposedException ex) { @@ -40,9 +40,7 @@ public static Optional getByName(XTextDocument doc, String name) NoDocumentException { XNameAccess nameAccess = getNameAccess(doc); try { - return Optional.ofNullable((XTextSection) - ((Any) nameAccess.getByName(name)) - .getObject()); + return Optional.ofNullable((XTextSection) ((Any) nameAccess.getByName(name)).getObject()); } catch (NoSuchElementException ex) { return Optional.empty(); } @@ -60,31 +58,23 @@ public static Optional getAnchor(XTextDocument doc, String name) NoDocumentException { XNameAccess nameAccess = getNameAccess(doc); - return (UnoNameAccess.getTextContentByName(nameAccess, name) - .map(e -> e.getAnchor())); + return (UnoNameAccess.getTextContentByName(nameAccess, name).map(XTextContent::getAnchor)); } /** - * Create a text section with the provided name and insert it at - * the provided cursor. + * Create a text section with the provided name and insert it at the provided cursor. * * @param name The desired name for the section. * @param range The location to insert at. * - * If an XTextSection by that name already exists, - * LibreOffice (6.4.6.2) creates a section with a name different from - * what we requested, in "Section {number}" format. + * If an XTextSection by that name already exists, LibreOffice (6.4.6.2) creates a section with + * a name different from what we requested, in "Section {number}" format. */ public static XNamed create(XTextDocument doc, String name, XTextRange range, boolean absorb) throws - IllegalArgumentException, CreationException { - return UnoNamed.insertNamedTextContent(doc, - "com.sun.star.text.TextSection", - name, - range, - absorb); + return UnoNamed.insertNamedTextContent(doc, "com.sun.star.text.TextSection", name, range, absorb); } } diff --git a/src/main/java/org/jabref/model/openoffice/uno/UnoUndo.java b/src/main/java/org/jabref/model/openoffice/uno/UnoUndo.java index 7f41515dd1c..b75bee17f3e 100644 --- a/src/main/java/org/jabref/model/openoffice/uno/UnoUndo.java +++ b/src/main/java/org/jabref/model/openoffice/uno/UnoUndo.java @@ -16,14 +16,13 @@ private UnoUndo() { } public static Optional getXUndoManager(XTextDocument doc) { // https://www.openoffice.org/api/docs/common/ref/com/sun/star/document/XUndoManager.html - return (UnoCast.optUnoQI(XUndoManagerSupplier.class, doc) - .map(e -> e.getUndoManager())); + return (UnoCast.cast(XUndoManagerSupplier.class, doc) + .map(XUndoManagerSupplier::getUndoManager)); } /** - * Each call to enterUndoContext must be paired by a call to - * leaveUndoContext, otherwise, the document's undo stack is - * left in an inconsistent state. + * Each call to enterUndoContext must be paired by a call to leaveUndoContext, otherwise, the + * document's undo stack is left in an inconsistent state. */ public static void enterUndoContext(XTextDocument doc, String title) { Optional um = getXUndoManager(doc); @@ -38,7 +37,7 @@ public static void leaveUndoContext(XTextDocument doc) { try { um.get().leaveUndoContext(); } catch (InvalidStateException ex) { - throw new RuntimeException("leaveUndoContext reported InvalidStateException"); + throw new IllegalStateException("leaveUndoContext reported InvalidStateException"); } } } diff --git a/src/main/java/org/jabref/model/openoffice/uno/UnoUserDefinedProperty.java b/src/main/java/org/jabref/model/openoffice/uno/UnoUserDefinedProperty.java index dd09ae4745a..634edb0c57b 100644 --- a/src/main/java/org/jabref/model/openoffice/uno/UnoUserDefinedProperty.java +++ b/src/main/java/org/jabref/model/openoffice/uno/UnoUserDefinedProperty.java @@ -7,13 +7,14 @@ import com.sun.star.beans.IllegalTypeException; import com.sun.star.beans.NotRemoveableException; +import com.sun.star.beans.PropertyAttribute; import com.sun.star.beans.PropertyExistException; import com.sun.star.beans.PropertyVetoException; import com.sun.star.beans.UnknownPropertyException; import com.sun.star.beans.XPropertyContainer; import com.sun.star.beans.XPropertySet; import com.sun.star.beans.XPropertySetInfo; -import com.sun.star.lang.IllegalArgumentException; +import com.sun.star.document.XDocumentProperties; import com.sun.star.lang.WrappedTargetException; import com.sun.star.text.XTextDocument; import com.sun.star.uno.Any; @@ -33,7 +34,7 @@ public class UnoUserDefinedProperty { private UnoUserDefinedProperty() { } public static Optional getPropertyContainer(XTextDocument doc) { - return UnoTextDocument.getDocumentProperties(doc).map(e -> e.getUserDefinedProperties()); + return UnoTextDocument.getDocumentProperties(doc).map(XDocumentProperties::getUserDefinedProperties); } public static List getListOfNames(XTextDocument doc) { @@ -43,13 +44,12 @@ public static List getListOfNames(XTextDocument doc) { } /** - * @param property Name of a custom document property in the - * current document. + * @param property Name of a custom document property in the current document. * * @return The value of the property or Optional.empty() * - * These properties are used to store extra data about - * individual citation. In particular, the `pageInfo` part. + * These properties are used to store extra data about individual citation. + * In particular, the `pageInfo` part. * */ public static Optional getStringValue(XTextDocument doc, String property) @@ -58,7 +58,7 @@ public static Optional getStringValue(XTextDocument doc, String property Optional propertySet = (UnoUserDefinedProperty.getPropertyContainer(doc) .flatMap(UnoProperties::asPropertySet)); if (propertySet.isEmpty()) { - throw new RuntimeException("getting UserDefinedProperties as XPropertySet failed"); + throw new java.lang.IllegalArgumentException("getting UserDefinedProperties as XPropertySet failed"); } try { String v = propertySet.get().getPropertyValue(property).toString(); @@ -69,17 +69,14 @@ public static Optional getStringValue(XTextDocument doc, String property } /** - * @param property Name of a custom document property in the - * current document. Created if does not exist yet. + * @param property Name of a custom document property in the current document. + * Created if does not exist yet. * * @param value The value to be stored. */ - public static void createStringProperty(XTextDocument doc, String property, String value) + public static void setStringProperty(XTextDocument doc, String property, String value) throws - NotRemoveableException, - PropertyExistException, IllegalTypeException, - IllegalArgumentException, PropertyVetoException, WrappedTargetException { @@ -89,12 +86,12 @@ public static void createStringProperty(XTextDocument doc, String property, Stri Optional container = UnoUserDefinedProperty.getPropertyContainer(doc); if (container.isEmpty()) { - throw new RuntimeException("UnoUserDefinedProperty.getPropertyContainer failed"); + throw new java.lang.IllegalArgumentException("UnoUserDefinedProperty.getPropertyContainer failed"); } Optional propertySet = container.flatMap(UnoProperties::asPropertySet); if (propertySet.isEmpty()) { - throw new RuntimeException("asPropertySet failed"); + throw new java.lang.IllegalArgumentException("asPropertySet failed"); } XPropertySetInfo propertySetInfo = propertySet.get().getPropertySetInfo(); @@ -108,60 +105,53 @@ public static void createStringProperty(XTextDocument doc, String property, Stri } } - container.get().addProperty(property, - com.sun.star.beans.PropertyAttribute.REMOVEABLE, - new Any(Type.STRING, value)); + try { + container.get().addProperty(property, PropertyAttribute.REMOVEABLE, new Any(Type.STRING, value)); + } catch (PropertyExistException ex) { + throw new java.lang.IllegalStateException("Caught PropertyExistException for property assumed not to exist"); + } } /** - * @param property Name of a custom document property in the - * current document. + * @param property Name of a custom document property in the current document. * * Logs warning if does not exist. */ public static void remove(XTextDocument doc, String property) throws - NotRemoveableException, - PropertyExistException, - IllegalTypeException, - IllegalArgumentException { + NotRemoveableException { Objects.requireNonNull(property); Optional container = UnoUserDefinedProperty.getPropertyContainer(doc); if (container.isEmpty()) { - throw new RuntimeException("getUserDefinedPropertiesAsXPropertyContainer failed"); + throw new java.lang.IllegalArgumentException("getUserDefinedPropertiesAsXPropertyContainer failed"); } try { container.get().removeProperty(property); } catch (UnknownPropertyException ex) { - LOGGER.warn(String.format("UnoUserDefinedProperty.remove(%s)" - + " This property was not there to remove", + LOGGER.warn(String.format("UnoUserDefinedProperty.remove(%s) This property was not there to remove", property)); } } /** - * @param property Name of a custom document property in the - * current document. + * @param property Name of a custom document property in the current document. * * Keep silent if property did not exist. */ public static void removeIfExists(XTextDocument doc, String property) throws - NotRemoveableException, - PropertyExistException, - IllegalTypeException, - IllegalArgumentException { + NotRemoveableException { Objects.requireNonNull(property); Optional container = UnoUserDefinedProperty.getPropertyContainer(doc); if (container.isEmpty()) { - throw new RuntimeException("getUserDefinedPropertiesAsXPropertyContainer failed"); + throw new java.lang.IllegalArgumentException("getUserDefinedPropertiesAsXPropertyContainer failed"); } try { diff --git a/src/main/java/org/jabref/model/openoffice/util/OOResult.java b/src/main/java/org/jabref/model/openoffice/util/OOResult.java index 2be478f6d8a..c90bc68ad13 100644 --- a/src/main/java/org/jabref/model/openoffice/util/OOResult.java +++ b/src/main/java/org/jabref/model/openoffice/util/OOResult.java @@ -1,50 +1,39 @@ package org.jabref.model.openoffice.util; +import java.util.NoSuchElementException; import java.util.Optional; import java.util.function.Consumer; import java.util.function.Function; /* - * error cannot be null - * result cannot be null + * An instance of this class represents either the result of a computation, or an error + * value. Neither of these is allowed to be null. * * Void is not allowed for R, use OOVoidResult instead. * * Out of `isPresent()` and `isError()` exactly one is true. */ public class OOResult { + private final Optional result; private final Optional error; /** * Exactly one of the arguments should be Optional.empty() - * - * @param result - * @param error */ private OOResult(Optional result, Optional error) { this.result = result; this.error = error; } - /** - * @param result Null is not allowed. - */ public static OOResult ok(R result) { return new OOResult<>(Optional.of(result), Optional.empty()); } - /** - * @param error Null is not allowed. - */ public static OOResult error(E error) { return new OOResult<>(Optional.empty(), Optional.of(error)); } - /* - * Test state - */ - public boolean isPresent() { return result.isPresent(); } @@ -61,13 +50,9 @@ public boolean isOK() { return !isError(); } - /* - * getters - */ - public R get() { if (isError()) { - throw new RuntimeException("Cannot get from error"); + throw new NoSuchElementException("Cannot get from error"); } return result.get(); } @@ -76,10 +61,6 @@ public E getError() { return error.get(); } - /* - * Conditionals - */ - public OOResult ifPresent(Consumer fun) { if (isPresent()) { fun.accept(get()); diff --git a/src/main/java/org/jabref/model/openoffice/util/OOTuple3.java b/src/main/java/org/jabref/model/openoffice/util/OOTuple3.java index ca324ebc4ad..8742e06764f 100644 --- a/src/main/java/org/jabref/model/openoffice/util/OOTuple3.java +++ b/src/main/java/org/jabref/model/openoffice/util/OOTuple3.java @@ -1,9 +1,20 @@ package org.jabref.model.openoffice.util; +/** + * This class allows three objects to be packed together, and later accessed as fields `a`, `b` and + * `c`. + * + * Can be used to avoid creating a new class for just this purpose. + * + * Can be useful if you do not have `Trifunction` at hand but need to pass three objects at a time. + * + */ public class OOTuple3 { + public final A a; public final B b; public final C c; + public OOTuple3(A a, B b, C c) { this.a = a; this.b = b; diff --git a/src/test/java/org/jabref/logic/openoffice/style/OOBibStyleTest.java b/src/test/java/org/jabref/logic/openoffice/style/OOBibStyleTest.java index 03a0b535e34..82d7888afd8 100644 --- a/src/test/java/org/jabref/logic/openoffice/style/OOBibStyleTest.java +++ b/src/test/java/org/jabref/logic/openoffice/style/OOBibStyleTest.java @@ -109,7 +109,7 @@ static String runGetNumCitationMarker2b(OOBibStyle style, CitationMarkerNumericEntry... s) { List input = Stream.of(s).collect(Collectors.toList()); OOText res = style.getNumCitationMarker2(input, minGroupingCount); - return res.asString(); + return res.toString(); } static CitationMarkerEntry makeCitationMarkerEntry(BibEntry entry, @@ -178,7 +178,7 @@ void testGetNumCitationMarker() throws IOException { CitationMarkerNumericEntry e2 = numEntry("key", 1, "pp. 55-56"); assertEquals(true, e2.getPageInfo().isPresent()); - assertEquals("pp. 55-56", e2.getPageInfo().get().asString()); + assertEquals("pp. 55-56", e2.getPageInfo().get().toString()); citation = runGetNumCitationMarker2b(style, -1, e2); assertEquals("[1; pp. 55-56]", citation); @@ -250,7 +250,7 @@ void testGetNumCitationMarkerUndefined() throws IOException { */ CitationMarkerNumericBibEntry x = numBibEntry("key", Optional.empty()); assertEquals("[" + OOBibStyle.UNDEFINED_CITATION_MARKER + "key" + "] ", - style.getNumCitationMarkerForBibliography(x).asString()); + style.getNumCitationMarkerForBibliography(x).toString()); } @Test @@ -761,13 +761,13 @@ void testGetCitationMarkerJoinFirst() throws IOException { + "; Boström, Wäyrynen, Bodén, Beznosov & NotKruchten, 2006c]", style.createCitationMarker(citationMarkerEntries, true, - NonUniqueCitationMarker.THROWS).asString()); + NonUniqueCitationMarker.THROWS).toString()); assertEquals("Boström, Wäyrynen, Bodén, Beznosov & Kruchten [2006a,b]" + "; Boström, Wäyrynen, Bodén, Beznosov & NotKruchten [2006c]", style.createCitationMarker(citationMarkerEntries, false, - NonUniqueCitationMarker.THROWS).asString()); + NonUniqueCitationMarker.THROWS).toString()); } // Without pageInfo, only the first is isFirstAppearanceOfSource. @@ -789,7 +789,7 @@ void testGetCitationMarkerJoinFirst() throws IOException { + "; Boström et al., 2006c]", style.createCitationMarker(citationMarkerEntries, true, - NonUniqueCitationMarker.THROWS).asString()); + NonUniqueCitationMarker.THROWS).toString()); } // Without pageInfo, only the second is isFirstAppearanceOfSource. @@ -813,7 +813,7 @@ void testGetCitationMarkerJoinFirst() throws IOException { + "; Boström et al., 2006c]", style.createCitationMarker(citationMarkerEntries, true, - NonUniqueCitationMarker.THROWS).asString()); + NonUniqueCitationMarker.THROWS).toString()); } // Without pageInfo, neither is isFirstAppearanceOfSource. @@ -835,7 +835,7 @@ void testGetCitationMarkerJoinFirst() throws IOException { assertEquals("[Boström et al., 2006a,b,c]", style.createCitationMarker(citationMarkerEntries, true, - NonUniqueCitationMarker.THROWS).asString()); + NonUniqueCitationMarker.THROWS).toString()); } // With pageInfo: different entries with identical non-null pageInfo: not joined. @@ -857,7 +857,7 @@ void testGetCitationMarkerJoinFirst() throws IOException { + "; Boström et al., 2006c; p1]", style.createCitationMarker(citationMarkerEntries, true, - NonUniqueCitationMarker.THROWS).asString()); + NonUniqueCitationMarker.THROWS).toString()); } // With pageInfo: same entries with identical non-null pageInfo: collapsed. @@ -878,7 +878,7 @@ void testGetCitationMarkerJoinFirst() throws IOException { assertEquals("[Boström et al., 2006a; p1]", style.createCitationMarker(citationMarkerEntries, true, - NonUniqueCitationMarker.THROWS).asString()); + NonUniqueCitationMarker.THROWS).toString()); } // With pageInfo: same entries with different pageInfo: kept separate. // Empty ("") and missing pageInfos considered equal, thus collapsed. @@ -902,7 +902,7 @@ void testGetCitationMarkerJoinFirst() throws IOException { + "; Boström et al., 2006a]", style.createCitationMarker(citationMarkerEntries, true, - NonUniqueCitationMarker.THROWS).asString()); + NonUniqueCitationMarker.THROWS).toString()); } } } diff --git a/src/test/java/org/jabref/logic/openoffice/style/OOBibStyleTestHelper.java b/src/test/java/org/jabref/logic/openoffice/style/OOBibStyleTestHelper.java index 9fbe9579ccb..ae5e83abd2f 100644 --- a/src/test/java/org/jabref/logic/openoffice/style/OOBibStyleTestHelper.java +++ b/src/test/java/org/jabref/logic/openoffice/style/OOBibStyleTestHelper.java @@ -98,14 +98,14 @@ static String runGetNumCitationMarker2a(OOBibStyle style, List num, int minGroupingCount, boolean inList) { if (inList) { if (num.size() != 1) { - throw new RuntimeException("Numeric label for the bibliography with " - + String.valueOf(num.size()) + " numbers?"); + throw new IllegalArgumentException("Numeric label for the bibliography with " + + String.valueOf(num.size()) + " numbers?"); } int n = num.get(0); CitationMarkerNumericBibEntryImpl x = new CitationMarkerNumericBibEntryImpl("key", (n == 0) ? Optional.empty() : Optional.of(n)); - return style.getNumCitationMarkerForBibliography(x).asString(); + return style.getNumCitationMarkerForBibliography(x).toString(); } else { List input = num.stream() @@ -114,7 +114,7 @@ static String runGetNumCitationMarker2a(OOBibStyle style, n, Optional.empty())) .collect(Collectors.toList()); - return style.getNumCitationMarker2(input, minGroupingCount).asString(); + return style.getNumCitationMarker2(input, minGroupingCount).toString(); } } @@ -131,7 +131,7 @@ static String runGetNumCitationMarker2b(OOBibStyle style, CitationMarkerNumericEntry... s) { List input = Stream.of(s).collect(Collectors.toList()); OOText res = style.getNumCitationMarker2(input, minGroupingCount); - return res.asString(); + return res.toString(); } /* @@ -146,8 +146,8 @@ static CitationMarkerEntry makeCitationMarkerEntry(BibEntry entry, String uniqueLetterQ, String pageInfoQ, boolean isFirstAppearanceOfSource) { - if (!entry.getCitationKey().isPresent()) { - throw new RuntimeException("!entry.getCitationKey().isPresent()"); + if (entry.getCitationKey().isEmpty()) { + throw new IllegalArgumentException("entry.getCitationKey() is empty"); } String citationKey = entry.getCitationKey().get(); Citation result = new Citation(citationKey); @@ -194,7 +194,7 @@ static String getCitationMarker2(OOBibStyle style, } return style.createCitationMarker(citationMarkerEntries, inParenthesis, - NonUniqueCitationMarker.THROWS).asString(); + NonUniqueCitationMarker.THROWS).toString(); } /* From b9491d8aaa55f7b8cfc70b19ca6f480201176909 Mon Sep 17 00:00:00 2001 From: Antal K Date: Sat, 19 Jun 2021 15:23:28 +0200 Subject: [PATCH 25/51] not using RangeSet --- .../logic/openoffice/frontend/OOFrontend.java | 81 ++++++------------- .../model/openoffice/rangesort/RangeSet.java | 53 ------------ 2 files changed, 23 insertions(+), 111 deletions(-) delete mode 100644 src/main/java/org/jabref/model/openoffice/rangesort/RangeSet.java diff --git a/src/main/java/org/jabref/logic/openoffice/frontend/OOFrontend.java b/src/main/java/org/jabref/logic/openoffice/frontend/OOFrontend.java index 6a4f741b793..967a8cf084d 100644 --- a/src/main/java/org/jabref/logic/openoffice/frontend/OOFrontend.java +++ b/src/main/java/org/jabref/logic/openoffice/frontend/OOFrontend.java @@ -17,7 +17,6 @@ import org.jabref.model.openoffice.rangesort.RangeOverlap; import org.jabref.model.openoffice.rangesort.RangeOverlapBetween; import org.jabref.model.openoffice.rangesort.RangeOverlapWithin; -import org.jabref.model.openoffice.rangesort.RangeSet; import org.jabref.model.openoffice.rangesort.RangeSort; import org.jabref.model.openoffice.rangesort.RangeSortEntry; import org.jabref.model.openoffice.rangesort.RangeSortVisual; @@ -400,69 +399,35 @@ public List> viewCursorRanges(XTextDocumen NoDocumentException, WrappedTargetException { - if (false) { - // Avoid inserting the same mark twice. - RangeSet seen = new RangeSet(); + // We partition by XText and use a single range from + // each partition to get at the corresponding footnotemark range. - List> result = new ArrayList<>(); - - for (RangeForOverlapCheck citationRange : citationRanges) { - - Optional footnoteMarkRange = UnoTextRange.getFootnoteMarkRange(citationRange.range); - - if (footnoteMarkRange.isEmpty()) { - // not in footnote - continue; - } - - boolean seenContains = seen.contains(footnoteMarkRange.get()); - if (!seenContains) { - seen.add(footnoteMarkRange.get()); - result.add(new RangeForOverlapCheck<>(footnoteMarkRange.get(), - citationRange.idWithinKind, - RangeForOverlapCheck.FOOTNOTE_MARK_KIND, - "FootnoteMark for " + citationRange.format())); - } + List> result = new ArrayList<>(); + RangeSort.RangePartitions> partitions = + RangeSort.partitionRanges(citationRanges); + + // Each partition corresponds to an XText, and each footnote has a single XText. + // (This latter ignores the possibility of XTextContents inserted into footnotes.) + // Also: different footnotes cannot share a footnotemark range, we are not creating duplicates. + for (List> partition : partitions.getPartitions()) { + if (partition.isEmpty()) { + continue; } - return result; - } else { - - // RangeSet.add involves a few comparisons anf getText, which is probably costly. - // - // (On the other hand we only insert ranges of footnotes, which probably limits the sizes - // of its partitions) - // - // We can avoid using RangeSet by partitioning only and using a single range from - // each partition to get at the corresponding footnotemark range. - - List> result = new ArrayList<>(); - RangeSort.RangePartitions> partitions = - RangeSort.partitionRanges(citationRanges); - - // Now it is sufficient to check a single entry from each partition. - // Each partition corresponds to an XText, and each footnote has a single XText. - // (This latter ignores the possibility of XTextContents inserted into footnotes.) - // Also: different footnotes cannot share a footnotemark range, we are not creating duplicates. - for (List> partition : partitions.getPartitions()) { - if (partition.isEmpty()) { - continue; - } - RangeForOverlapCheck citationRange = partition.get(0); - - Optional footnoteMarkRange = UnoTextRange.getFootnoteMarkRange(citationRange.range); + RangeForOverlapCheck citationRange = partition.get(0); - if (footnoteMarkRange.isEmpty()) { - // not in footnote - continue; - } + Optional footnoteMarkRange = UnoTextRange.getFootnoteMarkRange(citationRange.range); - result.add(new RangeForOverlapCheck<>(footnoteMarkRange.get(), - citationRange.idWithinKind, - RangeForOverlapCheck.FOOTNOTE_MARK_KIND, - "FootnoteMark for " + citationRange.format())); + if (footnoteMarkRange.isEmpty()) { + // not in footnote + continue; } - return result; + + result.add(new RangeForOverlapCheck<>(footnoteMarkRange.get(), + citationRange.idWithinKind, + RangeForOverlapCheck.FOOTNOTE_MARK_KIND, + "FootnoteMark for " + citationRange.format())); } + return result; } static String rangeOverlapsToMessage(List>> overlaps) { diff --git a/src/main/java/org/jabref/model/openoffice/rangesort/RangeSet.java b/src/main/java/org/jabref/model/openoffice/rangesort/RangeSet.java deleted file mode 100644 index e389c902b90..00000000000 --- a/src/main/java/org/jabref/model/openoffice/rangesort/RangeSet.java +++ /dev/null @@ -1,53 +0,0 @@ -package org.jabref.model.openoffice.rangesort; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.TreeSet; - -import org.jabref.model.openoffice.uno.UnoTextRange; - -import com.sun.star.text.XText; -import com.sun.star.text.XTextRange; - -public class RangeSet { - - private final Map> partitions; - - public RangeSet() { - this.partitions = new HashMap<>(); - } - - public boolean contains(XTextRange range) { - Objects.requireNonNull(range); - XText partitionKey = range.getText(); - if (!this.partitions.containsKey(partitionKey)) { - return false; - } - return partitions.get(partitionKey).contains(range); - } - - /* - * return false if already contained - * - * Beware: using UnoTextRange::compareStartsThenEnds as comparator involves range.getText() - * twice on each comparison. This makes it costly to use this class. - */ - public boolean add(XTextRange range) { - TreeSet partition = partitions.get(range.getText()); - if (partition == null) { - partition = new TreeSet<>(UnoTextRange::compareStartsThenEnds); - partitions.put(range.getText(), partition); - } - return partition.add(range); - } - - /** - * @return A list of the partitions. - */ - public List> partitionValues() { - return new ArrayList<>(partitions.values()); - } -} From 23200dee95d47993a06e58e88849ab59ce365fc2 Mon Sep 17 00:00:00 2001 From: Antal K Date: Tue, 22 Jun 2021 15:42:36 +0200 Subject: [PATCH 26/51] use StringUtil.isNullOrEmpty --- .../java/org/jabref/model/openoffice/ootext/OOFormat.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/jabref/model/openoffice/ootext/OOFormat.java b/src/main/java/org/jabref/model/openoffice/ootext/OOFormat.java index be5ee399e55..4bfba9e5baf 100644 --- a/src/main/java/org/jabref/model/openoffice/ootext/OOFormat.java +++ b/src/main/java/org/jabref/model/openoffice/ootext/OOFormat.java @@ -1,5 +1,7 @@ package org.jabref.model.openoffice.ootext; +import org.jabref.model.strings.StringUtil; + /** * Helper functions to produce some of the markup as understood by OOTextIntoOO.write * @@ -54,7 +56,7 @@ public static OOText setCharStyle(OOText ootext, String charStyle) { * Mark {@code ootext} as part of a paragraph with style {@code paraStyle} */ public static OOText paragraph(OOText ootext, String paraStyle) { - if (paraStyle == null || "".equals(paraStyle)) { + if (StringUtil.isNullOrEmpty(paraStyle)) { return paragraph(ootext); } String startTag = String.format("

", paraStyle); From 9a849dd64ce9d66e6a13c460b042fb2ce28dcd8c Mon Sep 17 00:00:00 2001 From: Antal K Date: Tue, 22 Jun 2021 15:42:54 +0200 Subject: [PATCH 27/51] no natural sort for ComparableMark --- .../openoffice/rangesort/RangeSortVisual.java | 48 +++++-------------- 1 file changed, 13 insertions(+), 35 deletions(-) diff --git a/src/main/java/org/jabref/model/openoffice/rangesort/RangeSortVisual.java b/src/main/java/org/jabref/model/openoffice/rangesort/RangeSortVisual.java index 969f9fed44e..e971e7ea67c 100644 --- a/src/main/java/org/jabref/model/openoffice/rangesort/RangeSortVisual.java +++ b/src/main/java/org/jabref/model/openoffice/rangesort/RangeSortVisual.java @@ -3,7 +3,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.Objects; import org.jabref.model.openoffice.uno.NoDocumentException; import org.jabref.model.openoffice.uno.UnoScreenRefresh; @@ -73,7 +72,7 @@ public static List> visualSort(List> input input.getIndexInPosition(), input)); } - Collections.sort(set); + Collections.sort(set, RangeSortVisual::compareTopToBottomLeftToRight); if (set.size() != inputSize) { throw new IllegalStateException("visualSort: set.size() != inputSize"); @@ -115,6 +114,17 @@ private static Point findPositionOfTextRange(XTextRange range, XTextViewCursor c return cursor.getPosition(); } + private static int compareTopToBottomLeftToRight(ComparableMark a, ComparableMark b) { + + if (a.position.Y != b.position.Y) { + return a.position.Y - b.position.Y; + } + if (a.position.X != b.position.X) { + return a.position.X - b.position.X; + } + return a.indexInPosition - b.indexInPosition; + } + /** * A reference mark name paired with its visual position. * @@ -123,7 +133,7 @@ private static Point findPositionOfTextRange(XTextRange range, XTextViewCursor c * * Used for sorting reference marks by their visual positions. */ - private static class ComparableMark implements Comparable> { + private static class ComparableMark { private final Point position; private final int indexInPosition; @@ -135,42 +145,10 @@ public ComparableMark(Point position, int indexInPosition, T content) { this.content = content; } - @Override - public int compareTo(ComparableMark other) { - - if (position.Y != other.position.Y) { - return position.Y - other.position.Y; - } - if (position.X != other.position.X) { - return position.X - other.position.X; - } - return indexInPosition - other.indexInPosition; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - - if (o instanceof ComparableMark) { - ComparableMark other = (ComparableMark) o; - return ((this.position.X == other.position.X) - && (this.position.Y == other.position.Y) - && (this.indexInPosition == other.indexInPosition) - && Objects.equals(this.content, other.content)); - } - return false; - } - public T getContent() { return content; } - @Override - public int hashCode() { - return Objects.hash(position, indexInPosition, content); - } } } From 2d4d5fb753bd89f5386859352e5a5133c93dde8a Mon Sep 17 00:00:00 2001 From: Antal K Date: Wed, 7 Jul 2021 20:27:07 +0200 Subject: [PATCH 28/51] in response to review https://github.com/JabRef/jabref/pull/7788#pullrequestreview-698494039 - more use of StringUtil.isNullOrEmpty - private final XTextRangeCompare cmp; - List partition = partitions.computeIfAbsent(partitionKey, _key -> new ArrayList<>()); - visualSort does not throw WrappedTargetException, NoDocumentException - set renamed to comparableMarks --- .../model/openoffice/ootext/OOFormat.java | 4 +-- .../model/openoffice/ootext/OOText.java | 5 ++- .../model/openoffice/ootext/OOTextIntoOO.java | 9 ++--- .../model/openoffice/rangesort/RangeSort.java | 36 +++++++++++-------- .../openoffice/rangesort/RangeSortVisual.java | 25 ++++--------- 5 files changed, 36 insertions(+), 43 deletions(-) diff --git a/src/main/java/org/jabref/model/openoffice/ootext/OOFormat.java b/src/main/java/org/jabref/model/openoffice/ootext/OOFormat.java index 4bfba9e5baf..f502922f818 100644 --- a/src/main/java/org/jabref/model/openoffice/ootext/OOFormat.java +++ b/src/main/java/org/jabref/model/openoffice/ootext/OOFormat.java @@ -73,8 +73,8 @@ public static OOText paragraph(OOText ootext) { /** * Format an OO cross-reference showing the target's page number as label to a reference mark. */ - public static OOText formatReferenceToPageNumberOfReferenceMark(String referencMarkName) { - String string = String.format("", referencMarkName); + public static OOText formatReferenceToPageNumberOfReferenceMark(String referenceMarkName) { + String string = String.format("", referenceMarkName); return OOText.fromString(string); } } diff --git a/src/main/java/org/jabref/model/openoffice/ootext/OOText.java b/src/main/java/org/jabref/model/openoffice/ootext/OOText.java index ad0b9fb448d..f5142e8dc2e 100644 --- a/src/main/java/org/jabref/model/openoffice/ootext/OOText.java +++ b/src/main/java/org/jabref/model/openoffice/ootext/OOText.java @@ -17,7 +17,7 @@ private OOText(String data) { this.data = data; } - /* null input is passed through */ + /** @return null for null input, otherwise the argument wrapped into a new OOText */ public static OOText fromString(String string) { if (string == null) { return null; @@ -25,7 +25,7 @@ public static OOText fromString(String string) { return new OOText(string); } - /* null input is passed through */ + /** @return null for null input, otherwise the string inside the argument */ public static String toString(OOText ootext) { if (ootext == null) { return null; @@ -38,7 +38,6 @@ public String toString() { return data; } - /* Object.equals */ @Override public boolean equals(Object object) { diff --git a/src/main/java/org/jabref/model/openoffice/ootext/OOTextIntoOO.java b/src/main/java/org/jabref/model/openoffice/ootext/OOTextIntoOO.java index 0758d515446..caa78f07d2c 100644 --- a/src/main/java/org/jabref/model/openoffice/ootext/OOTextIntoOO.java +++ b/src/main/java/org/jabref/model/openoffice/ootext/OOTextIntoOO.java @@ -17,6 +17,7 @@ import org.jabref.model.openoffice.uno.UnoCast; import org.jabref.model.openoffice.uno.UnoCrossRef; import org.jabref.model.openoffice.util.OOPair; +import org.jabref.model.strings.StringUtil; import com.sun.star.awt.FontSlant; import com.sun.star.awt.FontStrikeout; @@ -181,7 +182,7 @@ public static void write(XTextDocument doc, XTextCursor position, OOText ootext) String endTagName = m.group(1); String startTagName = m.group(2); String attributeListPart = m.group(3); - boolean isStartTag = (endTagName == null) || "".equals(endTagName); + boolean isStartTag = StringUtil.isNullOrEmpty(endTagName); String tagName = isStartTag ? startTagName : endTagName; Objects.requireNonNull(tagName); @@ -231,7 +232,7 @@ public static void write(XTextDocument doc, XTextCursor position, OOText ootext) switch (key) { case "oo:ParaStyleName": //

- if (value != null && !value.equals("")) { + if (!StringUtil.isNullOrEmpty(value)) { if (setParagraphStyle(cursor, value)) { // Presumably tested already: LOGGER.debug(String.format("oo:ParaStyleName=\"%s\" failed", value)); @@ -689,7 +690,7 @@ private static List> setCharStrikeout(short value) { // CharStyleName private static List> setCharStyleName(String value) { List> settings = new ArrayList<>(); - if (value != null && value != "") { + if (!StringUtil.isNullOrEmpty(value)) { settings.add(new OOPair<>(CHAR_STYLE_NAME, value)); } else { LOGGER.warn("setCharStyleName: received null or empty value"); @@ -708,7 +709,7 @@ private static List> setCharLocale(Locale value) { * Locale from string encoding: language, language-country or language-country-variant */ private static List> setCharLocale(String value) { - if (value == null || "".equals(value)) { + if (StringUtil.isNullOrEmpty(value)) { throw new java.lang.IllegalArgumentException("setCharLocale \"\" or null"); } String[] parts = value.split("-"); diff --git a/src/main/java/org/jabref/model/openoffice/rangesort/RangeSort.java b/src/main/java/org/jabref/model/openoffice/rangesort/RangeSort.java index 1b4fc327598..d5024d6d8e2 100644 --- a/src/main/java/org/jabref/model/openoffice/rangesort/RangeSort.java +++ b/src/main/java/org/jabref/model/openoffice/rangesort/RangeSort.java @@ -12,21 +12,29 @@ import com.sun.star.text.XText; import com.sun.star.text.XTextRangeCompare; +/** + * RangeSort provides sorting based on XTextRangeCompare, which only provides comparison + * between XTextRange values within the same XText. + */ public class RangeSort { - /* - * Sort within a partition + /** + * Compare two RangeHolders (using RangeHolder.getRange()) within an XText. + * + * Note: since we only look at the ranges, this comparison is generally not consistent with + * `equals` on the RangeHolders. Probably should not be used for key comparison in + * TreeMap<RangeHolder> or Set<RangeHolder> + * */ - public static class HolderComparatorWithinPartition implements Comparator { - XTextRangeCompare cmp; + private final XTextRangeCompare cmp; HolderComparatorWithinPartition(XText text) { cmp = UnoCast.cast(XTextRangeCompare.class, text).get(); } - /* + /** * Assumes a and b belong to the same XText as cmp. */ @Override @@ -35,7 +43,7 @@ public int compare(RangeHolder a, RangeHolder b) { } } - /* + /** * Sort a list of RangeHolder values known to share the same getText(). * * Note: RangeHolder.getRange() is called many times. @@ -48,10 +56,9 @@ public static void sortWithinPartition(List rangeHold rangeHolders.sort(new HolderComparatorWithinPartition(text)); } - /* - * Partitioning + /** + * Represent a partitioning of RangeHolders by XText */ - public static class RangePartitions { private final Map> partitions; @@ -61,11 +68,7 @@ public RangePartitions() { public void add(V holder) { XText partitionKey = holder.getRange().getText(); - List partition = partitions.get(partitionKey); - if (partition == null) { - partition = new ArrayList<>(); - partitions.put(partitionKey, partition); - } + List partition = partitions.computeIfAbsent(partitionKey, _key -> new ArrayList<>()); partition.add(holder); } @@ -74,6 +77,9 @@ public List> getPartitions() { } } + /** + * Partition RangeHolders by the corresponding XText. + */ public static RangePartitions partitionRanges(List holders) { RangePartitions result = new RangePartitions<>(); for (V holder : holders) { @@ -82,7 +88,7 @@ public static RangePartitions partitionRanges(List return result; } - /* + /** * Note: RangeHolder.getRange() is called many times. */ public static RangePartitions partitionAndSortRanges(List holders) { diff --git a/src/main/java/org/jabref/model/openoffice/rangesort/RangeSortVisual.java b/src/main/java/org/jabref/model/openoffice/rangesort/RangeSortVisual.java index e971e7ea67c..1f39427cf43 100644 --- a/src/main/java/org/jabref/model/openoffice/rangesort/RangeSortVisual.java +++ b/src/main/java/org/jabref/model/openoffice/rangesort/RangeSortVisual.java @@ -4,11 +4,9 @@ import java.util.Collections; import java.util.List; -import org.jabref.model.openoffice.uno.NoDocumentException; import org.jabref.model.openoffice.uno.UnoScreenRefresh; import com.sun.star.awt.Point; -import com.sun.star.lang.WrappedTargetException; import com.sun.star.text.XTextDocument; import com.sun.star.text.XTextRange; import com.sun.star.text.XTextViewCursor; @@ -38,10 +36,7 @@ public class RangeSortVisual { */ public static List> visualSort(List> inputs, XTextDocument doc, - FunctionalTextViewCursor fcursor) - throws - WrappedTargetException, - NoDocumentException { + FunctionalTextViewCursor fcursor) { final int inputSize = inputs.size(); @@ -60,27 +55,19 @@ public static List> visualSort(List> input } fcursor.restore(doc); - if (positions.size() != inputSize) { - throw new IllegalStateException("visualSort: positions.size() != inputSize"); - } - // order by position - ArrayList>> set = new ArrayList<>(inputSize); + ArrayList>> comparableMarks = new ArrayList<>(inputSize); for (int i = 0; i < inputSize; i++) { RangeSortable input = inputs.get(i); - set.add(new ComparableMark<>(positions.get(i), + comparableMarks.add(new ComparableMark<>(positions.get(i), input.getIndexInPosition(), input)); } - Collections.sort(set, RangeSortVisual::compareTopToBottomLeftToRight); - - if (set.size() != inputSize) { - throw new IllegalStateException("visualSort: set.size() != inputSize"); - } + Collections.sort(comparableMarks, RangeSortVisual::compareTopToBottomLeftToRight); // collect ordered result - List> result = new ArrayList<>(set.size()); - for (ComparableMark> mark : set) { + List> result = new ArrayList<>(comparableMarks.size()); + for (ComparableMark> mark : comparableMarks) { result.add(mark.getContent()); } From 63a7c0bd218ce5dc020da3f57d32bc8dba202f67 Mon Sep 17 00:00:00 2001 From: Antal K Date: Fri, 9 Jul 2021 23:19:28 +0200 Subject: [PATCH 29/51] use {@code }, PMD suggestions --- .../model/openoffice/ootext/OOFormat.java | 4 + .../model/openoffice/ootext/OOText.java | 4 +- .../model/openoffice/ootext/OOTextIntoOO.java | 82 +++++++++---------- .../rangesort/RangeOverlapBetween.java | 2 +- .../rangesort/RangeOverlapWithin.java | 6 +- .../model/openoffice/rangesort/RangeSort.java | 13 ++- .../openoffice/rangesort/RangeSortEntry.java | 8 +- .../openoffice/rangesort/RangeSortVisual.java | 15 ++-- .../openoffice/rangesort/RangeSortable.java | 6 +- 9 files changed, 75 insertions(+), 65 deletions(-) diff --git a/src/main/java/org/jabref/model/openoffice/ootext/OOFormat.java b/src/main/java/org/jabref/model/openoffice/ootext/OOFormat.java index f502922f818..6b4a96de644 100644 --- a/src/main/java/org/jabref/model/openoffice/ootext/OOFormat.java +++ b/src/main/java/org/jabref/model/openoffice/ootext/OOFormat.java @@ -11,6 +11,10 @@ */ public class OOFormat { + private OOFormat() { + /* */ + } + /** * Mark {@code ootext} as using a character locale known to OO. * diff --git a/src/main/java/org/jabref/model/openoffice/ootext/OOText.java b/src/main/java/org/jabref/model/openoffice/ootext/OOText.java index f5142e8dc2e..5b5fa4caba9 100644 --- a/src/main/java/org/jabref/model/openoffice/ootext/OOText.java +++ b/src/main/java/org/jabref/model/openoffice/ootext/OOText.java @@ -49,9 +49,9 @@ public boolean equals(Object object) { return false; } - OOText c = (OOText) object; + OOText other = (OOText) object; - return data.equals(c.data); + return data.equals(other.data); } @Override diff --git a/src/main/java/org/jabref/model/openoffice/ootext/OOTextIntoOO.java b/src/main/java/org/jabref/model/openoffice/ootext/OOTextIntoOO.java index caa78f07d2c..6e61faed147 100644 --- a/src/main/java/org/jabref/model/openoffice/ootext/OOTextIntoOO.java +++ b/src/main/java/org/jabref/model/openoffice/ootext/OOTextIntoOO.java @@ -112,29 +112,29 @@ private OOTextIntoOO() { * * - new tags: * - * - <span lang="zxx"> + * - {@code } * - earlier was applied from code * - * - <span oo:CharStyleName="CharStylename"> + * - {@code } * - earlier was applied from code, for "CitationCharacterFormat" * - * - <p> start new paragraph + * - {@code

} start new paragraph * - earlier was applied from code * - * - <p oo:ParaStyleName="ParStyleName"> : start new paragraph and apply ParStyleName + * - {@code

} : start new paragraph and apply ParStyleName * - earlier was applied from code * - * - <tt> + * - {@code } * - earlier: known, but ignored - * - now: equivalent to <span oo:CharStyleName="Example"> - * - <oo:referenceToPageNumberOfReferenceMark> (self-closing) + * - now: equivalent to {@code } + * - {@code } (self-closing) * * - closing tags try to properly restore state (in particular, the "not directly set" state) * instead of dictating an "off" state. This makes a difference when the value inherited from * another level (for example the paragraph) is not the "off" state. * * An example: a style with - * ReferenceParagraphFormat="JR_bibentry" + * {@code ReferenceParagraphFormat="JR_bibentry"} * Assume JR_bibentry in LibreOffice is a paragraph style that prescribes "bold" font. * LAYOUT only prescribes bold around year. * Which parts of the bibliography entries should come out as bold? @@ -147,7 +147,6 @@ private OOTextIntoOO() { */ public static void write(XTextDocument doc, XTextCursor position, OOText ootext) throws - PropertyVetoException, WrappedTargetException, CreationException { @@ -168,20 +167,19 @@ public static void write(XTextDocument doc, XTextCursor position, OOText ootext) // We need to extract formatting. Use a simple regexp search iteration: int piv = 0; - Matcher m = HTML_TAG.matcher(lText); - while (m.find()) { + Matcher tagMatcher = HTML_TAG.matcher(lText); + while (tagMatcher.find()) { - String currentSubstring = lText.substring(piv, m.start()); + String currentSubstring = lText.substring(piv, tagMatcher.start()); if (!currentSubstring.isEmpty()) { cursor.setString(currentSubstring); } formatStack.apply(cursor); cursor.collapseToEnd(); - String fullTag = m.group(); - String endTagName = m.group(1); - String startTagName = m.group(2); - String attributeListPart = m.group(3); + String endTagName = tagMatcher.group(1); + String startTagName = tagMatcher.group(2); + String attributeListPart = tagMatcher.group(3); boolean isStartTag = StringUtil.isNullOrEmpty(endTagName); String tagName = isStartTag ? startTagName : endTagName; Objects.requireNonNull(tagName); @@ -232,13 +230,13 @@ public static void write(XTextDocument doc, XTextCursor position, OOText ootext) switch (key) { case "oo:ParaStyleName": //

- if (!StringUtil.isNullOrEmpty(value)) { + if (StringUtil.isNullOrEmpty(value)) { + LOGGER.debug(String.format("oo:ParaStyleName inherited")); + } else { if (setParagraphStyle(cursor, value)) { // Presumably tested already: LOGGER.debug(String.format("oo:ParaStyleName=\"%s\" failed", value)); } - } else { - LOGGER.debug(String.format("oo:ParaStyleName inherited")); } break; default: @@ -283,7 +281,7 @@ public static void write(XTextDocument doc, XTextCursor position, OOText ootext) break; case "style": // HTML-style small-caps - if (value.equals("font-variant: small-caps")) { + if ("font-variant: small-caps".equals(value)) { settings.addAll(setCharCaseMap(CaseMap.SMALLCAPS)); break; } @@ -317,9 +315,12 @@ public static void write(XTextDocument doc, XTextCursor position, OOText ootext) currentSubstring)); } break; + default: + LOGGER.warn(String.format("ignoring unknown tag '<%s>'", tagName)); + break; } - piv = m.end(); + piv = tagMatcher.end(); } if (piv < lText.length()) { @@ -396,7 +397,7 @@ public static void removeDirectFormatting(XTextCursor cursor) { continue; } } catch (UnknownPropertyException ex) { - throw new java.lang.IllegalStateException("Unexpected UnknownPropertyException"); + throw new IllegalStateException("Unexpected UnknownPropertyException", ex); } if (knownToFail.contains(p.Name)) { continue; @@ -511,11 +512,11 @@ static class MyPropertyStack { * Get the initial state of the properties and add the first layer. */ XMultiPropertyStates mpss = UnoCast.cast(XMultiPropertyStates.class, cursor).get(); - PropertyState[] propertyStates = null; + PropertyState[] propertyStates; try { propertyStates = mpss.getPropertyStates(goodNames); } catch (UnknownPropertyException ex) { - throw new java.lang.IllegalStateException("Caught unexpected UnknownPropertyException"); + throw new IllegalStateException("Caught unexpected UnknownPropertyException", ex); } XMultiPropertySet mps = UnoCast.cast(XMultiPropertySet.class, cursor).get(); @@ -546,13 +547,13 @@ void pushLayer(List> settings) { ArrayList> newLayer = new ArrayList<>(oldLayer); for (OOPair pair : settings) { String name = pair.a; - Integer i = goodNameToIndex.get(name); - if (i == null) { + Integer index = goodNameToIndex.get(name); + if (index == null) { LOGGER.warn(String.format("pushLayer: '%s' is not in goodNameToIndex", name)); continue; } Object newValue = pair.b; - newLayer.set(i, Optional.ofNullable(newValue)); + newLayer.set(index, Optional.ofNullable(newValue)); } layers.push(newLayer); } @@ -592,8 +593,8 @@ void apply(XTextCursor cursor) { } } // namesArray must be alphabetically sorted. - String[] namesArray = names.toArray(new String[names.size()]); - String[] delNamesArray = delNames.toArray(new String[delNames.size()]); + String[] namesArray = names.toArray(new String[0]); + String[] delNamesArray = delNames.toArray(new String[0]); mpss.setPropertiesToDefault(delNamesArray); mps.setPropertyValues(namesArray, values.toArray()); } catch (UnknownPropertyException ex) { @@ -608,10 +609,9 @@ void apply(XTextCursor cursor) { // Relative CharEscapement needs to know current values. Optional getPropertyValue(String name) { if (goodNameToIndex.containsKey(name)) { - int i = goodNameToIndex.get(name); + int index = goodNameToIndex.get(name); ArrayList> topLayer = layers.peek(); - Optional value = topLayer.get(i); - return value; + return topLayer.get(index); } return Optional.empty(); } @@ -620,15 +620,15 @@ Optional getPropertyValue(String name) { /** * Parse HTML-like attributes to a list of (name,value) pairs. */ - private static List> parseAttributes(String s) { + private static List> parseAttributes(String attributes) { List> res = new ArrayList<>(); - if (s == null) { + if (attributes == null) { return res; } - Matcher m = ATTRIBUTE_PATTERN.matcher(s); - while (m.find()) { - String key = m.group(1); - String value = m.group(2); + Matcher attributeMatcher = ATTRIBUTE_PATTERN.matcher(attributes); + while (attributeMatcher.find()) { + String key = attributeMatcher.group(1); + String value = attributeMatcher.group(2); res.add(new OOPair(key, value)); } return res; @@ -690,10 +690,10 @@ private static List> setCharStrikeout(short value) { // CharStyleName private static List> setCharStyleName(String value) { List> settings = new ArrayList<>(); - if (!StringUtil.isNullOrEmpty(value)) { - settings.add(new OOPair<>(CHAR_STYLE_NAME, value)); - } else { + if (StringUtil.isNullOrEmpty(value)) { LOGGER.warn("setCharStyleName: received null or empty value"); + } else { + settings.add(new OOPair<>(CHAR_STYLE_NAME, value)); } return settings; } diff --git a/src/main/java/org/jabref/model/openoffice/rangesort/RangeOverlapBetween.java b/src/main/java/org/jabref/model/openoffice/rangesort/RangeOverlapBetween.java index 9a3ef1c9968..16b7735fb7a 100644 --- a/src/main/java/org/jabref/model/openoffice/rangesort/RangeOverlapBetween.java +++ b/src/main/java/org/jabref/model/openoffice/rangesort/RangeOverlapBetween.java @@ -31,7 +31,7 @@ List> findFirst(XTextDocument doc, List> result = new ArrayList<>(); - if (fewHolders.size() == 0) { + if (fewHolders.isEmpty()) { return result; } diff --git a/src/main/java/org/jabref/model/openoffice/rangesort/RangeOverlapWithin.java b/src/main/java/org/jabref/model/openoffice/rangesort/RangeOverlapWithin.java index e4df2d8278c..a8534b630ae 100644 --- a/src/main/java/org/jabref/model/openoffice/rangesort/RangeOverlapWithin.java +++ b/src/main/java/org/jabref/model/openoffice/rangesort/RangeOverlapWithin.java @@ -39,9 +39,7 @@ List> findOverlappingRanges(XTextDocument doc, RangeSort.RangePartitions partitions = RangeSort.partitionAndSortRanges(rangeHolders); - List> overlaps = findOverlappingRanges(partitions, reportAtMost, includeTouching); - - return overlaps; + return findOverlappingRanges(partitions, reportAtMost, includeTouching); } /** @@ -64,7 +62,7 @@ List> findOverlappingRanges(RangeSort.RangePartitions input, List> result = new ArrayList<>(); for (List partition : input.getPartitions()) { - if (partition.size() == 0) { + if (partition.isEmpty()) { continue; } XTextRangeCompare cmp = UnoCast.cast(XTextRangeCompare.class, diff --git a/src/main/java/org/jabref/model/openoffice/rangesort/RangeSort.java b/src/main/java/org/jabref/model/openoffice/rangesort/RangeSort.java index d5024d6d8e2..99ace5154e1 100644 --- a/src/main/java/org/jabref/model/openoffice/rangesort/RangeSort.java +++ b/src/main/java/org/jabref/model/openoffice/rangesort/RangeSort.java @@ -18,20 +18,25 @@ */ public class RangeSort { + private RangeSort() { + /**/ + } + /** * Compare two RangeHolders (using RangeHolder.getRange()) within an XText. * * Note: since we only look at the ranges, this comparison is generally not consistent with * `equals` on the RangeHolders. Probably should not be used for key comparison in - * TreeMap<RangeHolder> or Set<RangeHolder> + * {@code TreeMap} or {@code Set} * */ - public static class HolderComparatorWithinPartition implements Comparator { + private static class HolderComparatorWithinPartition implements Comparator { private final XTextRangeCompare cmp; HolderComparatorWithinPartition(XText text) { - cmp = UnoCast.cast(XTextRangeCompare.class, text).get(); + cmp = (UnoCast.cast(XTextRangeCompare.class, text) + .orElseThrow(java.lang.IllegalArgumentException::new)); } /** @@ -68,7 +73,7 @@ public RangePartitions() { public void add(V holder) { XText partitionKey = holder.getRange().getText(); - List partition = partitions.computeIfAbsent(partitionKey, _key -> new ArrayList<>()); + List partition = partitions.computeIfAbsent(partitionKey, unused -> new ArrayList<>()); partition.add(holder); } diff --git a/src/main/java/org/jabref/model/openoffice/rangesort/RangeSortEntry.java b/src/main/java/org/jabref/model/openoffice/rangesort/RangeSortEntry.java index 4ed651e5396..0ed686ee901 100644 --- a/src/main/java/org/jabref/model/openoffice/rangesort/RangeSortEntry.java +++ b/src/main/java/org/jabref/model/openoffice/rangesort/RangeSortEntry.java @@ -32,11 +32,11 @@ public T getContent() { return content; } - public void setRange(XTextRange r) { - range = r; + public void setRange(XTextRange range) { + this.range = range; } - public void setIndexInPosition(int i) { - indexInPosition = i; + public void setIndexInPosition(int indexInPosition) { + this.indexInPosition = indexInPosition; } } diff --git a/src/main/java/org/jabref/model/openoffice/rangesort/RangeSortVisual.java b/src/main/java/org/jabref/model/openoffice/rangesort/RangeSortVisual.java index 1f39427cf43..e412f4ade6c 100644 --- a/src/main/java/org/jabref/model/openoffice/rangesort/RangeSortVisual.java +++ b/src/main/java/org/jabref/model/openoffice/rangesort/RangeSortVisual.java @@ -1,7 +1,6 @@ package org.jabref.model.openoffice.rangesort; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import org.jabref.model.openoffice.uno.UnoScreenRefresh; @@ -27,6 +26,10 @@ public class RangeSortVisual { private static final Logger LOGGER = LoggerFactory.getLogger(RangeSortVisual.class); + private RangeSortVisual() { + /**/ + } + /** * Sort the input {@code inputs} visually. * @@ -38,8 +41,6 @@ public static List> visualSort(List> input XTextDocument doc, FunctionalTextViewCursor fcursor) { - final int inputSize = inputs.size(); - if (UnoScreenRefresh.hasControllersLocked(doc)) { final String msg = "visualSort: with ControllersLocked, viewCursor.gotoRange is probably useless"; LOGGER.warn(msg); @@ -48,6 +49,8 @@ public static List> visualSort(List> input XTextViewCursor viewCursor = fcursor.getViewCursor(); + final int inputSize = inputs.size(); + // find coordinates List positions = new ArrayList<>(inputSize); for (RangeSortable v : inputs) { @@ -63,7 +66,7 @@ public static List> visualSort(List> input input.getIndexInPosition(), input)); } - Collections.sort(comparableMarks, RangeSortVisual::compareTopToBottomLeftToRight); + comparableMarks.sort(RangeSortVisual::compareTopToBottomLeftToRight); // collect ordered result List> result = new ArrayList<>(comparableMarks.size()); @@ -101,7 +104,7 @@ private static Point findPositionOfTextRange(XTextRange range, XTextViewCursor c return cursor.getPosition(); } - private static int compareTopToBottomLeftToRight(ComparableMark a, ComparableMark b) { + private static int compareTopToBottomLeftToRight(ComparableMark a, ComparableMark b) { if (a.position.Y != b.position.Y) { return a.position.Y - b.position.Y; @@ -111,7 +114,7 @@ private static int compareTopToBottomLeftToRight(ComparableMark a, ComparableMar } return a.indexInPosition - b.indexInPosition; } - + /** * A reference mark name paired with its visual position. * diff --git a/src/main/java/org/jabref/model/openoffice/rangesort/RangeSortable.java b/src/main/java/org/jabref/model/openoffice/rangesort/RangeSortable.java index c3eab225889..59a4c3fa9af 100644 --- a/src/main/java/org/jabref/model/openoffice/rangesort/RangeSortable.java +++ b/src/main/java/org/jabref/model/openoffice/rangesort/RangeSortable.java @@ -11,12 +11,12 @@ public interface RangeSortable extends RangeHolder { * * For citation marks in footnotes this may be the range of the footnote mark. */ - public XTextRange getRange(); + XTextRange getRange(); /** * For citation marks in footnotes this may provide order within the footnote. */ - public int getIndexInPosition(); + int getIndexInPosition(); - public T getContent(); + T getContent(); } From d5e6de8142c7ccbbe5d5c2bb6aba979ae91b7152 Mon Sep 17 00:00:00 2001 From: Antal K Date: Tue, 13 Jul 2021 11:48:42 +0200 Subject: [PATCH 30/51] update logic/style from improve-reversibility-rebased-03 --- .../style/OOBibStyleGetCitationMarker.java | 74 ++++++++++--------- .../style/OOBibStyleGetNumCitationMarker.java | 6 +- .../logic/openoffice/style/OOProcess.java | 30 +++----- .../style/OOProcessAuthorYearMarkers.java | 4 + .../style/OOProcessCitationKeyMarkers.java | 5 ++ .../style/OOProcessNumericMarkers.java | 4 + 6 files changed, 66 insertions(+), 57 deletions(-) diff --git a/src/main/java/org/jabref/logic/openoffice/style/OOBibStyleGetCitationMarker.java b/src/main/java/org/jabref/logic/openoffice/style/OOBibStyleGetCitationMarker.java index e501cd4aaaf..4235841e426 100644 --- a/src/main/java/org/jabref/logic/openoffice/style/OOBibStyleGetCitationMarker.java +++ b/src/main/java/org/jabref/logic/openoffice/style/OOBibStyleGetCitationMarker.java @@ -22,6 +22,10 @@ class OOBibStyleGetCitationMarker { + private OOBibStyleGetCitationMarker() { + /**/ + } + /** * Look up the nth author and return the "proper" last name for * citation markers. @@ -29,23 +33,23 @@ class OOBibStyleGetCitationMarker { * Note: "proper" in the sense that it includes the "von" part * of the name (followed by a space) if there is one. * - * @param al The author list. + * @param authorList The author list. * @param number The number of the author to return. * @return The author name, or an empty String if inapplicable. */ - private static String getAuthorLastName(AuthorList al, int number) { + private static String getAuthorLastName(AuthorList authorList, int number) { StringBuilder sb = new StringBuilder(); - if (al.getNumberOfAuthors() > number) { - Author a = al.getAuthor(number); + if (authorList.getNumberOfAuthors() > number) { + Author author = authorList.getAuthor(number); // "von " if von exists - Optional von = a.getVon(); + Optional von = author.getVon(); if (von.isPresent() && !von.get().isEmpty()) { sb.append(von.get()); sb.append(' '); } // last name if it exists - sb.append(a.getLast().orElse("")); + sb.append(author.getLast().orElse("")); } return sb.toString(); @@ -306,23 +310,23 @@ private enum AuthorYearMarkerPurpose { } /** - * How many authors would be emitted for ce, considering - * style and ce.getIsFirstAppearanceOfSource() + * How many authors would be emitted for entry, considering + * style and entry.getIsFirstAppearanceOfSource() * - * If ce is unresolved, return 0. + * If entry is unresolved, return 0. */ - private static int calculateNAuthorsToEmit(OOBibStyle style, CitationMarkerEntry ce) { - - int maxAuthors = (ce.getIsFirstAppearanceOfSource() - ? style.getMaxAuthorsFirst() - : style.getMaxAuthors()); + private static int calculateNAuthorsToEmit(OOBibStyle style, CitationMarkerEntry entry) { - if (ce.getLookupResult().isEmpty()) { + if (entry.getLookupResult().isEmpty()) { // unresolved return 0; } - AuthorList authorList = getAuthorList(style, ce.getLookupResult().get()); + int maxAuthors = (entry.getIsFirstAppearanceOfSource() + ? style.getMaxAuthorsFirst() + : style.getMaxAuthors()); + + AuthorList authorList = getAuthorList(style, entry.getLookupResult().get()); int nAuthors = authorList.getNumberOfAuthors(); if (maxAuthors == -1) { @@ -342,21 +346,21 @@ private static int calculateNAuthorsToEmit(OOBibStyle style, CitationMarkerEntry * ignores isFirstAppearanceOfSource (always * style.getMaxAuthors, not getMaxAuthorsFirst) * - * @param ces The list of CitationMarkerEntry values to process. + * @param entries The list of CitationMarkerEntry values to process. * * Here we do not check for duplicate entries: those * are handled by {@code getCitationMarker} by * omitting them from the list. * * Unresolved citations recognized by - * ce.getBibEntry() and/or - * ce.getDatabase() returning empty, and + * entry.getBibEntry() and/or + * entry.getDatabase() returning empty, and * emitted as "Unresolved${citationKey}". * * Neither uniqueLetter nor pageInfo are emitted * for unresolved citations. * - * @param startsNewGroup Should have the same length as {@code ces}, and + * @param startsNewGroup Should have the same length as {@code entries}, and * contain true for entries starting a new group, * false for those that only add a uniqueLetter to * the grouped presentation. @@ -370,7 +374,7 @@ private static int calculateNAuthorsToEmit(OOBibStyle style, CitationMarkerEntry */ private static OOText getAuthorYearParenthesisMarker2(OOBibStyle style, AuthorYearMarkerPurpose purpose, - List ces, + List entries, boolean[] startsNewGroup, Optional maxAuthorsOverride) { @@ -409,14 +413,14 @@ private static OOText getAuthorYearParenthesisMarker2(OOBibStyle style, sb.append(startBrace); // shared parenthesis } - for (int j = 0; j < ces.size(); j++) { - CitationMarkerEntry ce = ces.get(j); + for (int j = 0; j < entries.size(); j++) { + CitationMarkerEntry entry = entries.get(j); boolean startingNewGroup = startsNewGroup[j]; - boolean endingAGroup = (j + 1 == ces.size()) || startsNewGroup[j + 1]; + boolean endingAGroup = (j + 1 == entries.size()) || startsNewGroup[j + 1]; if (!startingNewGroup) { // Just add our uniqueLetter - String uniqueLetter = ce.getUniqueLetter().orElse(null); + String uniqueLetter = entry.getUniqueLetter().orElse(null); if (uniqueLetter != null) { sb.append(uniquefierSeparator); sb.append(uniqueLetter); @@ -436,26 +440,26 @@ private static OOText getAuthorYearParenthesisMarker2(OOBibStyle style, StringBuilder pageInfoPart = new StringBuilder(""); if (purpose != AuthorYearMarkerPurpose.NORMALIZED) { Optional pageInfo = - PageInfo.normalizePageInfo(ce.getPageInfo()); + PageInfo.normalizePageInfo(entry.getPageInfo()); if (pageInfo.isPresent()) { pageInfoPart.append(pageInfoSeparator); pageInfoPart.append(OOText.toString(pageInfo.get())); } } - final boolean isUnresolved = ce.getLookupResult().isEmpty(); + final boolean isUnresolved = entry.getLookupResult().isEmpty(); if (isUnresolved) { - sb.append(String.format("Unresolved(%s)", ce.getCitationKey())); + sb.append(String.format("Unresolved(%s)", entry.getCitationKey())); if (purpose != AuthorYearMarkerPurpose.NORMALIZED) { sb.append(pageInfoPart); } } else { - CitationLookupResult db = ce.getLookupResult().get(); + CitationLookupResult db = entry.getLookupResult().get(); int maxAuthors = (purpose == AuthorYearMarkerPurpose.NORMALIZED ? style.getMaxAuthors() - : calculateNAuthorsToEmit(style, ce)); + : calculateNAuthorsToEmit(style, entry)); if (maxAuthorsOverride.isPresent()) { maxAuthors = maxAuthorsOverride.get(); @@ -476,7 +480,7 @@ private static OOText getAuthorYearParenthesisMarker2(OOBibStyle style, } if (purpose != AuthorYearMarkerPurpose.NORMALIZED) { - String uniqueLetter = ce.getUniqueLetter().orElse(null); + String uniqueLetter = entry.getUniqueLetter().orElse(null); if (uniqueLetter != null) { sb.append(uniqueLetter); } @@ -547,10 +551,10 @@ static OOText getNormalizedCitationMarker(OOBibStyle style, CitationMarkerNormEntry cne, Optional maxAuthorsOverride) { boolean[] startsNewGroup = {true}; - CitationMarkerEntry ce = new CitationMarkerNormEntryWrap(cne); + CitationMarkerEntry entry = new CitationMarkerNormEntryWrap(cne); return getAuthorYearParenthesisMarker2(style, AuthorYearMarkerPurpose.NORMALIZED, - Collections.singletonList(ce), + Collections.singletonList(entry), startsNewGroup, maxAuthorsOverride); } @@ -630,8 +634,8 @@ static OOText getNormalizedCitationMarker(OOBibStyle style, int[] nAuthorsToEmit = new int[nEntries]; int[] nAuthorsToEmitRevised = new int[nEntries]; for (int i = 0; i < nEntries; i++) { - CitationMarkerEntry ce = citationMarkerEntries.get(i); - int n = calculateNAuthorsToEmit(style, ce); + CitationMarkerEntry entry = citationMarkerEntries.get(i); + int n = calculateNAuthorsToEmit(style, entry); nAuthorsToEmit[i] = n; nAuthorsToEmitRevised[i] = n; } diff --git a/src/main/java/org/jabref/logic/openoffice/style/OOBibStyleGetNumCitationMarker.java b/src/main/java/org/jabref/logic/openoffice/style/OOBibStyleGetNumCitationMarker.java index 8d8422fbc51..5dce4e8caa1 100644 --- a/src/main/java/org/jabref/logic/openoffice/style/OOBibStyleGetNumCitationMarker.java +++ b/src/main/java/org/jabref/logic/openoffice/style/OOBibStyleGetNumCitationMarker.java @@ -17,6 +17,10 @@ class OOBibStyleGetNumCitationMarker { */ public final static int UNRESOLVED_ENTRY_NUMBER = 0; + private OOBibStyleGetNumCitationMarker() { + /**/ + } + /** * Defines sort order for CitationMarkerNumericEntry. */ @@ -223,7 +227,7 @@ public static OOText getNumCitationMarker2(OOBibStyle style, throw new IllegalArgumentException("getNumCitationMarker2: found negative number"); } - if (currentBlock.size() == 0) { + if (currentBlock.isEmpty()) { currentBlock.add(current); } else { CitationMarkerNumericEntry prev = currentBlock.get(currentBlock.size() - 1); diff --git a/src/main/java/org/jabref/logic/openoffice/style/OOProcess.java b/src/main/java/org/jabref/logic/openoffice/style/OOProcess.java index 05475fcdfac..f8f7de6c7b7 100644 --- a/src/main/java/org/jabref/logic/openoffice/style/OOProcess.java +++ b/src/main/java/org/jabref/logic/openoffice/style/OOProcess.java @@ -1,6 +1,5 @@ package org.jabref.logic.openoffice.style; -import java.util.ArrayList; import java.util.Comparator; import java.util.List; @@ -11,37 +10,26 @@ import org.jabref.model.entry.field.StandardField; import org.jabref.model.openoffice.style.CitationGroups; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - public class OOProcess { static final Comparator AUTHOR_YEAR_TITLE_COMPARATOR = makeAuthorYearTitleComparator(); static final Comparator YEAR_AUTHOR_TITLE_COMPARATOR = makeYearAuthorTitleComparator(); - private static final Logger LOGGER = LoggerFactory.getLogger(OOProcess.class); + private OOProcess() { + /**/ + } private static Comparator makeAuthorYearTitleComparator() { - FieldComparator a = new FieldComparator(StandardField.AUTHOR); - FieldComparator y = new FieldComparator(StandardField.YEAR); - FieldComparator t = new FieldComparator(StandardField.TITLE); - - List> ayt = new ArrayList<>(3); - ayt.add(a); - ayt.add(y); - ayt.add(t); + List> ayt = List.of(new FieldComparator(StandardField.AUTHOR), + new FieldComparator(StandardField.YEAR), + new FieldComparator(StandardField.TITLE)); return new FieldComparatorStack<>(ayt); } private static Comparator makeYearAuthorTitleComparator() { - FieldComparator y = new FieldComparator(StandardField.YEAR); - FieldComparator a = new FieldComparator(StandardField.AUTHOR); - FieldComparator t = new FieldComparator(StandardField.TITLE); - - List> yat = new ArrayList<>(3); - yat.add(y); - yat.add(a); - yat.add(t); + List> yat = List.of(new FieldComparator(StandardField.YEAR), + new FieldComparator(StandardField.AUTHOR), + new FieldComparator(StandardField.TITLE)); return new FieldComparatorStack<>(yat); } diff --git a/src/main/java/org/jabref/logic/openoffice/style/OOProcessAuthorYearMarkers.java b/src/main/java/org/jabref/logic/openoffice/style/OOProcessAuthorYearMarkers.java index a55d59107a8..cbd03cd79c6 100644 --- a/src/main/java/org/jabref/logic/openoffice/style/OOProcessAuthorYearMarkers.java +++ b/src/main/java/org/jabref/logic/openoffice/style/OOProcessAuthorYearMarkers.java @@ -21,6 +21,10 @@ class OOProcessAuthorYearMarkers { + private OOProcessAuthorYearMarkers() { + /**/ + } + /** * Fills {@code sortedCitedKeys//normCitMarker} */ diff --git a/src/main/java/org/jabref/logic/openoffice/style/OOProcessCitationKeyMarkers.java b/src/main/java/org/jabref/logic/openoffice/style/OOProcessCitationKeyMarkers.java index c63a4b3d0e4..9789d6c6caa 100644 --- a/src/main/java/org/jabref/logic/openoffice/style/OOProcessCitationKeyMarkers.java +++ b/src/main/java/org/jabref/logic/openoffice/style/OOProcessCitationKeyMarkers.java @@ -9,6 +9,11 @@ import org.jabref.model.openoffice.util.OOListUtil; class OOProcessCitationKeyMarkers { + + private OOProcessCitationKeyMarkers() { + /**/ + } + /** * Produce citation markers for the case when the citation * markers are the citation keys themselves, separated by commas. diff --git a/src/main/java/org/jabref/logic/openoffice/style/OOProcessNumericMarkers.java b/src/main/java/org/jabref/logic/openoffice/style/OOProcessNumericMarkers.java index 4023ed0e028..589ebfb7a48 100644 --- a/src/main/java/org/jabref/logic/openoffice/style/OOProcessNumericMarkers.java +++ b/src/main/java/org/jabref/logic/openoffice/style/OOProcessNumericMarkers.java @@ -11,6 +11,10 @@ class OOProcessNumericMarkers { + private OOProcessNumericMarkers() { + /**/ + } + /** * Produce citation markers for the case of numbered citations * with bibliography sorted by first appearance in the text. From 8b46a70153a1a6c27ce237113b1e28f022dcbfce Mon Sep 17 00:00:00 2001 From: Antal K Date: Tue, 13 Jul 2021 11:35:16 +0200 Subject: [PATCH 31/51] update model/style from improve-reversibility-rebased-03 --- .../model/openoffice/style/Citation.java | 30 +++++----- .../model/openoffice/style/CitationGroup.java | 4 +- .../openoffice/style/CitationGroups.java | 59 +++++++++---------- .../style/CitationLookupResult.java | 8 +-- .../model/openoffice/style/CitedKey.java | 8 +-- .../openoffice/style/ComparableCitation.java | 2 +- .../openoffice/style/ComparableCitedKey.java | 4 +- .../openoffice/style/CompareCitedKey.java | 6 +- .../model/openoffice/style/OODataModel.java | 4 +- .../model/openoffice/style/PageInfo.java | 11 ++-- 10 files changed, 67 insertions(+), 69 deletions(-) diff --git a/src/main/java/org/jabref/model/openoffice/style/Citation.java b/src/main/java/org/jabref/model/openoffice/style/Citation.java index 3f949237e07..9b71a1051b2 100644 --- a/src/main/java/org/jabref/model/openoffice/style/Citation.java +++ b/src/main/java/org/jabref/model/openoffice/style/Citation.java @@ -93,8 +93,8 @@ public Optional getNumber() { return number; } - public void setNumber(Optional n) { - number = n; + public void setNumber(Optional number) { + this.number = number; } public int getNumberOrThrow() { @@ -109,12 +109,12 @@ public void setUniqueLetter(Optional uniqueLetter) { this.uniqueLetter = uniqueLetter; } - public void setPageInfo(Optional v) { - Optional vv = PageInfo.normalizePageInfo(v); - if (!vv.equals(v)) { + public void setPageInfo(Optional pageInfo) { + Optional normalizedPageInfo = PageInfo.normalizePageInfo(pageInfo); + if (!normalizedPageInfo.equals(pageInfo)) { throw new IllegalArgumentException("setPageInfo argument is not normalized"); } - this.pageInfo = vv; + this.pageInfo = normalizedPageInfo; } public void setIsFirstAppearanceOfSource(boolean value) { @@ -124,19 +124,19 @@ public void setIsFirstAppearanceOfSource(boolean value) { /* * Setters for CitationGroups.distribute() */ - public static void setLookupResult(OOPair> x) { - Citation cit = x.a; - cit.db = x.b; + public static void setLookupResult(OOPair> pair) { + Citation cit = pair.a; + cit.db = pair.b; } - public static void setNumber(OOPair> x) { - Citation cit = x.a; - cit.number = x.b; + public static void setNumber(OOPair> pair) { + Citation cit = pair.a; + cit.number = pair.b; } - public static void setUniqueLetter(OOPair> x) { - Citation cit = x.a; - cit.uniqueLetter = x.b; + public static void setUniqueLetter(OOPair> pair) { + Citation cit = pair.a; + cit.uniqueLetter = pair.b; } } diff --git a/src/main/java/org/jabref/model/openoffice/style/CitationGroup.java b/src/main/java/org/jabref/model/openoffice/style/CitationGroup.java index 2986a6f4501..3b9021eb73f 100644 --- a/src/main/java/org/jabref/model/openoffice/style/CitationGroup.java +++ b/src/main/java/org/jabref/model/openoffice/style/CitationGroup.java @@ -121,8 +121,8 @@ public List getCitationsInLocalOrder() { * indexInGlobalOrder */ - public void setIndexInGlobalOrder(Optional i) { - this.indexInGlobalOrder = i; + public void setIndexInGlobalOrder(Optional indexInGlobalOrder) { + this.indexInGlobalOrder = indexInGlobalOrder; } public Optional getIndexInGlobalOrder() { diff --git a/src/main/java/org/jabref/model/openoffice/style/CitationGroups.java b/src/main/java/org/jabref/model/openoffice/style/CitationGroups.java index bd0cca64e13..0bfe4a832d0 100644 --- a/src/main/java/org/jabref/model/openoffice/style/CitationGroups.java +++ b/src/main/java/org/jabref/model/openoffice/style/CitationGroups.java @@ -79,29 +79,28 @@ public void lookupCitations(List databases) { /* * It is not clear which of the two solutions below is better. */ - if (true) { - // collect-lookup-distribute - // - // CitationDatabaseLookupResult for the same citation key is the same object. Until we - // insert a new citation from the GUI. - CitedKeys cks = getCitedKeysUnordered(); - cks.lookupInDatabases(databases); - cks.distributeLookupResults(this); - } else { - // lookup each citation directly - // - // CitationDatabaseLookupResult for the same citation key may be a different object: - // CitedKey.addPath has to use equals, so CitationDatabaseLookupResult has to override - // Object.equals, which depends on BibEntry.equals and BibDatabase.equals doing the - // right thing. Seems to work. But what we gained from avoiding collect-and-distribute - // may be lost in more complicated consistency checking in addPath. - // - for (CitationGroup cg : getCitationGroupsUnordered()) { - for (Citation cit : cg.citationsInStorageOrder) { - cit.lookupInDatabases(databases); - } - } - } + + // (1) collect-lookup-distribute + // + // CitationDatabaseLookupResult for the same citation key is the same object. Until we + // insert a new citation from the GUI. + CitedKeys cks = getCitedKeysUnordered(); + cks.lookupInDatabases(databases); + cks.distributeLookupResults(this); + + // (2) lookup each citation directly + // + // CitationDatabaseLookupResult for the same citation key may be a different object: + // CitedKey.addPath has to use equals, so CitationDatabaseLookupResult has to override + // Object.equals, which depends on BibEntry.equals and BibDatabase.equals doing the + // right thing. Seems to work. But what we gained from avoiding collect-and-distribute + // may be lost in more complicated consistency checking in addPath. + // + /// for (CitationGroup cg : getCitationGroupsUnordered()) { + /// for (Citation cit : cg.citationsInStorageOrder) { + /// cit.lookupInDatabases(databases); + /// } + /// } } public List getCitationGroupsUnordered() { @@ -162,11 +161,11 @@ public CitedKeys getCitedKeysUnordered() { int storageIndexInGroup = 0; for (Citation cit : cg.citationsInStorageOrder) { String key = cit.citationKey; - CitationPath p = new CitationPath(cg.cgid, storageIndexInGroup); + CitationPath path = new CitationPath(cg.cgid, storageIndexInGroup); if (res.containsKey(key)) { - res.get(key).addPath(p, cit); + res.get(key).addPath(path, cit); } else { - res.put(key, new CitedKey(key, p, cit)); + res.put(key, new CitedKey(key, path, cit)); } storageIndexInGroup++; } @@ -178,19 +177,19 @@ public CitedKeys getCitedKeysUnordered() { * CitedKeys created iterating citations in (globalOrder,localOrder) */ public CitedKeys getCitedKeysSortedInOrderOfAppearance() { - LinkedHashMap res = new LinkedHashMap<>(); if (!hasGlobalOrder()) { throw new IllegalStateException("getSortedCitedKeys: no globalOrder"); } + LinkedHashMap res = new LinkedHashMap<>(); for (CitationGroup cg : getCitationGroupsInGlobalOrder()) { for (int i : cg.getLocalOrder()) { Citation cit = cg.citationsInStorageOrder.get(i); String citationKey = cit.citationKey; - CitationPath p = new CitationPath(cg.cgid, i); + CitationPath path = new CitationPath(cg.cgid, i); if (res.containsKey(citationKey)) { - res.get(citationKey).addPath(p, cit); + res.get(citationKey).addPath(path, cit); } else { - res.put(citationKey, new CitedKey(citationKey, p, cit)); + res.put(citationKey, new CitedKey(citationKey, path, cit)); } } } diff --git a/src/main/java/org/jabref/model/openoffice/style/CitationLookupResult.java b/src/main/java/org/jabref/model/openoffice/style/CitationLookupResult.java index b17ec100ee2..86674583d1c 100644 --- a/src/main/java/org/jabref/model/openoffice/style/CitationLookupResult.java +++ b/src/main/java/org/jabref/model/openoffice/style/CitationLookupResult.java @@ -26,14 +26,14 @@ public CitationLookupResult(BibEntry entry, BibDatabase database) { * Since within each GUI call we use a fixed list of databases, it is OK. */ @Override - public boolean equals(Object o) { - if (o == this) { + public boolean equals(Object otherObject) { + if (otherObject == this) { return true; } - if (!(o instanceof CitationLookupResult)) { + if (!(otherObject instanceof CitationLookupResult)) { return false; } - CitationLookupResult that = (CitationLookupResult) o; + CitationLookupResult that = (CitationLookupResult) otherObject; return this.entry.equals(that.entry) && this.database.equals(that.database); } diff --git a/src/main/java/org/jabref/model/openoffice/style/CitedKey.java b/src/main/java/org/jabref/model/openoffice/style/CitedKey.java index f577867c539..785d828ce7e 100644 --- a/src/main/java/org/jabref/model/openoffice/style/CitedKey.java +++ b/src/main/java/org/jabref/model/openoffice/style/CitedKey.java @@ -27,11 +27,11 @@ public class CitedKey implements private Optional uniqueLetter; // For AuthorYear citation styles. private Optional normCitMarker; // For AuthorYear citation styles. - CitedKey(String citationKey, CitationPath p, Citation cit) { + CitedKey(String citationKey, CitationPath path, Citation cit) { this.citationKey = citationKey; this.where = new ArrayList<>(); // remember order - this.where.add(p); + this.where.add(path); // synchronized with Citation this.db = cit.getLookupResult(); @@ -100,8 +100,8 @@ public void setNormalizedCitationMarker(Optional normCitMarker) { /** * Appends to end of {@code where} */ - void addPath(CitationPath p, Citation cit) { - this.where.add(p); + void addPath(CitationPath path, Citation cit) { + this.where.add(path); // Check consistency if (!cit.getLookupResult().equals(this.db)) { diff --git a/src/main/java/org/jabref/model/openoffice/style/ComparableCitation.java b/src/main/java/org/jabref/model/openoffice/style/ComparableCitation.java index a6ac799b017..81120c09b2a 100644 --- a/src/main/java/org/jabref/model/openoffice/style/ComparableCitation.java +++ b/src/main/java/org/jabref/model/openoffice/style/ComparableCitation.java @@ -9,5 +9,5 @@ * Otherwise we sort citations as cited keys. */ public interface ComparableCitation extends ComparableCitedKey { - public Optional getPageInfo(); + Optional getPageInfo(); } diff --git a/src/main/java/org/jabref/model/openoffice/style/ComparableCitedKey.java b/src/main/java/org/jabref/model/openoffice/style/ComparableCitedKey.java index 0d4c8e2efce..23b3ffaba78 100644 --- a/src/main/java/org/jabref/model/openoffice/style/ComparableCitedKey.java +++ b/src/main/java/org/jabref/model/openoffice/style/ComparableCitedKey.java @@ -9,8 +9,8 @@ */ public interface ComparableCitedKey { - public String getCitationKey(); + String getCitationKey(); - public Optional getBibEntry(); + Optional getBibEntry(); } diff --git a/src/main/java/org/jabref/model/openoffice/style/CompareCitedKey.java b/src/main/java/org/jabref/model/openoffice/style/CompareCitedKey.java index a5484560316..7f6d3adfab0 100644 --- a/src/main/java/org/jabref/model/openoffice/style/CompareCitedKey.java +++ b/src/main/java/org/jabref/model/openoffice/style/CompareCitedKey.java @@ -24,18 +24,16 @@ public int compare(ComparableCitedKey a, ComparableCitedKey b) { Optional bBibEntry = b.getBibEntry(); final int mul = unresolvedComesFirst ? (+1) : (-1); - int res = 0; if (aBibEntry.isEmpty() && bBibEntry.isEmpty()) { // Both are unresolved: compare them by citation key. - res = a.getCitationKey().compareTo(b.getCitationKey()); + return a.getCitationKey().compareTo(b.getCitationKey()); } else if (aBibEntry.isEmpty()) { return -mul; } else if (bBibEntry.isEmpty()) { return mul; } else { // Proper comparison of entries - res = entryComparator.compare(aBibEntry.get(), bBibEntry.get()); + return entryComparator.compare(aBibEntry.get(), bBibEntry.get()); } - return res; } } diff --git a/src/main/java/org/jabref/model/openoffice/style/OODataModel.java b/src/main/java/org/jabref/model/openoffice/style/OODataModel.java index 20b2365ebf2..835715dfca5 100644 --- a/src/main/java/org/jabref/model/openoffice/style/OODataModel.java +++ b/src/main/java/org/jabref/model/openoffice/style/OODataModel.java @@ -26,8 +26,8 @@ public static List> fakePageInfos(String pageInfo, int nCitatio } if (pageInfo != null) { final int last = nCitations - 1; - Optional pi = Optional.ofNullable(OOText.fromString(pageInfo)); - pageInfos.set(last, PageInfo.normalizePageInfo(pi)); + Optional optionalPageInfo = Optional.ofNullable(OOText.fromString(pageInfo)); + pageInfos.set(last, PageInfo.normalizePageInfo(optionalPageInfo)); } return pageInfos; } diff --git a/src/main/java/org/jabref/model/openoffice/style/PageInfo.java b/src/main/java/org/jabref/model/openoffice/style/PageInfo.java index ace7eedae02..ec3a8436dcf 100644 --- a/src/main/java/org/jabref/model/openoffice/style/PageInfo.java +++ b/src/main/java/org/jabref/model/openoffice/style/PageInfo.java @@ -13,15 +13,16 @@ private PageInfo() { /* * pageInfo normalization */ - public static Optional normalizePageInfo(Optional o) { - if (o == null || o.isEmpty() || "".equals(OOText.toString(o.get()))) { + public static Optional normalizePageInfo(Optional optionalText) { + if (optionalText == null || optionalText.isEmpty() || "".equals(OOText.toString(optionalText.get()))) { return Optional.empty(); } - String s = OOText.toString(o.get()); - if (s.trim().equals("")) { + String str = OOText.toString(optionalText.get()); + String trimmed = str.trim(); + if (trimmed.equals("")) { return Optional.empty(); } - return Optional.of(OOText.fromString(s.trim())); + return Optional.of(OOText.fromString(trimmed)); } /** From 665ac66bff797f476be2e8e01dae3714a9f58da8 Mon Sep 17 00:00:00 2001 From: Antal K Date: Sun, 18 Jul 2021 08:57:21 +0200 Subject: [PATCH 32/51] replaced single-character names in OOBibStyle.java (in changed part) --- .../logic/openoffice/style/OOBibStyle.java | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/main/java/org/jabref/logic/openoffice/style/OOBibStyle.java b/src/main/java/org/jabref/logic/openoffice/style/OOBibStyle.java index feccb727f53..c612f179540 100644 --- a/src/main/java/org/jabref/logic/openoffice/style/OOBibStyle.java +++ b/src/main/java/org/jabref/logic/openoffice/style/OOBibStyle.java @@ -1019,36 +1019,36 @@ protected Map getCitProperties() { return citProperties; } - protected void addJournal(String s) { - journals.add(s); + protected void addJournal(String journalName) { + journals.add(journalName); } - protected void setLocalCopy(String s) { - localCopy = s; + protected void setLocalCopy(String contentsOfJstyleFile) { + localCopy = contentsOfJstyleFile; } - protected void setName(String s) { - name = s; + protected void setName(String nameOfTheStyle) { + name = nameOfTheStyle; } protected boolean getIsDefaultLayoutPresent() { return isDefaultLayoutPresent; } - protected void setIsDefaultLayoutPresent(boolean b) { - isDefaultLayoutPresent = b; + protected void setIsDefaultLayoutPresent(boolean isPresent) { + isDefaultLayoutPresent = isPresent; } - protected void setValid(boolean b) { - valid = b; + protected void setValid(boolean isValid) { + valid = isValid; } protected LayoutFormatterPreferences getPrefs() { return prefs; } - protected void setDefaultBibLayout(Layout l) { - defaultBibLayout = l; + protected void setDefaultBibLayout(Layout layout) { + defaultBibLayout = layout; } /** @@ -1270,9 +1270,9 @@ protected String getAuthorLastSeparator() { /* As getAuthorLastSeparator, for in-text citation. */ protected String getAuthorLastSeparatorInTextWithFallBack() { - String a = getStringCitProperty(OOBibStyle.AUTHOR_LAST_SEPARATOR_IN_TEXT); - String b = getStringCitProperty(OOBibStyle.AUTHOR_LAST_SEPARATOR); - return Objects.requireNonNullElse(a, b); + String value1 = getStringCitProperty(OOBibStyle.AUTHOR_LAST_SEPARATOR_IN_TEXT); + String value2 = getStringCitProperty(OOBibStyle.AUTHOR_LAST_SEPARATOR); + return Objects.requireNonNullElse(value1, value2); } protected String getPageInfoSeparator() { From 890fd2a56084ef5fe72d1e27393ad52b5b9841c2 Mon Sep 17 00:00:00 2001 From: Antal K Date: Sun, 18 Jul 2021 09:14:43 +0200 Subject: [PATCH 33/51] some longer names in OOBibStyleGetCitationMarker.java --- .../style/OOBibStyleGetCitationMarker.java | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/jabref/logic/openoffice/style/OOBibStyleGetCitationMarker.java b/src/main/java/org/jabref/logic/openoffice/style/OOBibStyleGetCitationMarker.java index 4235841e426..deb1a306913 100644 --- a/src/main/java/org/jabref/logic/openoffice/style/OOBibStyleGetCitationMarker.java +++ b/src/main/java/org/jabref/logic/openoffice/style/OOBibStyleGetCitationMarker.java @@ -503,6 +503,9 @@ private static OOText getAuthorYearParenthesisMarker2(OOBibStyle style, return OOText.fromString(sb.toString()); } + /** + * Add / override methods for the purpose of creating a normalized citation marker. + */ private static class CitationMarkerNormEntryWrap implements CitationMarkerEntry { CitationMarkerNormEntry inner; @@ -538,7 +541,7 @@ public boolean getIsFirstAppearanceOfSource() { } /** - * @param cne A citation to process. + * @param normEntry A citation to process. * * @return A normalized citation marker for deciding which * citations need uniqueLetters. @@ -548,10 +551,10 @@ public boolean getIsFirstAppearanceOfSource() { * Note: now includes some markup. */ static OOText getNormalizedCitationMarker(OOBibStyle style, - CitationMarkerNormEntry cne, + CitationMarkerNormEntry normEntry, Optional maxAuthorsOverride) { boolean[] startsNewGroup = {true}; - CitationMarkerEntry entry = new CitationMarkerNormEntryWrap(cne); + CitationMarkerEntry entry = new CitationMarkerNormEntryWrap(normEntry); return getAuthorYearParenthesisMarker2(style, AuthorYearMarkerPurpose.NORMALIZED, Collections.singletonList(entry), @@ -566,10 +569,10 @@ static OOText getNormalizedCitationMarker(OOBibStyle style, List normalizedMarkers = new ArrayList<>(citationMarkerEntries.size()); for (CitationMarkerEntry citationMarkerEntry : citationMarkerEntries) { - OOText nm = getNormalizedCitationMarker(style, - citationMarkerEntry, - maxAuthorsOverride); - normalizedMarkers.add(nm); + OOText normalized = getNormalizedCitationMarker(style, + citationMarkerEntry, + maxAuthorsOverride); + normalizedMarkers.add(normalized); } return normalizedMarkers; } From 0a267e5e60405484a5f5d2a1cc327177f77e09ca Mon Sep 17 00:00:00 2001 From: Antal K Date: Sun, 18 Jul 2021 14:34:54 +0200 Subject: [PATCH 34/51] drop normalizePageInfos, use 'preferred' and 'fallback' in getAuthorLastSeparatorInTextWithFallBack --- .../logic/openoffice/style/OOBibStyle.java | 36 ++----------------- 1 file changed, 3 insertions(+), 33 deletions(-) diff --git a/src/main/java/org/jabref/logic/openoffice/style/OOBibStyle.java b/src/main/java/org/jabref/logic/openoffice/style/OOBibStyle.java index c612f179540..e21804a2c86 100644 --- a/src/main/java/org/jabref/logic/openoffice/style/OOBibStyle.java +++ b/src/main/java/org/jabref/logic/openoffice/style/OOBibStyle.java @@ -42,8 +42,6 @@ import org.jabref.model.openoffice.style.CitationMarkerNumericBibEntry; import org.jabref.model.openoffice.style.CitationMarkerNumericEntry; import org.jabref.model.openoffice.style.NonUniqueCitationMarker; -import org.jabref.model.openoffice.style.PageInfo; -import org.jabref.model.openoffice.util.OOListUtil; import org.jabref.model.strings.StringUtil; import org.slf4j.Logger; @@ -1080,34 +1078,6 @@ public OOText getNumCitationMarkerForBibliography(CitationMarkerNumericBibEntry return OOBibStyleGetNumCitationMarker.getNumCitationMarkerForBibliography(this, entry); } - /** - * Make sure that (1) we have exactly one entry for each - * citation, (2) each entry is either Optional.empty or its content is not empty when trimmed. - * - * As a special case: pageInfos may be null. In this case - * the result is a list filled with Optional.empty() values. - */ - static List> - normalizePageInfos(List> pageInfos, int nCitations) { - - // translate null to all-empty - if (pageInfos == null) { - List> res = new ArrayList<>(nCitations); - for (int i = 0; i < nCitations; i++) { - res.add(Optional.empty()); - } - return res; - } - - // not null, check size - if (pageInfos.size() != nCitations) { - throw new IllegalArgumentException("normalizePageInfos: pageInfos.size() != nCitations"); - } - - // not null, normalize elementwise - return OOListUtil.map(pageInfos, PageInfo::normalizePageInfo); - } - public OOText getNormalizedCitationMarker(CitationMarkerNormEntry ce) { return OOBibStyleGetCitationMarker.getNormalizedCitationMarker(this, ce, Optional.empty()); } @@ -1270,9 +1240,9 @@ protected String getAuthorLastSeparator() { /* As getAuthorLastSeparator, for in-text citation. */ protected String getAuthorLastSeparatorInTextWithFallBack() { - String value1 = getStringCitProperty(OOBibStyle.AUTHOR_LAST_SEPARATOR_IN_TEXT); - String value2 = getStringCitProperty(OOBibStyle.AUTHOR_LAST_SEPARATOR); - return Objects.requireNonNullElse(value1, value2); + String preferred = getStringCitProperty(OOBibStyle.AUTHOR_LAST_SEPARATOR_IN_TEXT); + String fallback = getStringCitProperty(OOBibStyle.AUTHOR_LAST_SEPARATOR); + return Objects.requireNonNullElse(preferred, fallback); } protected String getPageInfoSeparator() { From 5be1e74e2904f9fed815e5a8760bbf9f1ea719d4 Mon Sep 17 00:00:00 2001 From: Antal K Date: Sun, 18 Jul 2021 14:53:20 +0200 Subject: [PATCH 35/51] checkstyle --- src/main/java/org/jabref/logic/openoffice/style/OOBibStyle.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/jabref/logic/openoffice/style/OOBibStyle.java b/src/main/java/org/jabref/logic/openoffice/style/OOBibStyle.java index e21804a2c86..082d94a7fdf 100644 --- a/src/main/java/org/jabref/logic/openoffice/style/OOBibStyle.java +++ b/src/main/java/org/jabref/logic/openoffice/style/OOBibStyle.java @@ -1241,7 +1241,7 @@ protected String getAuthorLastSeparator() { /* As getAuthorLastSeparator, for in-text citation. */ protected String getAuthorLastSeparatorInTextWithFallBack() { String preferred = getStringCitProperty(OOBibStyle.AUTHOR_LAST_SEPARATOR_IN_TEXT); - String fallback = getStringCitProperty(OOBibStyle.AUTHOR_LAST_SEPARATOR); + String fallback = getStringCitProperty(OOBibStyle.AUTHOR_LAST_SEPARATOR); return Objects.requireNonNullElse(preferred, fallback); } From f9c0415fcd59e12c480879f51c0205ab3d271fbe Mon Sep 17 00:00:00 2001 From: Antal K Date: Sun, 18 Jul 2021 14:53:34 +0200 Subject: [PATCH 36/51] use putIfAbsent --- .../style/OOProcessAuthorYearMarkers.java | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/jabref/logic/openoffice/style/OOProcessAuthorYearMarkers.java b/src/main/java/org/jabref/logic/openoffice/style/OOProcessAuthorYearMarkers.java index cbd03cd79c6..896e747913f 100644 --- a/src/main/java/org/jabref/logic/openoffice/style/OOProcessAuthorYearMarkers.java +++ b/src/main/java/org/jabref/logic/openoffice/style/OOProcessAuthorYearMarkers.java @@ -63,17 +63,10 @@ private static void createUniqueLetters(CitedKeys sortedCitedKeys, CitationGroup String normCitMarker = OOText.toString(citedKey.getNormalizedCitationMarker().get()); String citationKey = citedKey.citationKey; - if (!normCitMarkerToClachingKeys.containsKey(normCitMarker)) { - // Found new normCitMarker - List clashingKeys = new ArrayList<>(1); - normCitMarkerToClachingKeys.put(normCitMarker, clashingKeys); + List clashingKeys = normCitMarkerToClachingKeys.putIfAbsent(normCitMarker, new ArrayList<>(1)); + if (!clashingKeys.contains(citationKey)) { + // First appearance of citationKey, add to list. clashingKeys.add(citationKey); - } else { - List clashingKeys = normCitMarkerToClachingKeys.get(normCitMarker); - if (!clashingKeys.contains(citationKey)) { - // First appearance of citationKey, add to list. - clashingKeys.add(citationKey); - } } } From 843b7ac03ba5e531945f0d4a1e9d2f52d6cfa810 Mon Sep 17 00:00:00 2001 From: Antal K Date: Sun, 18 Jul 2021 14:56:59 +0200 Subject: [PATCH 37/51] use "{}" with LOGGER --- .../java/org/jabref/logic/openoffice/style/StyleLoader.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/jabref/logic/openoffice/style/StyleLoader.java b/src/main/java/org/jabref/logic/openoffice/style/StyleLoader.java index 727db88cbc4..3dbdf766056 100644 --- a/src/main/java/org/jabref/logic/openoffice/style/StyleLoader.java +++ b/src/main/java/org/jabref/logic/openoffice/style/StyleLoader.java @@ -92,9 +92,9 @@ private void loadExternalStyles() { } } catch (FileNotFoundException e) { // The file couldn't be found... should we tell anyone? - LOGGER.info("Cannot find external style file " + filename); + LOGGER.info("Cannot find external style file {}", filename); } catch (IOException e) { - LOGGER.info("Problem reading external style file " + filename, e); + LOGGER.info("Problem reading external style file {}", filename, e); } } } From 09f55036dacb9c6e5fdac76f7b6a37940e3642d0 Mon Sep 17 00:00:00 2001 From: Antal K Date: Sun, 18 Jul 2021 16:52:37 +0200 Subject: [PATCH 38/51] use Objects.hash and Objects.equals in CitationLookupResult --- .../openoffice/style/CitationLookupResult.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/jabref/model/openoffice/style/CitationLookupResult.java b/src/main/java/org/jabref/model/openoffice/style/CitationLookupResult.java index 86674583d1c..bfe6dc1debe 100644 --- a/src/main/java/org/jabref/model/openoffice/style/CitationLookupResult.java +++ b/src/main/java/org/jabref/model/openoffice/style/CitationLookupResult.java @@ -24,6 +24,11 @@ public CitationLookupResult(BibEntry entry, BibDatabase database) { * is equivalent to {@code this.database == that.database}. * * Since within each GUI call we use a fixed list of databases, it is OK. + * + * CitationLookupResult.equals is used in CitedKey.addPath to check the added Citation + * refers to the same source as the others. As long as we look up each citation key + * only once (in CitationGroups.lookupCitations), the default implementation for equals + * would be sufficient (and could also omit hashCode below). */ @Override public boolean equals(Object otherObject) { @@ -34,15 +39,11 @@ public boolean equals(Object otherObject) { return false; } CitationLookupResult that = (CitationLookupResult) otherObject; - return this.entry.equals(that.entry) && this.database.equals(that.database); + return Objects.equals(this.entry, that.entry) && Objects.equals(this.database, that.database); } @Override public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + entry.hashCode(); - result = prime * result + database.hashCode(); - return result; + return Objects.hash(entry, database); } } From d6a22be9fad6196eff79ae79309ea4ce2ba509d0 Mon Sep 17 00:00:00 2001 From: Antal K Date: Sun, 18 Jul 2021 17:05:17 +0200 Subject: [PATCH 39/51] simplified CitedKey.getBibEntry --- src/main/java/org/jabref/model/openoffice/style/CitedKey.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/org/jabref/model/openoffice/style/CitedKey.java b/src/main/java/org/jabref/model/openoffice/style/CitedKey.java index 785d828ce7e..5331204bd75 100644 --- a/src/main/java/org/jabref/model/openoffice/style/CitedKey.java +++ b/src/main/java/org/jabref/model/openoffice/style/CitedKey.java @@ -52,9 +52,7 @@ public String getCitationKey() { @Override public Optional getBibEntry() { - return (db.isPresent() - ? Optional.of(db.get().entry) - : Optional.empty()); + return db.map(e -> e.entry); } /* From 8395e0c7bd17b27c883a56bb81f2e7491d773e10 Mon Sep 17 00:00:00 2001 From: Antal K Date: Sun, 18 Jul 2021 18:11:28 +0200 Subject: [PATCH 40/51] more use of "{}" in LOGGER --- .../jabref/logic/openoffice/style/StyleLoader.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/jabref/logic/openoffice/style/StyleLoader.java b/src/main/java/org/jabref/logic/openoffice/style/StyleLoader.java index 3dbdf766056..3098e06233c 100644 --- a/src/main/java/org/jabref/logic/openoffice/style/StyleLoader.java +++ b/src/main/java/org/jabref/logic/openoffice/style/StyleLoader.java @@ -61,19 +61,19 @@ public boolean addStyleIfValid(String filename) { try { OOBibStyle newStyle = new OOBibStyle(new File(filename), layoutFormatterPreferences, encoding); if (externalStyles.contains(newStyle)) { - LOGGER.info("External style file " + filename + " already existing."); + LOGGER.info("External style file {} already existing.", filename); } else if (newStyle.isValid()) { externalStyles.add(newStyle); storeExternalStyles(); return true; } else { - LOGGER.error(String.format("Style with filename %s is invalid", filename)); + LOGGER.error("Style with filename {} is invalid", filename); } } catch (FileNotFoundException e) { // The file couldn't be found... should we tell anyone? - LOGGER.info("Cannot find external style file " + filename, e); + LOGGER.info("Cannot find external style file {}", filename, e); } catch (IOException e) { - LOGGER.info("Problem reading external style file " + filename, e); + LOGGER.info("Problem reading external style file {}", filename, e); } return false; } @@ -88,7 +88,7 @@ private void loadExternalStyles() { if (style.isValid()) { // Problem! externalStyles.add(style); } else { - LOGGER.error(String.format("Style with filename %s is invalid", filename)); + LOGGER.error("Style with filename {} is invalid", filename); } } catch (FileNotFoundException e) { // The file couldn't be found... should we tell anyone? @@ -105,7 +105,7 @@ private void loadInternalStyles() { try { internalStyles.add(new OOBibStyle(filename, layoutFormatterPreferences)); } catch (IOException e) { - LOGGER.info("Problem reading internal style file " + filename, e); + LOGGER.info("Problem reading internal style file {}", filename, e); } } } From 0343d8139c42789393036a3c0d20671ea9fd3ccb Mon Sep 17 00:00:00 2001 From: Antal K Date: Sun, 18 Jul 2021 18:11:50 +0200 Subject: [PATCH 41/51] more use of "{}" in LOGGER --- .../logic/openoffice/backend/NamedRangeReferenceMark.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/jabref/logic/openoffice/backend/NamedRangeReferenceMark.java b/src/main/java/org/jabref/logic/openoffice/backend/NamedRangeReferenceMark.java index 63d5f917fc5..29a90601056 100644 --- a/src/main/java/org/jabref/logic/openoffice/backend/NamedRangeReferenceMark.java +++ b/src/main/java/org/jabref/logic/openoffice/backend/NamedRangeReferenceMark.java @@ -198,8 +198,7 @@ public Optional nrGetRawCursor(XTextDocument doc) Optional markAsTextContent = UnoReferenceMark.getAsTextContent(doc, name); if (markAsTextContent.isEmpty()) { - String msg = String.format("nrGetRawCursor: markAsTextContent(%s).isEmpty()", name); - LOGGER.warn(msg); + LOGGER.warn("nrGetRawCursor: markAsTextContent({}).isEmpty()", name); } full = UnoCursor.getTextCursorOfTextContentAnchor(markAsTextContent.get()); @@ -338,7 +337,7 @@ public XTextCursor nrGetFillCursor(XTextDocument doc) alpha.goRight(leftLength, true); LOGGER.debug("nrGetFillCursor: alpha(7) covers '{}', should be '{}'", alpha.getString(), left); omega.goLeft(rightLength, true); - LOGGER.debug("nrGetFillCursor: omega(8) covers '%s', should be '%s'%n", omega.getString(), right); + LOGGER.debug("nrGetFillCursor: omega(8) covers '{}', should be '{}'", omega.getString(), right); } } From 9890320d5b9f66d068c309ce9f95857d102243a2 Mon Sep 17 00:00:00 2001 From: Antal K Date: Sun, 18 Jul 2021 18:11:56 +0200 Subject: [PATCH 42/51] more use of "{}" in LOGGER --- .../logic/openoffice/frontend/UpdateCitationMarkers.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/jabref/logic/openoffice/frontend/UpdateCitationMarkers.java b/src/main/java/org/jabref/logic/openoffice/frontend/UpdateCitationMarkers.java index b3f90d380a7..a3363938797 100644 --- a/src/main/java/org/jabref/logic/openoffice/frontend/UpdateCitationMarkers.java +++ b/src/main/java/org/jabref/logic/openoffice/frontend/UpdateCitationMarkers.java @@ -63,9 +63,8 @@ public static void applyNewCitationMarkers(XTextDocument doc, OOFrontend fr, OOB Optional marker = cg.getCitationMarker(); if (!marker.isPresent()) { - String msg = String.format("applyNewCitationMarkers: no marker for %s", - cg.cgid.citationGroupIdAsString()); - LOGGER.warn(msg); + LOGGER.warn("applyNewCitationMarkers: no marker for {}", + cg.cgid.citationGroupIdAsString()); continue; } From b34803f47514a7417bf32cd507b25b2272474922 Mon Sep 17 00:00:00 2001 From: Antal K Date: Sun, 25 Jul 2021 00:17:31 +0200 Subject: [PATCH 43/51] Citation.lookup: use streams --- .../model/openoffice/style/Citation.java | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/jabref/model/openoffice/style/Citation.java b/src/main/java/org/jabref/model/openoffice/style/Citation.java index 9b71a1051b2..9b286029310 100644 --- a/src/main/java/org/jabref/model/openoffice/style/Citation.java +++ b/src/main/java/org/jabref/model/openoffice/style/Citation.java @@ -62,14 +62,18 @@ public Optional getBibEntry() { : Optional.empty()); } + public static Optional lookup(BibDatabase database, String key) { + return (database + .getEntryByCitationKey(key) + .map(bibEntry -> new CitationLookupResult(bibEntry, database))); + } + public static Optional lookup(List databases, String key) { - for (BibDatabase database : databases) { - Optional entry = database.getEntryByCitationKey(key); - if (entry.isPresent()) { - return Optional.of(new CitationLookupResult(entry.get(), database)); - } - } - return Optional.empty(); + return (databases.stream() + .map(database -> Citation.lookup(database, key)) + .filter(Optional::isPresent) + .findFirst() + .orElse(Optional.empty())); } public void lookupInDatabases(List databases) { From 04905474345099bf9594f0bd138081491d54391c Mon Sep 17 00:00:00 2001 From: Antal K Date: Sun, 25 Jul 2021 01:00:26 +0200 Subject: [PATCH 44/51] Citation.lookup: Optional::get before findFirst --- src/main/java/org/jabref/model/openoffice/style/Citation.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/jabref/model/openoffice/style/Citation.java b/src/main/java/org/jabref/model/openoffice/style/Citation.java index 9b286029310..7bae4fd2548 100644 --- a/src/main/java/org/jabref/model/openoffice/style/Citation.java +++ b/src/main/java/org/jabref/model/openoffice/style/Citation.java @@ -72,8 +72,8 @@ public static Optional lookup(List databases, return (databases.stream() .map(database -> Citation.lookup(database, key)) .filter(Optional::isPresent) - .findFirst() - .orElse(Optional.empty())); + .map(Optional::get) + .findFirst()); } public void lookupInDatabases(List databases) { From dd0c039b192eacbcef13f7b894c7b7f112a81a47 Mon Sep 17 00:00:00 2001 From: Antal K Date: Mon, 2 Aug 2021 16:25:40 +0200 Subject: [PATCH 45/51] putIfAbsent returns null for new entry --- .../logic/openoffice/style/OOProcessAuthorYearMarkers.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/org/jabref/logic/openoffice/style/OOProcessAuthorYearMarkers.java b/src/main/java/org/jabref/logic/openoffice/style/OOProcessAuthorYearMarkers.java index 896e747913f..306c0111a22 100644 --- a/src/main/java/org/jabref/logic/openoffice/style/OOProcessAuthorYearMarkers.java +++ b/src/main/java/org/jabref/logic/openoffice/style/OOProcessAuthorYearMarkers.java @@ -64,6 +64,9 @@ private static void createUniqueLetters(CitedKeys sortedCitedKeys, CitationGroup String citationKey = citedKey.citationKey; List clashingKeys = normCitMarkerToClachingKeys.putIfAbsent(normCitMarker, new ArrayList<>(1)); + if (clashingKeys == null) { + clashingKeys = normCitMarkerToClachingKeys.get(normCitMarker); + } if (!clashingKeys.contains(citationKey)) { // First appearance of citationKey, add to list. clashingKeys.add(citationKey); From 2f36705c5990b32d83eb3bd6b2339c6b580a5036 Mon Sep 17 00:00:00 2001 From: Antal K Date: Tue, 3 Aug 2021 15:40:03 +0200 Subject: [PATCH 46/51] What is 52 in Backend52 --- .../java/org/jabref/logic/openoffice/backend/Backend52.java | 5 +++++ .../java/org/jabref/logic/openoffice/backend/Codec52.java | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/jabref/logic/openoffice/backend/Backend52.java b/src/main/java/org/jabref/logic/openoffice/backend/Backend52.java index cf91ad1043e..c03bd24496a 100644 --- a/src/main/java/org/jabref/logic/openoffice/backend/Backend52.java +++ b/src/main/java/org/jabref/logic/openoffice/backend/Backend52.java @@ -39,6 +39,11 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +/** + * Backend52, Codec52 and OODataModel.JabRef52 refer to the mode of storage, encoding and + * what-is-stored in the document under JabRef version 5.2. These basically did not change up to + * JabRef 5.4. + */ public class Backend52 { private static final Logger LOGGER = LoggerFactory.getLogger(Backend52.class); public final OODataModel dataModel; diff --git a/src/main/java/org/jabref/logic/openoffice/backend/Codec52.java b/src/main/java/org/jabref/logic/openoffice/backend/Codec52.java index e4405dc6115..93f57e5d99d 100644 --- a/src/main/java/org/jabref/logic/openoffice/backend/Codec52.java +++ b/src/main/java/org/jabref/logic/openoffice/backend/Codec52.java @@ -13,7 +13,7 @@ import org.jabref.model.openoffice.uno.NoDocumentException; /** - * How and what is encoded in a mark names. + * How and what is encoded in reference mark names under JabRef 5.2. * * - pageInfo does not appear here. It is not encoded in the mark name. */ From d2cf3731a42b85fcf8f4e9da94cc5ab9fb3afd0d Mon Sep 17 00:00:00 2001 From: Antal K Date: Fri, 20 Aug 2021 12:09:54 +0200 Subject: [PATCH 47/51] apply 2021-08-20-a/oobranch-E-update.patch Brings oobranch-E up to 89b096881 @ origin/improve-reversibility-rebased-03 Merge remote-tracking branch 'upstream/main' into improve-reversibility-rebased-03 --- .../logic/openoffice/backend/Backend52.java | 60 +++++-------------- .../logic/openoffice/backend/Codec52.java | 19 +++--- .../logic/openoffice/backend/GetContext.java | 8 +-- .../backend/NamedRangeReferenceMark.java | 35 +++++------ .../style/OOProcessAuthorYearMarkers.java | 3 - .../model/openoffice/backend/NamedRange.java | 21 +++---- .../openoffice/backend/NamedRangeManager.java | 14 ++--- 7 files changed, 58 insertions(+), 102 deletions(-) diff --git a/src/main/java/org/jabref/logic/openoffice/backend/Backend52.java b/src/main/java/org/jabref/logic/openoffice/backend/Backend52.java index c03bd24496a..878571090f0 100644 --- a/src/main/java/org/jabref/logic/openoffice/backend/Backend52.java +++ b/src/main/java/org/jabref/logic/openoffice/backend/Backend52.java @@ -28,10 +28,7 @@ import com.sun.star.beans.IllegalTypeException; import com.sun.star.beans.NotRemoveableException; -import com.sun.star.beans.PropertyExistException; import com.sun.star.beans.PropertyVetoException; -import com.sun.star.beans.UnknownPropertyException; -import com.sun.star.container.NoSuchElementException; import com.sun.star.lang.WrappedTargetException; import com.sun.star.text.XTextCursor; import com.sun.star.text.XTextDocument; @@ -39,11 +36,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * Backend52, Codec52 and OODataModel.JabRef52 refer to the mode of storage, encoding and - * what-is-stored in the document under JabRef version 5.2. These basically did not change up to - * JabRef 5.4. - */ public class Backend52 { private static final Logger LOGGER = LoggerFactory.getLogger(Backend52.class); public final OODataModel dataModel; @@ -118,25 +110,17 @@ public Optional healthReport(XTextDocument doc) private static void setPageInfoInDataInitial(List citations, Optional pageInfo) { // attribute to last citation (initially localOrder == storageOrder) - if (citations.size() > 0) { - citations.get(citations.size() - 1).setPageInfo(pageInfo); - } - } - - private static void setPageInfoInData(CitationGroup cg, Optional pageInfo) { - List citations = cg.getCitationsInLocalOrder(); - if (citations.size() > 0) { + if (!citations.isEmpty()) { citations.get(citations.size() - 1).setPageInfo(pageInfo); } } private static Optional getPageInfoFromData(CitationGroup cg) { List citations = cg.getCitationsInLocalOrder(); - if (citations.size() > 0) { - return citations.get(citations.size() - 1).getPageInfo(); - } else { + if (citations.isEmpty()) { return Optional.empty(); } + return citations.get(citations.size() - 1).getPageInfo(); } /** @@ -148,20 +132,18 @@ public CitationGroup readCitationGroupFromDocumentOrThrow(XTextDocument doc, Str WrappedTargetException, NoDocumentException { - Optional op = Codec52.parseMarkName(refMarkName); - if (op.isEmpty()) { + Optional optionalParsed = Codec52.parseMarkName(refMarkName); + if (optionalParsed.isEmpty()) { throw new IllegalArgumentException("readCitationGroupFromDocumentOrThrow:" + " found unparsable referenceMarkName"); } - Codec52.ParsedMarkName ov = op.get(); - CitationGroupId cgid = new CitationGroupId(refMarkName); - List citations = (ov.citationKeys.stream() + Codec52.ParsedMarkName parsed = optionalParsed.get(); + List citations = (parsed.citationKeys.stream() .map(Citation::new) .collect(Collectors.toList())); - Optional pageInfo = - (UnoUserDefinedProperty.getStringValue(doc, refMarkName) - .map(OOText::fromString)); + Optional pageInfo = (UnoUserDefinedProperty.getStringValue(doc, refMarkName) + .map(OOText::fromString)); pageInfo = PageInfo.normalizePageInfo(pageInfo); setPageInfoInDataInitial(citations, pageInfo); @@ -173,9 +155,10 @@ public CitationGroup readCitationGroupFromDocumentOrThrow(XTextDocument doc, Str + " referenceMarkName is not in the document"); } + CitationGroupId cgid = new CitationGroupId(refMarkName); CitationGroup cg = new CitationGroup(OODataModel.JabRef52, cgid, - ov.citationType, + parsed.citationType, citations, Optional.of(refMarkName)); this.cgidToNamedRange.put(cgid, namedRange.get()); @@ -203,7 +186,6 @@ public CitationGroup createCitationGroup(XTextDocument doc, NoDocumentException, WrappedTargetException, NotRemoveableException, - PropertyExistException, PropertyVetoException, IllegalTypeException { @@ -243,6 +225,8 @@ public CitationGroup createCitationGroup(XTextDocument doc, case JabRef60: cit.setPageInfo(pageInfo); break; + default: + throw new IllegalStateException("Unhandled dataModel in Backend52.createCitationGroup"); } } @@ -325,7 +309,6 @@ public List> combinePageInfos(List joinableGroup private NamedRange getNamedRangeOrThrow(CitationGroup cg) { NamedRange namedRange = this.cgidToNamedRange.get(cg.cgid); if (namedRange == null) { - String msg = "getNamedRange: could not lookup namedRange"; throw new IllegalStateException("getNamedRange: could not lookup namedRange"); } return namedRange; @@ -335,11 +318,7 @@ public void removeCitationGroup(CitationGroup cg, XTextDocument doc) throws WrappedTargetException, NoDocumentException, - NoSuchElementException, - NotRemoveableException, - IllegalTypeException, - PropertyExistException { - + NotRemoveableException { NamedRange namedRange = getNamedRangeOrThrow(cg); String refMarkName = namedRange.nrGetRangeName(); namedRange.nrRemoveFromDocument(doc); @@ -388,15 +367,13 @@ public XTextCursor getFillCursorForCitationGroup(CitationGroup cg, XTextDocument public void cleanFillCursorForCitationGroup(CitationGroup cg, XTextDocument doc) throws NoDocumentException, - WrappedTargetException, - CreationException { + WrappedTargetException { NamedRange namedRange = getNamedRangeOrThrow(cg); namedRange.nrCleanFillCursor(doc); } public List getCitationEntries(XTextDocument doc, CitationGroups cgs) throws - UnknownPropertyException, WrappedTargetException, NoDocumentException { @@ -404,8 +381,7 @@ public List getCitationEntries(XTextDocument doc, CitationGroups case JabRef52: // One context per CitationGroup: Backend52 (DataModel.JabRef52) // For DataModel.JabRef60 (Backend60) we need one context per Citation - int n = cgs.numberOfCitationGroups(); - List citations = new ArrayList<>(n); + List citations = new ArrayList<>(cgs.numberOfCitationGroups()); for (CitationGroup cg : cgs.getCitationGroupsUnordered()) { String name = cg.cgid.citationGroupIdAsString(); XTextCursor cursor = (this @@ -433,13 +409,9 @@ public List getCitationEntries(XTextDocument doc, CitationGroups */ public void applyCitationEntries(XTextDocument doc, List citationEntries) throws - UnknownPropertyException, - NotRemoveableException, - PropertyExistException, PropertyVetoException, IllegalTypeException, IllegalArgumentException, - NoDocumentException, WrappedTargetException { switch (dataModel) { diff --git a/src/main/java/org/jabref/logic/openoffice/backend/Codec52.java b/src/main/java/org/jabref/logic/openoffice/backend/Codec52.java index 93f57e5d99d..f6d2b12245e 100644 --- a/src/main/java/org/jabref/logic/openoffice/backend/Codec52.java +++ b/src/main/java/org/jabref/logic/openoffice/backend/Codec52.java @@ -10,10 +10,9 @@ import java.util.stream.Collectors; import org.jabref.model.openoffice.style.CitationType; -import org.jabref.model.openoffice.uno.NoDocumentException; /** - * How and what is encoded in reference mark names under JabRef 5.2. + * How and what is encoded in a mark names. * * - pageInfo does not appear here. It is not encoded in the mark name. */ @@ -24,6 +23,10 @@ class Codec52 { // citationType is always "1" "2" or "3" Pattern.compile(BIB_CITATION + "(\\d*)_([123])_(.*)"); + private Codec52() { + /**/ + } + /** * This is what we get back from parsing a refMarkName. */ @@ -47,7 +50,7 @@ public static class ParsedMarkName { /** * Integer representation was written into the document in JabRef52, keep it for compatibility. */ - public static CitationType CitationTypeFromInt(int i) { + private static CitationType citationTypeFromInt(int i) { switch (i) { case 1: return CitationType.AUTHORYEAR_PAR; @@ -60,7 +63,7 @@ public static CitationType CitationTypeFromInt(int i) { } } - public static int CitationTypeToInt(CitationType i) { + private static int citationTypeToInt(CitationType i) { switch (i) { case AUTHORYEAR_PAR: return 1; @@ -86,12 +89,10 @@ public static int CitationTypeToInt(CitationType i) { */ public static String getUniqueMarkName(Set usedNames, String bibtexKey, - CitationType citationType) - throws - NoDocumentException { + CitationType citationType) { int i = 0; - int citTypeCode = CitationTypeToInt(citationType); + int citTypeCode = citationTypeToInt(citationType); String name = BIB_CITATION + '_' + citTypeCode + '_' + bibtexKey; while (usedNames.contains(name)) { name = BIB_CITATION + i + '_' + citTypeCode + '_' + bibtexKey; @@ -116,7 +117,7 @@ public static Optional parseMarkName(String refMarkName) { List keys = Arrays.asList(citeMatcher.group(3).split(",")); String i = citeMatcher.group(1); int citTypeCode = Integer.parseInt(citeMatcher.group(2)); - CitationType citationType = CitationTypeFromInt(citTypeCode); + CitationType citationType = citationTypeFromInt(citTypeCode); return (Optional.of(new Codec52.ParsedMarkName(i, citationType, keys))); } diff --git a/src/main/java/org/jabref/logic/openoffice/backend/GetContext.java b/src/main/java/org/jabref/logic/openoffice/backend/GetContext.java index 4cde12ad236..ffce5b9553e 100644 --- a/src/main/java/org/jabref/logic/openoffice/backend/GetContext.java +++ b/src/main/java/org/jabref/logic/openoffice/backend/GetContext.java @@ -1,8 +1,5 @@ package org.jabref.logic.openoffice.backend; -import org.jabref.model.openoffice.uno.NoDocumentException; - -import com.sun.star.lang.WrappedTargetException; import com.sun.star.text.XTextCursor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -34,10 +31,7 @@ private GetContext() { public static String getCursorStringWithContext(XTextCursor cursor, int charBefore, int charAfter, - boolean htmlMarkup) - throws - WrappedTargetException, - NoDocumentException { + boolean htmlMarkup) { String citPart = cursor.getString(); diff --git a/src/main/java/org/jabref/logic/openoffice/backend/NamedRangeReferenceMark.java b/src/main/java/org/jabref/logic/openoffice/backend/NamedRangeReferenceMark.java index 29a90601056..a11e29aecd1 100644 --- a/src/main/java/org/jabref/logic/openoffice/backend/NamedRangeReferenceMark.java +++ b/src/main/java/org/jabref/logic/openoffice/backend/NamedRangeReferenceMark.java @@ -8,7 +8,6 @@ import org.jabref.model.openoffice.uno.UnoCursor; import org.jabref.model.openoffice.uno.UnoReferenceMark; -import com.sun.star.container.NoSuchElementException; import com.sun.star.lang.WrappedTargetException; import com.sun.star.text.XText; import com.sun.star.text.XTextContent; @@ -57,24 +56,24 @@ String getId() { * {@code safeInsertSpaces(n): para, para, left, space(n), right-delete, left(n), left-delete} * * @param position Where to insert (at position.getStart()) - * @param n Number of spaces to insert. + * @param numSpaces Number of spaces to insert. * * @return a new cursor, covering the just-inserted spaces. * */ - private static XTextCursor safeInsertSpacesBetweenReferenceMarks(XTextRange position, int n) { + private static XTextCursor safeInsertSpacesBetweenReferenceMarks(XTextRange position, int numSpaces) { // Start with an empty cursor at position.getStart(); XText text = position.getText(); XTextCursor cursor = text.createTextCursorByRange(position.getStart()); text.insertString(cursor, "\r\r", false); // para, para cursor.goLeft((short) 1, false); // left - text.insertString(cursor, " ".repeat(n), false); // space(n) + text.insertString(cursor, " ".repeat(numSpaces), false); // space(numSpaces) cursor.goRight((short) 1, true); cursor.setString(""); // right-delete - cursor.goLeft((short) n, false); // left(n) + cursor.goLeft((short) numSpaces, false); // left(numSpaces) cursor.goLeft((short) 1, true); cursor.setString(""); // left-delete - cursor.goRight((short) n, true); // select the newly inserted spaces + cursor.goRight((short) numSpaces, true); // select the newly inserted spaces return cursor; } @@ -101,8 +100,6 @@ private static void createReprInDocument(XTextDocument doc, final String left = NamedRangeReferenceMark.REFERENCE_MARK_LEFT_BRACKET; final String right = NamedRangeReferenceMark.REFERENCE_MARK_RIGHT_BRACKET; - final short leftLength = (short) left.length(); - final short rightLength = (short) right.length(); String bracketedContent = (withoutBrackets ? "" : left + right); @@ -111,9 +108,11 @@ private static void createReprInDocument(XTextDocument doc, UnoReferenceMark.create(doc, refMarkName, cursor, true /* absorb */); + // eat the first inserted space cursorBefore.goRight((short) 1, true); cursorBefore.setString(""); if (!insertSpaceAfter) { + // eat the second inserted space cursorAfter.goLeft((short) 1, true); cursorAfter.setString(""); } @@ -151,8 +150,7 @@ static Optional getFromDocument(XTextDocument doc, Stri public void nrRemoveFromDocument(XTextDocument doc) throws WrappedTargetException, - NoDocumentException, - NoSuchElementException { + NoDocumentException { UnoReferenceMark.removeIfExists(doc, this.nrGetRangeName()); } @@ -193,7 +191,6 @@ public Optional nrGetRawCursor(XTextDocument doc) WrappedTargetException { String name = this.nrGetRangeName(); - Optional full = Optional.empty(); Optional markAsTextContent = UnoReferenceMark.getAsTextContent(doc, name); @@ -201,10 +198,9 @@ public Optional nrGetRawCursor(XTextDocument doc) LOGGER.warn("nrGetRawCursor: markAsTextContent({}).isEmpty()", name); } - full = UnoCursor.getTextCursorOfTextContentAnchor(markAsTextContent.get()); + Optional full = UnoCursor.getTextCursorOfTextContentAnchor(markAsTextContent.get()); if (full.isEmpty()) { - String msg = "nrGetRawCursor: full.isEmpty()"; - LOGGER.warn(msg); + LOGGER.warn("nrGetRawCursor: full.isEmpty()"); return Optional.empty(); } return full; @@ -224,8 +220,6 @@ public XTextCursor nrGetFillCursor(XTextDocument doc) final String left = NamedRangeReferenceMark.REFERENCE_MARK_LEFT_BRACKET; final String right = NamedRangeReferenceMark.REFERENCE_MARK_RIGHT_BRACKET; - final short leftLength = (short) left.length(); - final short rightLength = (short) right.length(); final boolean debugThisFun = false; XTextCursor full = null; @@ -286,6 +280,7 @@ public XTextCursor nrGetFillCursor(XTextDocument doc) beta.goRight((short) (fullText.length() - 2), true); LOGGER.debug("nrGetFillCursor: beta(1) covers '{}'", beta.getString()); + final short rightLength = (short) right.length(); if (fullText.startsWith(left) && fullText.endsWith(right)) { beta.setString(""); } else { @@ -334,6 +329,7 @@ public XTextCursor nrGetFillCursor(XTextDocument doc) beta.setString(""); // should be OK now. if (debugThisFun) { + final short leftLength = (short) left.length(); alpha.goRight(leftLength, true); LOGGER.debug("nrGetFillCursor: alpha(7) covers '{}', should be '{}'", alpha.getString(), left); omega.goLeft(rightLength, true); @@ -351,8 +347,6 @@ public XTextCursor nrGetFillCursor(XTextDocument doc) public static void checkFillCursor(XTextCursor cursor) { final String left = REFERENCE_MARK_LEFT_BRACKET; final String right = REFERENCE_MARK_RIGHT_BRACKET; - final short leftLength = (short) left.length(); - final short rightLength = (short) right.length(); XTextCursor alpha = cursor.getText().createTextCursorByRange(cursor); alpha.collapseToStart(); @@ -360,6 +354,7 @@ public static void checkFillCursor(XTextCursor cursor) { XTextCursor omega = cursor.getText().createTextCursorByRange(cursor); omega.collapseToEnd(); + final short leftLength = (short) left.length(); if (leftLength > 0) { alpha.goLeft(leftLength, true); if (!left.equals(alpha.getString())) { @@ -370,6 +365,7 @@ public static void checkFillCursor(XTextCursor cursor) { } } + final short rightLength = (short) right.length(); if (rightLength > 0) { omega.goRight(rightLength, true); if (!right.equals(omega.getString())) { @@ -391,8 +387,7 @@ public static void checkFillCursor(XTextCursor cursor) { public void nrCleanFillCursor(XTextDocument doc) throws NoDocumentException, - WrappedTargetException, - CreationException { + WrappedTargetException { // alwaysRemoveBrackets : full compatibility with JabRef 5.2: brackets are temporary, only // exist between nrGetFillCursor and nrCleanFillCursor. diff --git a/src/main/java/org/jabref/logic/openoffice/style/OOProcessAuthorYearMarkers.java b/src/main/java/org/jabref/logic/openoffice/style/OOProcessAuthorYearMarkers.java index 306c0111a22..896e747913f 100644 --- a/src/main/java/org/jabref/logic/openoffice/style/OOProcessAuthorYearMarkers.java +++ b/src/main/java/org/jabref/logic/openoffice/style/OOProcessAuthorYearMarkers.java @@ -64,9 +64,6 @@ private static void createUniqueLetters(CitedKeys sortedCitedKeys, CitationGroup String citationKey = citedKey.citationKey; List clashingKeys = normCitMarkerToClachingKeys.putIfAbsent(normCitMarker, new ArrayList<>(1)); - if (clashingKeys == null) { - clashingKeys = normCitMarkerToClachingKeys.get(normCitMarker); - } if (!clashingKeys.contains(citationKey)) { // First appearance of citationKey, add to list. clashingKeys.add(citationKey); diff --git a/src/main/java/org/jabref/model/openoffice/backend/NamedRange.java b/src/main/java/org/jabref/model/openoffice/backend/NamedRange.java index d8e1832b6ef..aa09f36edef 100644 --- a/src/main/java/org/jabref/model/openoffice/backend/NamedRange.java +++ b/src/main/java/org/jabref/model/openoffice/backend/NamedRange.java @@ -5,7 +5,6 @@ import org.jabref.model.openoffice.uno.CreationException; import org.jabref.model.openoffice.uno.NoDocumentException; -import com.sun.star.container.NoSuchElementException; import com.sun.star.lang.WrappedTargetException; import com.sun.star.text.XTextCursor; import com.sun.star.text.XTextDocument; @@ -13,12 +12,12 @@ public interface NamedRange { - public String nrGetRangeName(); + String nrGetRangeName(); /** * @return Optional.empty if the mark is missing from the document. */ - public Optional nrGetMarkRange(XTextDocument doc) + Optional nrGetMarkRange(XTextDocument doc) throws NoDocumentException, WrappedTargetException; @@ -27,7 +26,7 @@ public Optional nrGetMarkRange(XTextDocument doc) * Cursor for the reference marks as is, not prepared for filling, but does not need * nrCleanFillCursor either. */ - public Optional nrGetRawCursor(XTextDocument doc) + Optional nrGetRawCursor(XTextDocument doc) throws NoDocumentException, WrappedTargetException; @@ -37,7 +36,7 @@ public Optional nrGetRawCursor(XTextDocument doc) * * Must be followed by nrCleanFillCursor() */ - public XTextCursor nrGetFillCursor(XTextDocument doc) + XTextCursor nrGetFillCursor(XTextDocument doc) throws NoDocumentException, WrappedTargetException, @@ -45,21 +44,19 @@ public XTextCursor nrGetFillCursor(XTextDocument doc) /** * Remove brackets, but if the result would become empty, leave them; if the result would be a - * single characer, leave the left bracket. + * single character, leave the left bracket. * */ - public void nrCleanFillCursor(XTextDocument doc) + void nrCleanFillCursor(XTextDocument doc) throws NoDocumentException, - WrappedTargetException, - CreationException; + WrappedTargetException; /** * Note: create is in NamedRangeManager */ - public void nrRemoveFromDocument(XTextDocument doc) + void nrRemoveFromDocument(XTextDocument doc) throws WrappedTargetException, - NoDocumentException, - NoSuchElementException; + NoDocumentException; } diff --git a/src/main/java/org/jabref/model/openoffice/backend/NamedRangeManager.java b/src/main/java/org/jabref/model/openoffice/backend/NamedRangeManager.java index f53fcc72527..536871b5c34 100644 --- a/src/main/java/org/jabref/model/openoffice/backend/NamedRangeManager.java +++ b/src/main/java/org/jabref/model/openoffice/backend/NamedRangeManager.java @@ -12,19 +12,19 @@ public interface NamedRangeManager { - public NamedRange nrmCreate(XTextDocument doc, - String markName, - XTextCursor position, - boolean insertSpaceAfter, - boolean withoutBrackets) + NamedRange nrmCreate(XTextDocument doc, + String markName, + XTextCursor position, + boolean insertSpaceAfter, + boolean withoutBrackets) throws CreationException; - public List nrmGetUsedNames(XTextDocument doc) + List nrmGetUsedNames(XTextDocument doc) throws NoDocumentException; - public Optional nrmGetFromDocument(XTextDocument doc, String markName) + Optional nrmGetFromDocument(XTextDocument doc, String markName) throws NoDocumentException, WrappedTargetException; From 952f08ba320f3df26c96846cdce07dfaba76f21f Mon Sep 17 00:00:00 2001 From: Antal K Date: Fri, 20 Aug 2021 14:19:54 +0200 Subject: [PATCH 48/51] sync to improve-reversibility-rebased-03 cb132566f32cba17af24b41d037446a96a90dc16 commit cb132566f32cba17af24b41d037446a96a90dc16 (HEAD -> improve-reversibility-rebased-03, origin/improve-reversibility-rebased-03) Author: Antal K Date: Fri Aug 20 11:39:39 2021 +0200 align dots --- .../logic/openoffice/action/EditInsert.java | 16 +- .../logic/openoffice/action/EditMerge.java | 52 +-- .../logic/openoffice/action/EditSeparate.java | 24 +- .../logic/openoffice/action/ExportCited.java | 8 +- .../openoffice/action/ManageCitations.java | 11 +- .../logic/openoffice/action/Update.java | 24 +- .../logic/openoffice/backend/Backend52.java | 340 +++++++++--------- .../logic/openoffice/backend/Codec52.java | 56 ++- .../NamedRangeManagerReferenceMark.java | 14 +- .../backend/NamedRangeReferenceMark.java | 79 ++-- .../logic/openoffice/frontend/OOFrontend.java | 131 +++---- .../frontend/UpdateBibliography.java | 18 +- .../frontend/UpdateCitationMarkers.java | 55 ++- .../style/OOFormatBibliography.java | 12 +- .../style/OOProcessAuthorYearMarkers.java | 15 +- .../style/OOProcessCitationKeyMarkers.java | 6 +- .../style/OOProcessNumericMarkers.java | 6 +- .../model/openoffice/backend/NamedRange.java | 25 +- .../openoffice/backend/NamedRangeManager.java | 14 +- .../model/openoffice/ootext/OOTextIntoOO.java | 248 ++++++------- .../model/openoffice/style/CitationGroup.java | 6 +- .../openoffice/style/CitationGroups.java | 54 +-- .../model/openoffice/style/CitationType.java | 4 +- 23 files changed, 571 insertions(+), 647 deletions(-) diff --git a/src/main/java/org/jabref/logic/openoffice/action/EditInsert.java b/src/main/java/org/jabref/logic/openoffice/action/EditInsert.java index 1345f1f2a99..81491bd8590 100644 --- a/src/main/java/org/jabref/logic/openoffice/action/EditInsert.java +++ b/src/main/java/org/jabref/logic/openoffice/action/EditInsert.java @@ -5,7 +5,6 @@ import java.util.List; import java.util.Optional; -import org.jabref.logic.JabRefException; import org.jabref.logic.openoffice.frontend.OOFrontend; import org.jabref.logic.openoffice.frontend.UpdateCitationMarkers; import org.jabref.logic.openoffice.style.OOBibStyle; @@ -24,19 +23,16 @@ import com.sun.star.beans.IllegalTypeException; import com.sun.star.beans.NotRemoveableException; -import com.sun.star.beans.PropertyExistException; import com.sun.star.beans.PropertyVetoException; -import com.sun.star.beans.UnknownPropertyException; -import com.sun.star.container.NoSuchElementException; import com.sun.star.lang.WrappedTargetException; import com.sun.star.text.XTextCursor; import com.sun.star.text.XTextDocument; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; public class EditInsert { - private static final Logger LOGGER = LoggerFactory.getLogger(EditInsert.class); + private EditInsert() { + /**/ + } /** * In insertEntry we receive BibEntry values from the GUI. @@ -68,16 +64,12 @@ public static void insertCitationGroup(XTextDocument doc, CitationType citationType, String pageInfo) throws - UnknownPropertyException, NoDocumentException, NotRemoveableException, WrappedTargetException, PropertyVetoException, - PropertyExistException, - NoSuchElementException, CreationException, - IllegalTypeException, - JabRefException { + IllegalTypeException { List citationKeys = OOListUtil.map(entries, EditInsert::insertEntryGetCitationKey); diff --git a/src/main/java/org/jabref/logic/openoffice/action/EditMerge.java b/src/main/java/org/jabref/logic/openoffice/action/EditMerge.java index 4cd225eb1a8..43bdcd59e2d 100644 --- a/src/main/java/org/jabref/logic/openoffice/action/EditMerge.java +++ b/src/main/java/org/jabref/logic/openoffice/action/EditMerge.java @@ -6,7 +6,6 @@ import java.util.Optional; import java.util.stream.Collectors; -import org.jabref.logic.JabRefException; import org.jabref.logic.openoffice.frontend.OOFrontend; import org.jabref.logic.openoffice.frontend.UpdateCitationMarkers; import org.jabref.logic.openoffice.style.OOBibStyle; @@ -22,15 +21,11 @@ import com.sun.star.beans.IllegalTypeException; import com.sun.star.beans.NotRemoveableException; -import com.sun.star.beans.PropertyExistException; import com.sun.star.beans.PropertyVetoException; -import com.sun.star.beans.UnknownPropertyException; -import com.sun.star.container.NoSuchElementException; import com.sun.star.lang.WrappedTargetException; import com.sun.star.text.XTextCursor; import com.sun.star.text.XTextDocument; import com.sun.star.text.XTextRange; -import com.sun.star.util.InvalidStateException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -50,14 +45,9 @@ public static boolean mergeCitationGroups(XTextDocument doc, OOFrontend fr, OOBi CreationException, IllegalArgumentException, IllegalTypeException, - InvalidStateException, - JabRefException, NoDocumentException, - NoSuchElementException, NotRemoveableException, - PropertyExistException, PropertyVetoException, - UnknownPropertyException, WrappedTargetException { boolean madeModifications = false; @@ -72,7 +62,7 @@ public static boolean mergeCitationGroups(XTextDocument doc, OOFrontend fr, OOBi List cgs = joinableGroupData.group; List newCitations = (cgs.stream() - .flatMap(cg -> cg.citationsInStorageOrder.stream()) + .flatMap(group -> group.citationsInStorageOrder.stream()) .collect(Collectors.toList())); CitationType citationType = cgs.get(0).citationType; @@ -155,15 +145,15 @@ void reset() { } /** - * Decide if cg could be added to state.currentGroup + * Decide if group could be added to state.currentGroup * - * @param cg The CitationGroup to test - * @param currentRange The XTextRange corresponding to cg. + * @param group The CitationGroup to test + * @param currentRange The XTextRange corresponding to group. * * @return false if cannot add, true if can. If returned true, then state.cursorBetween and * state.currentGroupCursor are expanded to end at the start of currentRange. */ - private static boolean checkAddToGroup(ScanState state, CitationGroup cg, XTextRange currentRange) { + private static boolean checkAddToGroup(ScanState state, CitationGroup group, XTextRange currentRange) { if (state.currentGroup.isEmpty()) { return false; @@ -175,14 +165,14 @@ private static boolean checkAddToGroup(ScanState state, CitationGroup cg, XTextR Objects.requireNonNull(state.prevRange); // Only combine (Author 2000) type citations - if (cg.citationType != CitationType.AUTHORYEAR_PAR) { + if (group.citationType != CitationType.AUTHORYEAR_PAR) { return false; } if (state.prev != null) { // Even if we combine AUTHORYEAR_INTEXT citations, we would not mix them with AUTHORYEAR_PAR - if (cg.citationType != state.prev.citationType) { + if (group.citationType != state.prev.citationType) { return false; } @@ -253,20 +243,16 @@ private static boolean checkAddToGroup(ScanState state, CitationGroup cg, XTextR } } - if (!couldExpand) { - return false; - } - - return true; + return couldExpand; } /** - * Add cg to state.currentGroup + * Add group to state.currentGroup * Set state.cursorBetween to start at currentRange.getEnd() * Expand state.currentGroupCursor to also cover currentRange - * Set state.prev to cg, state.prevRange to currentRange + * Set state.prev to group, state.prevRange to currentRange */ - private static void addToCurrentGroup(ScanState state, CitationGroup cg, XTextRange currentRange) { + private static void addToCurrentGroup(ScanState state, CitationGroup group, XTextRange currentRange) { final boolean isNewGroup = state.currentGroup.isEmpty(); if (!isNewGroup) { Objects.requireNonNull(state.currentGroupCursor); @@ -276,7 +262,7 @@ private static void addToCurrentGroup(ScanState state, CitationGroup cg, XTextRa } // Add the current entry to a group. - state.currentGroup.add(cg); + state.currentGroup.add(group); // Set up cursorBetween to start at currentRange.getEnd() XTextRange rangeEnd = currentRange.getEnd(); @@ -297,7 +283,7 @@ private static void addToCurrentGroup(ScanState state, CitationGroup cg, XTextRa } /* Store data about last entry in currentGroup */ - state.prev = cg; + state.prev = group; state.prevRange = currentRange; } @@ -317,22 +303,22 @@ private static List scan(XTextDocument doc, OOFrontend fr) ScanState state = new ScanState(); - for (CitationGroup cg : cgs) { + for (CitationGroup group : cgs) { - XTextRange currentRange = (fr.getMarkRange(doc, cg) + XTextRange currentRange = (fr.getMarkRange(doc, group) .orElseThrow(IllegalStateException::new)); /* - * Decide if we add cg to the group. False when the group is empty. + * Decide if we add group to the group. False when the group is empty. */ - boolean addToGroup = checkAddToGroup(state, cg, currentRange); + boolean addToGroup = checkAddToGroup(state, group, currentRange); /* * Even if we do not add it to an existing group, we might use it to start a new group. * * Can it start a new group? */ - boolean canStartGroup = (cg.citationType == CitationType.AUTHORYEAR_PAR); + boolean canStartGroup = (group.citationType == CitationType.AUTHORYEAR_PAR); if (!addToGroup) { // close currentGroup @@ -344,7 +330,7 @@ private static List scan(XTextDocument doc, OOFrontend fr) } if (addToGroup || canStartGroup) { - addToCurrentGroup(state, cg, currentRange); + addToCurrentGroup(state, group, currentRange); } } diff --git a/src/main/java/org/jabref/logic/openoffice/action/EditSeparate.java b/src/main/java/org/jabref/logic/openoffice/action/EditSeparate.java index 0293159d34f..fba50a93c6e 100644 --- a/src/main/java/org/jabref/logic/openoffice/action/EditSeparate.java +++ b/src/main/java/org/jabref/logic/openoffice/action/EditSeparate.java @@ -2,7 +2,6 @@ import java.util.List; -import org.jabref.logic.JabRefException; import org.jabref.logic.openoffice.frontend.OOFrontend; import org.jabref.logic.openoffice.frontend.UpdateCitationMarkers; import org.jabref.logic.openoffice.style.OOBibStyle; @@ -17,18 +16,18 @@ import com.sun.star.beans.IllegalTypeException; import com.sun.star.beans.NotRemoveableException; -import com.sun.star.beans.PropertyExistException; import com.sun.star.beans.PropertyVetoException; -import com.sun.star.beans.UnknownPropertyException; -import com.sun.star.container.NoSuchElementException; import com.sun.star.lang.WrappedTargetException; import com.sun.star.text.XTextCursor; import com.sun.star.text.XTextDocument; import com.sun.star.text.XTextRange; -import com.sun.star.util.InvalidStateException; public class EditSeparate { + private EditSeparate() { + /**/ + } + public static boolean separateCitations(XTextDocument doc, OOFrontend fr, List databases, @@ -36,14 +35,9 @@ public static boolean separateCitations(XTextDocument doc, throws CreationException, IllegalTypeException, - InvalidStateException, - JabRefException, NoDocumentException, - NoSuchElementException, NotRemoveableException, - PropertyExistException, PropertyVetoException, - UnknownPropertyException, WrappedTargetException, com.sun.star.lang.IllegalArgumentException { @@ -61,19 +55,19 @@ public static boolean separateCitations(XTextDocument doc, try { UnoScreenRefresh.lockControllers(doc); - for (CitationGroup cg : cgs) { + for (CitationGroup group : cgs) { XTextRange range1 = (fr - .getMarkRange(doc, cg) + .getMarkRange(doc, group) .orElseThrow(IllegalStateException::new)); XTextCursor textCursor = range1.getText().createTextCursorByRange(range1); - List cits = cg.citationsInStorageOrder; + List cits = group.citationsInStorageOrder; if (cits.size() <= 1) { continue; } - fr.removeCitationGroup(cg, doc); + fr.removeCitationGroup(group, doc); // Now we own the content of cits // Create a citation group for each citation. @@ -86,7 +80,7 @@ public static boolean separateCitations(XTextDocument doc, doc, List.of(cit.citationKey), List.of(cit.getPageInfo()), - cg.citationType, + group.citationType, OOText.fromString(cit.citationKey), textCursor, style, diff --git a/src/main/java/org/jabref/logic/openoffice/action/ExportCited.java b/src/main/java/org/jabref/logic/openoffice/action/ExportCited.java index ef7985d666a..26ed654de21 100644 --- a/src/main/java/org/jabref/logic/openoffice/action/ExportCited.java +++ b/src/main/java/org/jabref/logic/openoffice/action/ExportCited.java @@ -13,13 +13,15 @@ import org.jabref.model.openoffice.style.CitedKeys; import org.jabref.model.openoffice.uno.NoDocumentException; -import com.sun.star.beans.UnknownPropertyException; -import com.sun.star.container.NoSuchElementException; import com.sun.star.lang.WrappedTargetException; import com.sun.star.text.XTextDocument; public class ExportCited { + private ExportCited() { + /**/ + } + public static class GenerateDatabaseResult { /** * null: not done; isEmpty: no unresolved @@ -47,8 +49,6 @@ public static class GenerateDatabaseResult { public static GenerateDatabaseResult generateDatabase(XTextDocument doc, List databases) throws NoDocumentException, - NoSuchElementException, - UnknownPropertyException, WrappedTargetException { OOFrontend fr = new OOFrontend(doc); diff --git a/src/main/java/org/jabref/logic/openoffice/action/ManageCitations.java b/src/main/java/org/jabref/logic/openoffice/action/ManageCitations.java index 4046cbbe11b..9782261f612 100644 --- a/src/main/java/org/jabref/logic/openoffice/action/ManageCitations.java +++ b/src/main/java/org/jabref/logic/openoffice/action/ManageCitations.java @@ -7,19 +7,19 @@ import org.jabref.model.openoffice.uno.NoDocumentException; import com.sun.star.beans.IllegalTypeException; -import com.sun.star.beans.NotRemoveableException; -import com.sun.star.beans.PropertyExistException; import com.sun.star.beans.PropertyVetoException; -import com.sun.star.beans.UnknownPropertyException; import com.sun.star.lang.WrappedTargetException; import com.sun.star.text.XTextDocument; public class ManageCitations { + private ManageCitations() { + /**/ + } + public static List getCitationEntries(XTextDocument doc) throws NoDocumentException, - UnknownPropertyException, WrappedTargetException { OOFrontend fr = new OOFrontend(doc); return fr.getCitationEntries(doc); @@ -28,9 +28,6 @@ public static List getCitationEntries(XTextDocument doc) public static void applyCitationEntries(XTextDocument doc, List citationEntries) throws NoDocumentException, - UnknownPropertyException, - NotRemoveableException, - PropertyExistException, PropertyVetoException, IllegalTypeException, WrappedTargetException, diff --git a/src/main/java/org/jabref/logic/openoffice/action/Update.java b/src/main/java/org/jabref/logic/openoffice/action/Update.java index 675afb2a79f..49f79d6d2e7 100644 --- a/src/main/java/org/jabref/logic/openoffice/action/Update.java +++ b/src/main/java/org/jabref/logic/openoffice/action/Update.java @@ -2,7 +2,6 @@ import java.util.List; -import org.jabref.logic.JabRefException; import org.jabref.logic.openoffice.frontend.OOFrontend; import org.jabref.logic.openoffice.frontend.UpdateBibliography; import org.jabref.logic.openoffice.frontend.UpdateCitationMarkers; @@ -14,9 +13,6 @@ import org.jabref.model.openoffice.uno.NoDocumentException; import org.jabref.model.openoffice.uno.UnoScreenRefresh; -import com.sun.star.beans.PropertyVetoException; -import com.sun.star.beans.UnknownPropertyException; -import com.sun.star.container.NoSuchElementException; import com.sun.star.lang.WrappedTargetException; import com.sun.star.text.XTextDocument; @@ -25,6 +21,10 @@ */ public class Update { + private Update() { + /**/ + } + /** * @return the list of unresolved citation keys */ @@ -37,11 +37,7 @@ private static List updateDocument(XTextDocument doc, boolean alwaysAddCitedOnPages) throws CreationException, - JabRefException, NoDocumentException, - NoSuchElementException, - PropertyVetoException, - UnknownPropertyException, WrappedTargetException, com.sun.star.lang.IllegalArgumentException { @@ -64,8 +60,8 @@ private static List updateDocument(XTextDocument doc, style, alwaysAddCitedOnPages); } - List result = fr.citationGroups.getUnresolvedKeys(); - return result; + + return fr.citationGroups.getUnresolvedKeys(); } finally { if (useLockControllers && UnoScreenRefresh.hasControllersLocked(doc)) { UnoScreenRefresh.unlockControllers(doc); @@ -103,11 +99,7 @@ public static List synchronizeDocument(XTextDocument doc, SyncOptions syncOptions) throws CreationException, - JabRefException, NoDocumentException, - NoSuchElementException, - PropertyVetoException, - UnknownPropertyException, WrappedTargetException, com.sun.star.lang.IllegalArgumentException { @@ -129,11 +121,7 @@ public static List resyncDocument(XTextDocument doc, SyncOptions syncOptions) throws CreationException, - JabRefException, NoDocumentException, - NoSuchElementException, - PropertyVetoException, - UnknownPropertyException, WrappedTargetException, com.sun.star.lang.IllegalArgumentException { diff --git a/src/main/java/org/jabref/logic/openoffice/backend/Backend52.java b/src/main/java/org/jabref/logic/openoffice/backend/Backend52.java index 878571090f0..0016e5e4510 100644 --- a/src/main/java/org/jabref/logic/openoffice/backend/Backend52.java +++ b/src/main/java/org/jabref/logic/openoffice/backend/Backend52.java @@ -36,6 +36,11 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +/** + * Backend52, Codec52 and OODataModel.JabRef52 refer to the mode of storage, encoding and + * what-is-stored in the document under JabRef version 5.2. These basically did not change up to + * JabRef 5.4. + */ public class Backend52 { private static final Logger LOGGER = LoggerFactory.getLogger(Backend52.class); public final OODataModel dataModel; @@ -59,7 +64,7 @@ public Backend52() { public List getJabRefReferenceMarkNames(XTextDocument doc) throws NoDocumentException { - List allNames = this.citationStorageManager.nrmGetUsedNames(doc); + List allNames = this.citationStorageManager.getUsedNames(doc); return Codec52.filterIsJabRefReferenceMarkName(allNames); } @@ -73,8 +78,7 @@ public List getJabRefReferenceMarkNames(XTextDocument doc) private List findUnusedJabrefPropertyNames(XTextDocument doc, List citationGroupNames) { - // Collect unused jabrefPropertyNames - Set citationGroupNamesSet = citationGroupNames.stream().collect(Collectors.toSet()); + Set citationGroupNamesSet = new HashSet<>(citationGroupNames); List pageInfoThrash = new ArrayList<>(); List jabrefPropertyNames = @@ -99,13 +103,13 @@ public Optional healthReport(XTextDocument doc) List pageInfoThrash = this.findUnusedJabrefPropertyNames(doc, this.getJabRefReferenceMarkNames(doc)); if (pageInfoThrash.isEmpty()) { - return Optional.empty(); // "Backend52: found no unused pageInfo data"; + return Optional.empty(); } - String msg = - "Backend52: found unused pageInfo data, with names listed below.\n" - + "In LibreOffice you may remove these in [File]/[Properties]/[Custom Properties]\n"; - msg += "" + String.join("\n", pageInfoThrash) + ""; - return Optional.of(msg); + + StringBuilder msg = new StringBuilder("Backend52: found unused pageInfo data, with names listed below.\n"); + msg.append("In LibreOffice you may remove these in [File]/[Properties]/[Custom Properties]\n"); + msg.append(String.join("\n", pageInfoThrash)); + return Optional.of(msg.toString()); } private static void setPageInfoInDataInitial(List citations, Optional pageInfo) { @@ -115,8 +119,8 @@ private static void setPageInfoInDataInitial(List citations, Optional< } } - private static Optional getPageInfoFromData(CitationGroup cg) { - List citations = cg.getCitationsInLocalOrder(); + private static Optional getPageInfoFromData(CitationGroup group) { + List citations = group.getCitationsInLocalOrder(); if (citations.isEmpty()) { return Optional.empty(); } @@ -124,54 +128,52 @@ private static Optional getPageInfoFromData(CitationGroup cg) { } /** - * We have circular dependency here: backend uses - * class from ... + * @param markName Reference mark name */ - public CitationGroup readCitationGroupFromDocumentOrThrow(XTextDocument doc, String refMarkName) + public CitationGroup readCitationGroupFromDocumentOrThrow(XTextDocument doc, String markName) throws WrappedTargetException, NoDocumentException { - Optional optionalParsed = Codec52.parseMarkName(refMarkName); - if (optionalParsed.isEmpty()) { - throw new IllegalArgumentException("readCitationGroupFromDocumentOrThrow:" - + " found unparsable referenceMarkName"); - } - Codec52.ParsedMarkName parsed = optionalParsed.get(); + Codec52.ParsedMarkName parsed = Codec52.parseMarkName(markName).orElseThrow(IllegalArgumentException::new); + List citations = (parsed.citationKeys.stream() .map(Citation::new) .collect(Collectors.toList())); - Optional pageInfo = (UnoUserDefinedProperty.getStringValue(doc, refMarkName) + Optional pageInfo = (UnoUserDefinedProperty.getStringValue(doc, markName) .map(OOText::fromString)); pageInfo = PageInfo.normalizePageInfo(pageInfo); setPageInfoInDataInitial(citations, pageInfo); - Optional namedRange = citationStorageManager.nrmGetFromDocument(doc, refMarkName); - - if (namedRange.isEmpty()) { - throw new IllegalArgumentException("readCitationGroupFromDocumentOrThrow:" - + " referenceMarkName is not in the document"); - } - - CitationGroupId cgid = new CitationGroupId(refMarkName); - CitationGroup cg = new CitationGroup(OODataModel.JabRef52, - cgid, - parsed.citationType, - citations, - Optional.of(refMarkName)); - this.cgidToNamedRange.put(cgid, namedRange.get()); - return cg; + NamedRange namedRange = (citationStorageManager.getNamedRangeFromDocument(doc, markName) + .orElseThrow(IllegalArgumentException::new)); + + CitationGroupId groupId = new CitationGroupId(markName); + CitationGroup group = new CitationGroup(OODataModel.JabRef52, + groupId, + parsed.citationType, + citations, + Optional.of(markName)); + this.cgidToNamedRange.put(groupId, namedRange); + return group; } /** - * Create a reference mark with the given name, at the end of position. + * Create a reference mark at the end of {@code position} in the document. * * On return {@code position} is collapsed, and is after the inserted space, or at the end of * the reference mark. * + * @param citationKeys Keys to be cited. + * + * @param pageInfos An optional pageInfo for each citation key. + * Backend52 only uses and stores the last pageInfo, + * all others should be Optional.empty() + * * @param position Collapsed to its end. + * * @param insertSpaceAfter We insert a space after the mark, that carries on format of * characters from the original position. */ @@ -191,42 +193,46 @@ public CitationGroup createCitationGroup(XTextDocument doc, Objects.requireNonNull(pageInfos); if (pageInfos.size() != citationKeys.size()) { - throw new IllegalArgumentException("pageInfos.size != citationKeys.size"); + throw new IllegalArgumentException(); } - // Get a new refMarkName - Set usedNames = new HashSet<>(this.citationStorageManager.nrmGetUsedNames(doc)); - String xkey = (citationKeys.stream().collect(Collectors.joining(","))); - String refMarkName = Codec52.getUniqueMarkName(usedNames, xkey, citationType); + /* + * Backend52 uses reference marks to (1) mark the location of the citation in the text and (2) to encode + * the citation keys and citation type in the name of the reference mark. The name of the reference mark + * has to be unique in the document. + */ + String markName = Codec52.getUniqueMarkName(new HashSet<>(citationStorageManager.getUsedNames(doc)), + citationKeys, + citationType); - CitationGroupId cgid = new CitationGroupId(refMarkName); + CitationGroupId groupId = new CitationGroupId(markName); - final int nCitations = citationKeys.size(); - final int last = nCitations - 1; + final int numberOfCitations = citationKeys.size(); + final int last = numberOfCitations - 1; // Build citations, add pageInfo to each citation - List citations = new ArrayList<>(nCitations); - for (int i = 0; i < nCitations; i++) { + List citations = new ArrayList<>(numberOfCitations); + for (int i = 0; i < numberOfCitations; i++) { Citation cit = new Citation(citationKeys.get(i)); citations.add(cit); Optional pageInfo = PageInfo.normalizePageInfo(pageInfos.get(i)); switch (dataModel) { - case JabRef52: - if (i == last) { - cit.setPageInfo(pageInfo); - } else { - if (pageInfo.isPresent()) { - LOGGER.warn("dataModel JabRef52" - + " only supports pageInfo for the last citation of a group"); + case JabRef52: + if (i == last) { + cit.setPageInfo(pageInfo); + } else { + if (pageInfo.isPresent()) { + LOGGER.warn("dataModel JabRef52" + + " only supports pageInfo for the last citation of a group"); + } } - } - break; - case JabRef60: - cit.setPageInfo(pageInfo); - break; - default: - throw new IllegalStateException("Unhandled dataModel in Backend52.createCitationGroup"); + break; + case JabRef60: + cit.setPageInfo(pageInfo); + break; + default: + throw new IllegalStateException("Unhandled dataModel in Backend52.createCitationGroup"); } } @@ -234,29 +240,33 @@ public CitationGroup createCitationGroup(XTextDocument doc, * Apply to document */ boolean withoutBrackets = (citationType == CitationType.INVISIBLE_CIT); - NamedRange namedRange = - this.citationStorageManager.nrmCreate(doc, refMarkName, position, insertSpaceAfter, - withoutBrackets); + NamedRange namedRange = this.citationStorageManager.createNamedRange(doc, + markName, + position, + insertSpaceAfter, + withoutBrackets); switch (dataModel) { - case JabRef52: - Optional pageInfo = PageInfo.normalizePageInfo(pageInfos.get(last)); - - if (pageInfo.isPresent()) { - String pageInfoString = OOText.toString(pageInfo.get()); - UnoUserDefinedProperty.setStringProperty(doc, refMarkName, pageInfoString); - } else { - // do not inherit from trash - UnoUserDefinedProperty.removeIfExists(doc, refMarkName); - } - CitationGroup cg = new CitationGroup(OODataModel.JabRef52, - cgid, - citationType, citations, - Optional.of(refMarkName)); - this.cgidToNamedRange.put(cgid, namedRange); - return cg; - default: - throw new IllegalStateException("Backend52 requires JabRef52 dataModel"); + case JabRef52: + Optional pageInfo = PageInfo.normalizePageInfo(pageInfos.get(last)); + + if (pageInfo.isPresent()) { + String pageInfoString = OOText.toString(pageInfo.get()); + UnoUserDefinedProperty.setStringProperty(doc, markName, pageInfoString); + } else { + // do not inherit from trash + UnoUserDefinedProperty.removeIfExists(doc, markName); + } + CitationGroup group = new CitationGroup(OODataModel.JabRef52, + groupId, + citationType, citations, + Optional.of(markName)); + this.cgidToNamedRange.put(groupId, namedRange); + return group; + case JabRef60: + throw new IllegalStateException("createCitationGroup for JabRef60 is not implemented yet"); + default: + throw new IllegalStateException("Unhandled dataModel in Backend52.createCitationGroup"); } } @@ -269,33 +279,33 @@ public CitationGroup createCitationGroup(XTextDocument doc, public static List> combinePageInfosCommon(OODataModel dataModel, List joinableGroup) { switch (dataModel) { - case JabRef52: - // collect to cgPageInfos - List> cgPageInfos = OOListUtil.map(joinableGroup, - Backend52::getPageInfoFromData); - - // Try to do something of the cgPageInfos. - String cgPageInfo = (cgPageInfos.stream() - .filter(pi -> pi.isPresent()) - .map(pi -> OOText.toString(pi.get())) - .distinct() - .collect(Collectors.joining("; "))); - - int nCitations = (joinableGroup.stream() - .map(CitationGroup::numberOfCitations) - .mapToInt(Integer::intValue).sum()); - if ("".equals(cgPageInfo)) { - cgPageInfo = null; - } - return OODataModel.fakePageInfos(cgPageInfo, nCitations); - - case JabRef60: - return (joinableGroup.stream() - .flatMap(cg -> (cg.citationsInStorageOrder.stream() - .map(Citation::getPageInfo))) - .collect(Collectors.toList())); - default: - throw new IllegalArgumentException("unhandled dataModel here"); + case JabRef52: + // collect to pageInfos + List> pageInfos = OOListUtil.map(joinableGroup, + Backend52::getPageInfoFromData); + + // Try to do something of the pageInfos. + String singlePageInfo = (pageInfos.stream() + .filter(Optional::isPresent) + .map(pi -> OOText.toString(pi.get())) + .distinct() + .collect(Collectors.joining("; "))); + + int totalCitations = (joinableGroup.stream() + .map(CitationGroup::numberOfCitations) + .mapToInt(Integer::intValue).sum()); + if ("".equals(singlePageInfo)) { + singlePageInfo = null; + } + return OODataModel.fakePageInfos(singlePageInfo, totalCitations); + + case JabRef60: + return (joinableGroup.stream() + .flatMap(group -> (group.citationsInStorageOrder.stream() + .map(Citation::getPageInfo))) + .collect(Collectors.toList())); + default: + throw new IllegalArgumentException("unhandled dataModel here"); } } @@ -306,70 +316,70 @@ public List> combinePageInfos(List joinableGroup return combinePageInfosCommon(this.dataModel, joinableGroup); } - private NamedRange getNamedRangeOrThrow(CitationGroup cg) { - NamedRange namedRange = this.cgidToNamedRange.get(cg.cgid); + private NamedRange getNamedRangeOrThrow(CitationGroup group) { + NamedRange namedRange = this.cgidToNamedRange.get(group.groupId); if (namedRange == null) { throw new IllegalStateException("getNamedRange: could not lookup namedRange"); } return namedRange; } - public void removeCitationGroup(CitationGroup cg, XTextDocument doc) + public void removeCitationGroup(CitationGroup group, XTextDocument doc) throws WrappedTargetException, NoDocumentException, NotRemoveableException { - NamedRange namedRange = getNamedRangeOrThrow(cg); - String refMarkName = namedRange.nrGetRangeName(); - namedRange.nrRemoveFromDocument(doc); + NamedRange namedRange = getNamedRangeOrThrow(group); + String refMarkName = namedRange.getRangeName(); + namedRange.removeFromDocument(doc); UnoUserDefinedProperty.removeIfExists(doc, refMarkName); - this.cgidToNamedRange.remove(cg.cgid); + this.cgidToNamedRange.remove(group.groupId); } /** * @return Optional.empty if the reference mark is missing. */ - public Optional getMarkRange(CitationGroup cg, XTextDocument doc) + public Optional getMarkRange(CitationGroup group, XTextDocument doc) throws NoDocumentException, WrappedTargetException { - NamedRange namedRange = getNamedRangeOrThrow(cg); - return namedRange.nrGetMarkRange(doc); + NamedRange namedRange = getNamedRangeOrThrow(group); + return namedRange.getMarkRange(doc); } /** * Cursor for the reference marks as is: not prepared for filling, but does not need * cleanFillCursorForCitationGroup either. */ - public Optional getRawCursorForCitationGroup(CitationGroup cg, XTextDocument doc) + public Optional getRawCursorForCitationGroup(CitationGroup group, XTextDocument doc) throws NoDocumentException, WrappedTargetException { - NamedRange namedRange = getNamedRangeOrThrow(cg); - return namedRange.nrGetRawCursor(doc); + NamedRange namedRange = getNamedRangeOrThrow(group); + return namedRange.getRawCursor(doc); } /** * Must be followed by call to cleanFillCursorForCitationGroup */ - public XTextCursor getFillCursorForCitationGroup(CitationGroup cg, XTextDocument doc) + public XTextCursor getFillCursorForCitationGroup(CitationGroup group, XTextDocument doc) throws NoDocumentException, WrappedTargetException, CreationException { - NamedRange namedRange = getNamedRangeOrThrow(cg); - return namedRange.nrGetFillCursor(doc); + NamedRange namedRange = getNamedRangeOrThrow(group); + return namedRange.getFillCursor(doc); } /** To be called after getFillCursorForCitationGroup */ - public void cleanFillCursorForCitationGroup(CitationGroup cg, XTextDocument doc) + public void cleanFillCursorForCitationGroup(CitationGroup group, XTextDocument doc) throws NoDocumentException, WrappedTargetException { - NamedRange namedRange = getNamedRangeOrThrow(cg); - namedRange.nrCleanFillCursor(doc); + NamedRange namedRange = getNamedRangeOrThrow(group); + namedRange.cleanFillCursor(doc); } public List getCitationEntries(XTextDocument doc, CitationGroups cgs) @@ -378,29 +388,29 @@ public List getCitationEntries(XTextDocument doc, CitationGroups NoDocumentException { switch (dataModel) { - case JabRef52: - // One context per CitationGroup: Backend52 (DataModel.JabRef52) - // For DataModel.JabRef60 (Backend60) we need one context per Citation - List citations = new ArrayList<>(cgs.numberOfCitationGroups()); - for (CitationGroup cg : cgs.getCitationGroupsUnordered()) { - String name = cg.cgid.citationGroupIdAsString(); - XTextCursor cursor = (this - .getRawCursorForCitationGroup(cg, doc) - .orElseThrow(IllegalStateException::new)); - String context = GetContext.getCursorStringWithContext(cursor, 30, 30, true); - Optional pageInfo = (cg.numberOfCitations() > 0 - ? (getPageInfoFromData(cg) - .map(e -> OOText.toString(e))) - : Optional.empty()); - CitationEntry entry = new CitationEntry(name, context, pageInfo); - citations.add(entry); - } - return citations; - case JabRef60: - // xx - throw new IllegalStateException("getCitationEntries for JabRef60 is not implemented yet"); - default: - throw new IllegalStateException("getCitationEntries: unhandled dataModel "); + case JabRef52: + // One context per CitationGroup: Backend52 (DataModel.JabRef52) + // For DataModel.JabRef60 (Backend60) we need one context per Citation + List citations = new ArrayList<>(cgs.numberOfCitationGroups()); + for (CitationGroup group : cgs.getCitationGroupsUnordered()) { + String name = group.groupId.citationGroupIdAsString(); + XTextCursor cursor = (this + .getRawCursorForCitationGroup(group, doc) + .orElseThrow(IllegalStateException::new)); + String context = GetContext.getCursorStringWithContext(cursor, 30, 30, true); + Optional pageInfo = (group.numberOfCitations() > 0 + ? (getPageInfoFromData(group) + .map(e -> OOText.toString(e))) + : Optional.empty()); + CitationEntry entry = new CitationEntry(name, context, pageInfo); + citations.add(entry); + } + return citations; + case JabRef60: + // xx + throw new IllegalStateException("getCitationEntries for JabRef60 is not implemented yet"); + default: + throw new IllegalStateException("getCitationEntries: unhandled dataModel "); } } @@ -415,21 +425,21 @@ public void applyCitationEntries(XTextDocument doc, List citation WrappedTargetException { switch (dataModel) { - case JabRef52: - for (CitationEntry entry : citationEntries) { - Optional pageInfo = entry.getPageInfo().map(OOText::fromString); - pageInfo = PageInfo.normalizePageInfo(pageInfo); - if (pageInfo.isPresent()) { - String name = entry.getRefMarkName(); - UnoUserDefinedProperty.setStringProperty(doc, name, pageInfo.get().toString()); + case JabRef52: + for (CitationEntry entry : citationEntries) { + Optional pageInfo = entry.getPageInfo().map(OOText::fromString); + pageInfo = PageInfo.normalizePageInfo(pageInfo); + if (pageInfo.isPresent()) { + String name = entry.getRefMarkName(); + UnoUserDefinedProperty.setStringProperty(doc, name, pageInfo.get().toString()); + } } - } - break; - case JabRef60: - // xx - throw new IllegalStateException("applyCitationEntries for JabRef60 is not implemented yet"); - default: - throw new IllegalStateException("applyCitationEntries: unhandled dataModel "); + break; + case JabRef60: + // xx + throw new IllegalStateException("applyCitationEntries for JabRef60 is not implemented yet"); + default: + throw new IllegalStateException("applyCitationEntries: unhandled dataModel "); } } diff --git a/src/main/java/org/jabref/logic/openoffice/backend/Codec52.java b/src/main/java/org/jabref/logic/openoffice/backend/Codec52.java index f6d2b12245e..f567a3e459e 100644 --- a/src/main/java/org/jabref/logic/openoffice/backend/Codec52.java +++ b/src/main/java/org/jabref/logic/openoffice/backend/Codec52.java @@ -12,7 +12,7 @@ import org.jabref.model.openoffice.style.CitationType; /** - * How and what is encoded in a mark names. + * How and what is encoded in reference mark names under JabRef 5.2. * * - pageInfo does not appear here. It is not encoded in the mark name. */ @@ -51,51 +51,46 @@ public static class ParsedMarkName { * Integer representation was written into the document in JabRef52, keep it for compatibility. */ private static CitationType citationTypeFromInt(int i) { - switch (i) { - case 1: - return CitationType.AUTHORYEAR_PAR; - case 2: - return CitationType.AUTHORYEAR_INTEXT; - case 3: - return CitationType.INVISIBLE_CIT; - default: - throw new IllegalArgumentException("Invalid CitationType code"); - } + return switch (i) { + case 1 -> CitationType.AUTHORYEAR_PAR; + case 2 -> CitationType.AUTHORYEAR_INTEXT; + case 3 -> CitationType.INVISIBLE_CIT; + default -> throw new IllegalArgumentException("Invalid CitationType code"); + }; } private static int citationTypeToInt(CitationType i) { - switch (i) { - case AUTHORYEAR_PAR: - return 1; - case AUTHORYEAR_INTEXT: - return 2; - case INVISIBLE_CIT: - return 3; - default: - throw new IllegalArgumentException("Invalid CitationType"); - } + return switch (i) { + case AUTHORYEAR_PAR -> 1; + case AUTHORYEAR_INTEXT -> 2; + case INVISIBLE_CIT -> 3; + default -> throw new IllegalArgumentException("Invalid CitationType"); + }; } /** - * Produce a reference mark name for JabRef for the given citation key and citationType that + * Produce a reference mark name for JabRef for the given citationType and list citation keys that * does not yet appear among the reference marks of the document. * - * @param bibtexKey The citation key. + * @param usedNames Reference mark names already in use. + * @param citationKeys Identifies the cited sources. * @param citationType Encodes the effect of withText and inParenthesis options. * - * The first occurrence of bibtexKey gets no serial number, the second gets 0, the third 1 ... + * The first occurrence of citationKeys gets no serial number, the second gets 0, the third 1 ... * * Or the first unused in this series, after removals. */ public static String getUniqueMarkName(Set usedNames, - String bibtexKey, + List citationKeys, CitationType citationType) { + String citationKeysPart = String.join(",", citationKeys); + int i = 0; int citTypeCode = citationTypeToInt(citationType); - String name = BIB_CITATION + '_' + citTypeCode + '_' + bibtexKey; + String name = BIB_CITATION + '_' + citTypeCode + '_' + citationKeysPart; while (usedNames.contains(name)) { - name = BIB_CITATION + i + '_' + citTypeCode + '_' + bibtexKey; + name = BIB_CITATION + i + '_' + citTypeCode + '_' + citationKeysPart; i++; } return name; @@ -135,9 +130,8 @@ public static boolean isJabRefReferenceMarkName(String name) { * @param names The list to be filtered. */ public static List filterIsJabRefReferenceMarkName(List names) { - return (names - .stream() - .filter(Codec52::isJabRefReferenceMarkName) - .collect(Collectors.toList())); + return (names.stream() + .filter(Codec52::isJabRefReferenceMarkName) + .collect(Collectors.toList())); } } diff --git a/src/main/java/org/jabref/logic/openoffice/backend/NamedRangeManagerReferenceMark.java b/src/main/java/org/jabref/logic/openoffice/backend/NamedRangeManagerReferenceMark.java index d82ad08378e..93ad6d12908 100644 --- a/src/main/java/org/jabref/logic/openoffice/backend/NamedRangeManagerReferenceMark.java +++ b/src/main/java/org/jabref/logic/openoffice/backend/NamedRangeManagerReferenceMark.java @@ -16,25 +16,25 @@ public class NamedRangeManagerReferenceMark implements NamedRangeManager { @Override - public NamedRange nrmCreate(XTextDocument doc, - String refMarkName, - XTextCursor position, - boolean insertSpaceAfter, - boolean withoutBrackets) + public NamedRange createNamedRange(XTextDocument doc, + String refMarkName, + XTextCursor position, + boolean insertSpaceAfter, + boolean withoutBrackets) throws CreationException { return NamedRangeReferenceMark.create(doc, refMarkName, position, insertSpaceAfter, withoutBrackets); } @Override - public List nrmGetUsedNames(XTextDocument doc) + public List getUsedNames(XTextDocument doc) throws NoDocumentException { return UnoReferenceMark.getListOfNames(doc); } @Override - public Optional nrmGetFromDocument(XTextDocument doc, String refMarkName) + public Optional getNamedRangeFromDocument(XTextDocument doc, String refMarkName) throws NoDocumentException, WrappedTargetException { diff --git a/src/main/java/org/jabref/logic/openoffice/backend/NamedRangeReferenceMark.java b/src/main/java/org/jabref/logic/openoffice/backend/NamedRangeReferenceMark.java index a11e29aecd1..cfabb1e2de0 100644 --- a/src/main/java/org/jabref/logic/openoffice/backend/NamedRangeReferenceMark.java +++ b/src/main/java/org/jabref/logic/openoffice/backend/NamedRangeReferenceMark.java @@ -141,43 +141,42 @@ static Optional getFromDocument(XTextDocument doc, Stri .map(e -> new NamedRangeReferenceMark(refMarkName))); } - /* + /** * Remove it from the document. * * See: removeCitationGroups */ @Override - public void nrRemoveFromDocument(XTextDocument doc) + public void removeFromDocument(XTextDocument doc) throws WrappedTargetException, NoDocumentException { - UnoReferenceMark.removeIfExists(doc, this.nrGetRangeName()); + UnoReferenceMark.removeIfExists(doc, this.getRangeName()); } @Override - public String nrGetRangeName() { + public String getRangeName() { return id; } - /* - * ranges controlled by citation groups should not overlap with each other. + /** * * @return Optional.empty if the reference mark is missing. * * See: UnoReferenceMark.getAnchor */ @Override - public Optional nrGetMarkRange(XTextDocument doc) + public Optional getMarkRange(XTextDocument doc) throws NoDocumentException, WrappedTargetException { - String name = this.nrGetRangeName(); + String name = this.getRangeName(); return UnoReferenceMark.getAnchor(doc, name); } /** * Cursor for the reference marks as is, not prepared for filling, but does not need - * nrCleanFillCursor either. + * cleanFillCursor either. * * @return Optional.empty() if reference mark is missing from the document, * otherwise an XTextCursor for getMarkRange @@ -185,22 +184,22 @@ public Optional nrGetMarkRange(XTextDocument doc) * See: getRawCursorForCitationGroup */ @Override - public Optional nrGetRawCursor(XTextDocument doc) + public Optional getRawCursor(XTextDocument doc) throws NoDocumentException, WrappedTargetException { - String name = this.nrGetRangeName(); + String name = this.getRangeName(); Optional markAsTextContent = UnoReferenceMark.getAsTextContent(doc, name); if (markAsTextContent.isEmpty()) { - LOGGER.warn("nrGetRawCursor: markAsTextContent({}).isEmpty()", name); + LOGGER.warn("getRawCursor: markAsTextContent({}).isEmpty()", name); } Optional full = UnoCursor.getTextCursorOfTextContentAnchor(markAsTextContent.get()); if (full.isEmpty()) { - LOGGER.warn("nrGetRawCursor: full.isEmpty()"); + LOGGER.warn("getRawCursor: full.isEmpty()"); return Optional.empty(); } return full; @@ -210,13 +209,13 @@ public Optional nrGetRawCursor(XTextDocument doc) * See: getFillCursorForCitationGroup */ @Override - public XTextCursor nrGetFillCursor(XTextDocument doc) + public XTextCursor getFillCursor(XTextDocument doc) throws NoDocumentException, WrappedTargetException, CreationException { - String name = this.nrGetRangeName(); + String name = this.getRangeName(); final String left = NamedRangeReferenceMark.REFERENCE_MARK_LEFT_BRACKET; final String right = NamedRangeReferenceMark.REFERENCE_MARK_RIGHT_BRACKET; @@ -228,31 +227,31 @@ public XTextCursor nrGetFillCursor(XTextDocument doc) Optional markAsTextContent = UnoReferenceMark.getAsTextContent(doc, name); if (markAsTextContent.isEmpty()) { - String msg = String.format("nrGetFillCursor: markAsTextContent(%s).isEmpty (attempt %d)", name, i); + String msg = String.format("getFillCursor: markAsTextContent(%s).isEmpty (attempt %d)", name, i); throw new IllegalStateException(msg); } full = UnoCursor.getTextCursorOfTextContentAnchor(markAsTextContent.get()).orElse(null); if (full == null) { - String msg = String.format("nrGetFillCursor: full == null (attempt %d)", i); + String msg = String.format("getFillCursor: full == null (attempt %d)", i); throw new IllegalStateException(msg); } fullText = full.getString(); - LOGGER.debug("nrGetFillCursor: fulltext = '{}'", fullText); + LOGGER.debug("getFillCursor: fulltext = '{}'", fullText); if (fullText.length() >= 2) { - LOGGER.debug("nrGetFillCursor: (attempt: {}) fulltext.length() >= 2, break loop%n", i); + LOGGER.debug("getFillCursor: (attempt: {}) fulltext.length() >= 2, break loop%n", i); break; } else { // (fullText.length() < 2) if (i == 2) { - String msg = String.format("nrGetFillCursor: (fullText.length() < 2) (attempt %d)", i); + String msg = String.format("getFillCursor: (fullText.length() < 2) (attempt %d)", i); throw new IllegalStateException(msg); } // too short, recreate - LOGGER.warn("nrGetFillCursor: too short, recreate"); + LOGGER.warn("getFillCursor: too short, recreate"); full.setString(""); UnoReferenceMark.removeIfExists(doc, name); @@ -264,27 +263,27 @@ public XTextCursor nrGetFillCursor(XTextDocument doc) } if (full == null) { - throw new IllegalStateException("nrGetFillCursorFor: full == null (after loop)"); + throw new IllegalStateException("getFillCursorFor: full == null (after loop)"); } if (fullText == null) { - throw new IllegalStateException("nrGetFillCursor: fullText == null (after loop)"); + throw new IllegalStateException("getFillCursor: fullText == null (after loop)"); } fullText = full.getString(); if (fullText.length() < 2) { - throw new IllegalStateException("nrGetFillCursor: fullText.length() < 2 (after loop)'%n"); + throw new IllegalStateException("getFillCursor: fullText.length() < 2 (after loop)'%n"); } XTextCursor beta = full.getText().createTextCursorByRange(full); beta.collapseToStart(); beta.goRight((short) 1, false); beta.goRight((short) (fullText.length() - 2), true); - LOGGER.debug("nrGetFillCursor: beta(1) covers '{}'", beta.getString()); + LOGGER.debug("getFillCursor: beta(1) covers '{}'", beta.getString()); final short rightLength = (short) right.length(); if (fullText.startsWith(left) && fullText.endsWith(right)) { beta.setString(""); } else { - LOGGER.debug("nrGetFillCursor: recreating brackets for '{}'", fullText); + LOGGER.debug("getFillCursor: recreating brackets for '{}'", fullText); // we have at least two characters inside XTextCursor alpha = full.getText().createTextCursorByRange(full); @@ -299,41 +298,41 @@ public XTextCursor nrGetFillCursor(XTextDocument doc) String paddingy = "y"; String paddingz = "z"; beta.setString(paddingx + left + paddingy + right + paddingz); - LOGGER.debug("nrGetFillCursor: beta(2) covers '{}'", beta.getString()); + LOGGER.debug("getFillCursor: beta(2) covers '{}'", beta.getString()); // move beta to before the right bracket beta.collapseToEnd(); beta.goLeft((short) (rightLength + 1), false); // remove middle padding beta.goLeft((short) 1, true); - LOGGER.debug("nrGetFillCursor: beta(3) covers '{}'", beta.getString()); + LOGGER.debug("getFillCursor: beta(3) covers '{}'", beta.getString()); // only drop paddingy later: beta.setString(""); // drop the initial character and paddingx alpha.collapseToStart(); alpha.goRight((short) (1 + 1), true); - LOGGER.debug("nrGetFillCursor: alpha(4) covers '{}'", alpha.getString()); + LOGGER.debug("getFillCursor: alpha(4) covers '{}'", alpha.getString()); alpha.setString(""); // drop the last character and paddingz omega.collapseToEnd(); omega.goLeft((short) (1 + 1), true); - LOGGER.debug("nrGetFillCursor: omega(5) covers '{}'", omega.getString()); + LOGGER.debug("getFillCursor: omega(5) covers '{}'", omega.getString()); omega.setString(""); // drop paddingy now - LOGGER.debug("nrGetFillCursor: beta(6) covers '{}'", beta.getString()); + LOGGER.debug("getFillCursor: beta(6) covers '{}'", beta.getString()); beta.setString(""); // should be OK now. if (debugThisFun) { final short leftLength = (short) left.length(); alpha.goRight(leftLength, true); - LOGGER.debug("nrGetFillCursor: alpha(7) covers '{}', should be '{}'", alpha.getString(), left); + LOGGER.debug("getFillCursor: alpha(7) covers '{}', should be '{}'", alpha.getString(), left); omega.goLeft(rightLength, true); - LOGGER.debug("nrGetFillCursor: omega(8) covers '{}', should be '{}'", omega.getString(), right); + LOGGER.debug("getFillCursor: omega(8) covers '{}', should be '{}'", omega.getString(), right); } } @@ -384,13 +383,13 @@ public static void checkFillCursor(XTextCursor cursor) { * See: cleanFillCursorForCitationGroup */ @Override - public void nrCleanFillCursor(XTextDocument doc) + public void cleanFillCursor(XTextDocument doc) throws NoDocumentException, WrappedTargetException { // alwaysRemoveBrackets : full compatibility with JabRef 5.2: brackets are temporary, only - // exist between nrGetFillCursor and nrCleanFillCursor. + // exist between getFillCursor and cleanFillCursor. final boolean alwaysRemoveBrackets = false; // removeBracketsFromEmpty is intended to force removal if we are working on an "Empty citation" (INVISIBLE_CIT). @@ -401,25 +400,25 @@ public void nrCleanFillCursor(XTextDocument doc) final short leftLength = (short) left.length(); final short rightLength = (short) right.length(); - String name = this.nrGetRangeName(); + String name = this.getRangeName(); - XTextCursor full = this.nrGetRawCursor(doc).orElseThrow(IllegalStateException::new); + XTextCursor full = this.getRawCursor(doc).orElseThrow(IllegalStateException::new); final String fullText = full.getString(); final int fullTextLength = fullText.length(); if (!fullText.startsWith(left)) { - String msg = String.format("nrCleanFillCursor: (%s) does not start with REFERENCE_MARK_LEFT_BRACKET", name); + String msg = String.format("cleanFillCursor: (%s) does not start with REFERENCE_MARK_LEFT_BRACKET", name); throw new IllegalStateException(msg); } if (!fullText.endsWith(right)) { - String msg = String.format("nrCleanFillCursor: (%s) does not end with REFERENCE_MARK_RIGHT_BRACKET", name); + String msg = String.format("cleanFillCursor: (%s) does not end with REFERENCE_MARK_RIGHT_BRACKET", name); throw new IllegalStateException(msg); } final int contentLength = (fullTextLength - (leftLength + rightLength)); if (contentLength < 0) { - String msg = String.format("nrCleanFillCursor: length(%s) < 0", name); + String msg = String.format("cleanFillCursor: length(%s) < 0", name); throw new IllegalStateException(msg); } diff --git a/src/main/java/org/jabref/logic/openoffice/frontend/OOFrontend.java b/src/main/java/org/jabref/logic/openoffice/frontend/OOFrontend.java index 967a8cf084d..58057c382e7 100644 --- a/src/main/java/org/jabref/logic/openoffice/frontend/OOFrontend.java +++ b/src/main/java/org/jabref/logic/openoffice/frontend/OOFrontend.java @@ -35,19 +35,14 @@ import com.sun.star.beans.IllegalTypeException; import com.sun.star.beans.NotRemoveableException; -import com.sun.star.beans.PropertyExistException; import com.sun.star.beans.PropertyVetoException; -import com.sun.star.beans.UnknownPropertyException; -import com.sun.star.container.NoSuchElementException; import com.sun.star.lang.WrappedTargetException; import com.sun.star.text.XTextCursor; import com.sun.star.text.XTextDocument; import com.sun.star.text.XTextRange; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; public class OOFrontend { - private static final Logger LOGGER = LoggerFactory.getLogger(OOFrontend.class); + public final Backend52 backend; public final CitationGroups citationGroups; @@ -86,10 +81,9 @@ public Optional healthReport(XTextDocument doc) NoDocumentException { Map citationGroups = new HashMap<>(); - for (int i = 0; i < citationGroupNames.size(); i++) { - final String name = citationGroupNames.get(i); - CitationGroup cg = backend.readCitationGroupFromDocumentOrThrow(doc, name); - citationGroups.put(cg.cgid, cg); + for (String name : citationGroupNames) { + CitationGroup group = backend.readCitationGroupFromDocumentOrThrow(doc, name); + citationGroups.put(group.groupId, group); } return citationGroups; } @@ -117,11 +111,11 @@ public Optional healthReport(XTextDocument doc) WrappedTargetException { List> sortables = new ArrayList<>(); - for (CitationGroup cg : citationGroups.getCitationGroupsUnordered()) { + for (CitationGroup group : citationGroups.getCitationGroupsUnordered()) { XTextRange range = (this - .getMarkRange(doc, cg) + .getMarkRange(doc, group) .orElseThrow(IllegalStateException::new)); - sortables.add(new RangeSortEntry<>(range, 0, cg)); + sortables.add(new RangeSortEntry<>(range, 0, group)); } /* @@ -150,9 +144,7 @@ public Optional healthReport(XTextDocument doc) for (List> partition : partitions.getPartitions()) { int indexInPartition = 0; - for (int i = 0; i < partition.size(); i++) { - RangeSortEntry sortable = partition.get(i); - XTextRange aRange = sortable.getRange(); + for (RangeSortEntry sortable : partition) { sortable.setIndexInPosition(indexInPartition++); if (mapFootnotesToFootnoteMarks) { Optional footnoteMarkRange = @@ -183,18 +175,15 @@ private List getVisuallySortedCitationGroups(XTextDocument doc, FunctionalTextViewCursor fcursor) throws WrappedTargetException, - NoDocumentException, - JabRefException { + NoDocumentException { List> sortables = createVisualSortInput(doc, mapFootnotesToFootnoteMarks); List> sorted = RangeSortVisual.visualSort(sortables, doc, fcursor); - List result = (sorted.stream() - .map(RangeSortable::getContent) - .collect(Collectors.toList())); - - return result; + return (sorted.stream() + .map(RangeSortable::getContent) + .collect(Collectors.toList())); } /** @@ -240,7 +229,6 @@ public CitationGroup createCitationGroup(XTextDocument doc, NoDocumentException, WrappedTargetException, NotRemoveableException, - PropertyExistException, PropertyVetoException, IllegalTypeException { @@ -248,44 +236,38 @@ public CitationGroup createCitationGroup(XTextDocument doc, if (pageInfos.size() != citationKeys.size()) { throw new IllegalArgumentException("pageInfos.size != citationKeys.size"); } - CitationGroup cg = backend.createCitationGroup(doc, - citationKeys, - pageInfos, - citationType, - position, - insertSpaceAfter); - - this.citationGroups.afterCreateCitationGroup(cg); - return cg; + CitationGroup group = backend.createCitationGroup(doc, + citationKeys, + pageInfos, + citationType, + position, + insertSpaceAfter); + + this.citationGroups.afterCreateCitationGroup(group); + return group; } /** - * Remove {@code cg} both from the document and notify {@code citationGroups} + * Remove {@code group} both from the document and notify {@code citationGroups} */ - public void removeCitationGroup(CitationGroup cg, XTextDocument doc) + public void removeCitationGroup(CitationGroup group, XTextDocument doc) throws WrappedTargetException, NoDocumentException, - NoSuchElementException, - NotRemoveableException, - PropertyExistException, - IllegalTypeException { + NotRemoveableException { - backend.removeCitationGroup(cg, doc); - this.citationGroups.afterRemoveCitationGroup(cg); + backend.removeCitationGroup(group, doc); + this.citationGroups.afterRemoveCitationGroup(group); } public void removeCitationGroups(List cgs, XTextDocument doc) throws WrappedTargetException, NoDocumentException, - NoSuchElementException, - NotRemoveableException, - PropertyExistException, - IllegalTypeException { + NotRemoveableException { - for (CitationGroup cg : cgs) { - removeCitationGroup(cg, doc); + for (CitationGroup group : cgs) { + removeCitationGroup(group, doc); } } @@ -295,31 +277,30 @@ public void removeCitationGroups(List cgs, XTextDocument doc) * @return Optional.empty() if the reference mark is missing. * */ - public Optional getMarkRange(XTextDocument doc, CitationGroup cg) + public Optional getMarkRange(XTextDocument doc, CitationGroup group) throws NoDocumentException, WrappedTargetException { - return backend.getMarkRange(cg, doc); + return backend.getMarkRange(group, doc); } - public XTextCursor getFillCursorForCitationGroup(XTextDocument doc, CitationGroup cg) + public XTextCursor getFillCursorForCitationGroup(XTextDocument doc, CitationGroup group) throws NoDocumentException, WrappedTargetException, CreationException { - return backend.getFillCursorForCitationGroup(cg, doc); + return backend.getFillCursorForCitationGroup(group, doc); } /** * Remove brackets added by getFillCursorForCitationGroup. */ - public void cleanFillCursorForCitationGroup(XTextDocument doc, CitationGroup cg) + public void cleanFillCursorForCitationGroup(XTextDocument doc, CitationGroup group) throws NoDocumentException, - WrappedTargetException, - CreationException { + WrappedTargetException { - backend.cleanFillCursorForCitationGroup(cg, doc); + backend.cleanFillCursorForCitationGroup(group, doc); } /** @@ -335,11 +316,11 @@ public List> citationRanges(XTextDocument List> result = new ArrayList<>(citationGroups.numberOfCitationGroups()); - for (CitationGroup cg : citationGroups.getCitationGroupsUnordered()) { - XTextRange range = this.getMarkRange(doc, cg).orElseThrow(IllegalStateException::new); - String description = cg.cgid.citationGroupIdAsString(); + for (CitationGroup group : citationGroups.getCitationGroupsUnordered()) { + XTextRange range = this.getMarkRange(doc, group).orElseThrow(IllegalStateException::new); + String description = group.groupId.citationGroupIdAsString(); result.add(new RangeForOverlapCheck<>(range, - cg.cgid, + group.groupId, RangeForOverlapCheck.REFERENCE_MARK_KIND, description)); } @@ -364,10 +345,7 @@ public List> bibliographyRanges(XTextDocum return result; } - public List> viewCursorRanges(XTextDocument doc) - throws - NoDocumentException, - WrappedTargetException { + public List> viewCursorRanges(XTextDocument doc) { List> result = new ArrayList<>(); @@ -394,10 +372,7 @@ public List> viewCursorRanges(XTextDocumen * ranges. */ public List> - footnoteMarkRanges(XTextDocument doc, List> citationRanges) - throws - NoDocumentException, - WrappedTargetException { + footnoteMarkRanges(XTextDocument doc, List> citationRanges) { // We partition by XText and use a single range from // each partition to get at the corresponding footnotemark range. @@ -432,7 +407,7 @@ public List> viewCursorRanges(XTextDocumen static String rangeOverlapsToMessage(List>> overlaps) { - if (overlaps.size() == 0) { + if (overlaps.isEmpty()) { return "(*no overlaps*)"; } @@ -443,9 +418,9 @@ static String rangeOverlapsToMessage(List Localization.lang("Found identical ranges"); - case OVERLAP -> Localization.lang("Found overlapping ranges"); - case TOUCH -> Localization.lang("Found touching ranges"); + case EQUAL_RANGE -> Localization.lang("Found identical ranges"); + case OVERLAP -> Localization.lang("Found overlapping ranges"); + case TOUCH -> Localization.lang("Found touching ranges"); }); msg.append(": "); msg.append(listOfRanges); @@ -483,7 +458,7 @@ static String rangeOverlapsToMessage(List checkRangeOverlaps(XTextDocument doc, List>> overlaps = RangeOverlapWithin.findOverlappingRanges(doc, ranges, requireSeparation, reportAtMost); - if (overlaps.size() == 0) { + if (overlaps.isEmpty()) { return OOVoidResult.ok(); } return OOVoidResult.error(new JabRefException("Found overlapping or touching ranges", @@ -552,7 +527,6 @@ public OOVoidResult checkRangeOverlaps(XTextDocument doc, */ public List getCitationEntries(XTextDocument doc) throws - UnknownPropertyException, WrappedTargetException, NoDocumentException { return this.backend.getCitationEntries(doc, citationGroups); @@ -560,13 +534,9 @@ public List getCitationEntries(XTextDocument doc) public void applyCitationEntries(XTextDocument doc, List citationEntries) throws - UnknownPropertyException, - NotRemoveableException, - PropertyExistException, PropertyVetoException, IllegalTypeException, IllegalArgumentException, - NoDocumentException, WrappedTargetException { this.backend.applyCitationEntries(doc, citationEntries); } @@ -574,13 +544,12 @@ public void applyCitationEntries(XTextDocument doc, List citation public void imposeGlobalOrder(XTextDocument doc, FunctionalTextViewCursor fcursor) throws WrappedTargetException, - NoDocumentException, - JabRefException { + NoDocumentException { boolean mapFootnotesToFootnoteMarks = true; List sortedCitationGroups = getVisuallySortedCitationGroups(doc, mapFootnotesToFootnoteMarks, fcursor); - List sortedCitationGroupIds = OOListUtil.map(sortedCitationGroups, cg -> cg.cgid); + List sortedCitationGroupIds = OOListUtil.map(sortedCitationGroups, group -> group.groupId); citationGroups.setGlobalOrder(sortedCitationGroupIds); } } diff --git a/src/main/java/org/jabref/logic/openoffice/frontend/UpdateBibliography.java b/src/main/java/org/jabref/logic/openoffice/frontend/UpdateBibliography.java index 2d0de320ca6..f7b3d14def6 100644 --- a/src/main/java/org/jabref/logic/openoffice/frontend/UpdateBibliography.java +++ b/src/main/java/org/jabref/logic/openoffice/frontend/UpdateBibliography.java @@ -12,9 +12,6 @@ import org.jabref.model.openoffice.uno.UnoBookmark; import org.jabref.model.openoffice.uno.UnoTextSection; -import com.sun.star.beans.PropertyVetoException; -import com.sun.star.beans.UnknownPropertyException; -import com.sun.star.container.NoSuchElementException; import com.sun.star.lang.WrappedTargetException; import com.sun.star.text.XTextCursor; import com.sun.star.text.XTextDocument; @@ -28,12 +25,15 @@ public class UpdateBibliography { private static final String BIB_SECTION_NAME = "JR_bib"; private static final String BIB_SECTION_END_NAME = "JR_bib_end"; + private UpdateBibliography() { + /**/ + } + public static Optional getBibliographyRange(XTextDocument doc) throws NoDocumentException, WrappedTargetException { - Optional sectionRange = UnoTextSection.getAnchor(doc, BIB_SECTION_NAME); - return sectionRange; + return UnoTextSection.getAnchor(doc, BIB_SECTION_NAME); } /** @@ -45,11 +45,8 @@ public static void rebuildBibTextSection(XTextDocument doc, OOBibStyle style, boolean alwaysAddCitedOnPages) throws - NoSuchElementException, WrappedTargetException, CreationException, - PropertyVetoException, - UnknownPropertyException, NoDocumentException { clearBibTextSectionContent2(doc); @@ -116,9 +113,6 @@ private static void populateBibTextSection(XTextDocument doc, CreationException, IllegalArgumentException, NoDocumentException, - NoSuchElementException, - PropertyVetoException, - UnknownPropertyException, WrappedTargetException { XTextRange sectionRange = getBibliographyRange(doc).orElseThrow(IllegalStateException::new); @@ -134,7 +128,7 @@ private static void populateBibTextSection(XTextDocument doc, OOTextIntoOO.write(doc, cursor, bibliographyText); cursor.collapseToEnd(); - // remove the inital empty paragraph from the section. + // remove the initial empty paragraph from the section. sectionRange = getBibliographyRange(doc).orElseThrow(IllegalStateException::new); XTextCursor initialParagraph = doc.getText().createTextCursorByRange(sectionRange); initialParagraph.collapseToStart(); diff --git a/src/main/java/org/jabref/logic/openoffice/frontend/UpdateCitationMarkers.java b/src/main/java/org/jabref/logic/openoffice/frontend/UpdateCitationMarkers.java index a3363938797..55f04775094 100644 --- a/src/main/java/org/jabref/logic/openoffice/frontend/UpdateCitationMarkers.java +++ b/src/main/java/org/jabref/logic/openoffice/frontend/UpdateCitationMarkers.java @@ -4,7 +4,6 @@ import java.util.Objects; import java.util.Optional; -import org.jabref.logic.JabRefException; import org.jabref.logic.openoffice.style.OOBibStyle; import org.jabref.model.openoffice.ootext.OOText; import org.jabref.model.openoffice.ootext.OOTextIntoOO; @@ -16,10 +15,7 @@ import com.sun.star.beans.IllegalTypeException; import com.sun.star.beans.NotRemoveableException; -import com.sun.star.beans.PropertyExistException; import com.sun.star.beans.PropertyVetoException; -import com.sun.star.beans.UnknownPropertyException; -import com.sun.star.container.NoSuchElementException; import com.sun.star.lang.WrappedTargetException; import com.sun.star.text.XTextCursor; import com.sun.star.text.XTextDocument; @@ -33,6 +29,10 @@ public class UpdateCitationMarkers { private static final Logger LOGGER = LoggerFactory.getLogger(UpdateCitationMarkers.class); + private UpdateCitationMarkers() { + /**/ + } + /** * Visit each reference mark in referenceMarkNames, overwrite its * text content. @@ -48,33 +48,29 @@ public class UpdateCitationMarkers { public static void applyNewCitationMarkers(XTextDocument doc, OOFrontend fr, OOBibStyle style) throws NoDocumentException, - UnknownPropertyException, CreationException, - WrappedTargetException, - PropertyVetoException, - NoSuchElementException, - JabRefException { + WrappedTargetException { CitationGroups citationGroups = fr.citationGroups; - for (CitationGroup cg : citationGroups.getCitationGroupsUnordered()) { + for (CitationGroup group : citationGroups.getCitationGroupsUnordered()) { - boolean withText = (cg.citationType != CitationType.INVISIBLE_CIT); - Optional marker = cg.getCitationMarker(); + boolean withText = (group.citationType != CitationType.INVISIBLE_CIT); + Optional marker = group.getCitationMarker(); if (!marker.isPresent()) { LOGGER.warn("applyNewCitationMarkers: no marker for {}", - cg.cgid.citationGroupIdAsString()); + group.groupId.citationGroupIdAsString()); continue; } if (withText && marker.isPresent()) { - XTextCursor cursor = fr.getFillCursorForCitationGroup(doc, cg); + XTextCursor cursor = fr.getFillCursorForCitationGroup(doc, group); fillCitationMarkInCursor(doc, cursor, marker.get(), withText, style); - fr.cleanFillCursorForCitationGroup(doc, cg); + fr.cleanFillCursorForCitationGroup(doc, group); } } @@ -85,11 +81,8 @@ public static void fillCitationMarkInCursor(XTextDocument doc, OOText citationText, boolean withText, OOBibStyle style) - throws - UnknownPropertyException, - PropertyVetoException, + throws WrappedTargetException, - NoSuchElementException, CreationException, IllegalArgumentException { @@ -135,37 +128,33 @@ public static void createAndFillCitationGroup(OOFrontend fr, OOBibStyle style, boolean insertSpaceAfter) throws - UnknownPropertyException, NotRemoveableException, - PropertyExistException, - PropertyVetoException, WrappedTargetException, PropertyVetoException, IllegalArgumentException, CreationException, NoDocumentException, - IllegalTypeException, - NoSuchElementException { + IllegalTypeException { Objects.requireNonNull(pageInfos); if (pageInfos.size() != citationKeys.size()) { throw new IllegalArgumentException("pageInfos.size != citationKeys.size"); } - CitationGroup cg = fr.createCitationGroup(doc, - citationKeys, - pageInfos, - citationType, - position, - insertSpaceAfter); + CitationGroup group = fr.createCitationGroup(doc, + citationKeys, + pageInfos, + citationType, + position, + insertSpaceAfter); final boolean withText = citationType.withText(); if (withText) { - XTextCursor c2 = fr.getFillCursorForCitationGroup(doc, cg); + XTextCursor fillCursor = fr.getFillCursorForCitationGroup(doc, group); - UpdateCitationMarkers.fillCitationMarkInCursor(doc, c2, citationText, withText, style); + UpdateCitationMarkers.fillCitationMarkInCursor(doc, fillCursor, citationText, withText, style); - fr.cleanFillCursorForCitationGroup(doc, cg); + fr.cleanFillCursorForCitationGroup(doc, group); } position.collapseToEnd(); } diff --git a/src/main/java/org/jabref/logic/openoffice/style/OOFormatBibliography.java b/src/main/java/org/jabref/logic/openoffice/style/OOFormatBibliography.java index 6c22c8abb09..4bc3fac6191 100644 --- a/src/main/java/org/jabref/logic/openoffice/style/OOFormatBibliography.java +++ b/src/main/java/org/jabref/logic/openoffice/style/OOFormatBibliography.java @@ -168,12 +168,12 @@ private static OOText formatCitedOnPages(CitationGroups cgs, CitedKey ck) { List citationGroups = new ArrayList<>(); for (CitationPath p : ck.getCitationPaths()) { - CitationGroupId cgid = p.group; - Optional cg = cgs.getCitationGroup(cgid); - if (cg.isEmpty()) { + CitationGroupId groupId = p.group; + Optional group = cgs.getCitationGroup(groupId); + if (group.isEmpty()) { throw new IllegalStateException(); } - citationGroups.add(cg.get()); + citationGroups.add(group.get()); } // sort the citationGroups according to their indexInGlobalOrder @@ -184,11 +184,11 @@ private static OOText formatCitedOnPages(CitationGroups cgs, CitedKey ck) { }); int i = 0; - for (CitationGroup cg : citationGroups) { + for (CitationGroup group : citationGroups) { if (i > 0) { sb.append(", "); } - String markName = cg.getReferenceMarkNameForLinking().orElseThrow(IllegalStateException::new); + String markName = group.getReferenceMarkNameForLinking().orElseThrow(IllegalStateException::new); OOText xref = OOFormat.formatReferenceToPageNumberOfReferenceMark(markName); sb.append(xref.toString()); i++; diff --git a/src/main/java/org/jabref/logic/openoffice/style/OOProcessAuthorYearMarkers.java b/src/main/java/org/jabref/logic/openoffice/style/OOProcessAuthorYearMarkers.java index 896e747913f..1aaeb538930 100644 --- a/src/main/java/org/jabref/logic/openoffice/style/OOProcessAuthorYearMarkers.java +++ b/src/main/java/org/jabref/logic/openoffice/style/OOProcessAuthorYearMarkers.java @@ -64,6 +64,9 @@ private static void createUniqueLetters(CitedKeys sortedCitedKeys, CitationGroup String citationKey = citedKey.citationKey; List clashingKeys = normCitMarkerToClachingKeys.putIfAbsent(normCitMarker, new ArrayList<>(1)); + if (clashingKeys == null) { + clashingKeys = normCitMarkerToClachingKeys.get(normCitMarker); + } if (!clashingKeys.contains(citationKey)) { // First appearance of citationKey, add to list. clashingKeys.add(citationKey); @@ -107,8 +110,8 @@ private static void createUniqueLetters(CitedKeys sortedCitedKeys, CitationGroup */ private static void setIsFirstAppearanceOfSourceInCitations(CitationGroups cgs) { Set seenBefore = new HashSet<>(); - for (CitationGroup cg : cgs.getCitationGroupsInGlobalOrder()) { - for (Citation cit : cg.getCitationsInLocalOrder()) { + for (CitationGroup group : cgs.getCitationGroupsInGlobalOrder()) { + for (Citation cit : group.getCitationsInLocalOrder()) { String currentKey = cit.citationKey; if (!seenBefore.contains(currentKey)) { cit.setIsFirstAppearanceOfSource(true); @@ -142,17 +145,17 @@ static void produceCitationMarkers(CitationGroups cgs, OOBibStyle style) { // Mark first appearance of each citationKey setIsFirstAppearanceOfSourceInCitations(cgs); - for (CitationGroup cg : cgs.getCitationGroupsInGlobalOrder()) { + for (CitationGroup group : cgs.getCitationGroupsInGlobalOrder()) { - final boolean inParenthesis = (cg.citationType == CitationType.AUTHORYEAR_PAR); + final boolean inParenthesis = (group.citationType == CitationType.AUTHORYEAR_PAR); final NonUniqueCitationMarker strictlyUnique = NonUniqueCitationMarker.THROWS; - List cits = cg.getCitationsInLocalOrder(); + List cits = group.getCitationsInLocalOrder(); List citationMarkerEntries = OOListUtil.map(cits, e -> e); OOText citMarker = style.createCitationMarker(citationMarkerEntries, inParenthesis, strictlyUnique); - cg.setCitationMarker(Optional.of(citMarker)); + group.setCitationMarker(Optional.of(citMarker)); } } diff --git a/src/main/java/org/jabref/logic/openoffice/style/OOProcessCitationKeyMarkers.java b/src/main/java/org/jabref/logic/openoffice/style/OOProcessCitationKeyMarkers.java index 9789d6c6caa..5329d9ca021 100644 --- a/src/main/java/org/jabref/logic/openoffice/style/OOProcessCitationKeyMarkers.java +++ b/src/main/java/org/jabref/logic/openoffice/style/OOProcessCitationKeyMarkers.java @@ -24,12 +24,12 @@ static void produceCitationMarkers(CitationGroups cgs, OOBibStyle style) { cgs.createPlainBibliographySortedByComparator(OOProcess.AUTHOR_YEAR_TITLE_COMPARATOR); - for (CitationGroup cg : cgs.getCitationGroupsInGlobalOrder()) { + for (CitationGroup group : cgs.getCitationGroupsInGlobalOrder()) { String citMarker = style.getCitationGroupMarkupBefore() - + String.join(",", OOListUtil.map(cg.getCitationsInLocalOrder(), Citation::getCitationKey)) + + String.join(",", OOListUtil.map(group.getCitationsInLocalOrder(), Citation::getCitationKey)) + style.getCitationGroupMarkupAfter(); - cg.setCitationMarker(Optional.of(OOText.fromString(citMarker))); + group.setCitationMarker(Optional.of(OOText.fromString(citMarker))); } } } diff --git a/src/main/java/org/jabref/logic/openoffice/style/OOProcessNumericMarkers.java b/src/main/java/org/jabref/logic/openoffice/style/OOProcessNumericMarkers.java index 589ebfb7a48..deddab66cca 100644 --- a/src/main/java/org/jabref/logic/openoffice/style/OOProcessNumericMarkers.java +++ b/src/main/java/org/jabref/logic/openoffice/style/OOProcessNumericMarkers.java @@ -37,10 +37,10 @@ static void produceCitationMarkers(CitationGroups cgs, OOBibStyle style) { cgs.createNumberedBibliographySortedByComparator(OOProcess.AUTHOR_YEAR_TITLE_COMPARATOR); } - for (CitationGroup cg : cgs.getCitationGroupsInGlobalOrder()) { - List cits = OOListUtil.map(cg.getCitationsInLocalOrder(), e -> e); + for (CitationGroup group : cgs.getCitationGroupsInGlobalOrder()) { + List cits = OOListUtil.map(group.getCitationsInLocalOrder(), e -> e); OOText citMarker = style.getNumCitationMarker2(cits); - cg.setCitationMarker(Optional.of(citMarker)); + group.setCitationMarker(Optional.of(citMarker)); } } diff --git a/src/main/java/org/jabref/model/openoffice/backend/NamedRange.java b/src/main/java/org/jabref/model/openoffice/backend/NamedRange.java index aa09f36edef..63981ceba70 100644 --- a/src/main/java/org/jabref/model/openoffice/backend/NamedRange.java +++ b/src/main/java/org/jabref/model/openoffice/backend/NamedRange.java @@ -10,23 +10,32 @@ import com.sun.star.text.XTextDocument; import com.sun.star.text.XTextRange; +/** + * NamedRange (with NamedRangeManager) attempts to provide a common interface for working with + * reference mark based and bookmark based text ranges to be used as locations to fill with citation + * markers. LibreOffice supports name-based lookup and listing names for both (hence the name). + * + * Note: currently only implemented for refence marks (in NamedRangeReferenceMark and + * NamedRangeManagerReferenceMark). + * + */ public interface NamedRange { - String nrGetRangeName(); + String getRangeName(); /** * @return Optional.empty if the mark is missing from the document. */ - Optional nrGetMarkRange(XTextDocument doc) + Optional getMarkRange(XTextDocument doc) throws NoDocumentException, WrappedTargetException; /** * Cursor for the reference marks as is, not prepared for filling, but does not need - * nrCleanFillCursor either. + * cleanFillCursor either. */ - Optional nrGetRawCursor(XTextDocument doc) + Optional getRawCursor(XTextDocument doc) throws NoDocumentException, WrappedTargetException; @@ -34,9 +43,9 @@ Optional nrGetRawCursor(XTextDocument doc) /** * Get a cursor for filling in text. * - * Must be followed by nrCleanFillCursor() + * Must be followed by cleanFillCursor() */ - XTextCursor nrGetFillCursor(XTextDocument doc) + XTextCursor getFillCursor(XTextDocument doc) throws NoDocumentException, WrappedTargetException, @@ -47,7 +56,7 @@ XTextCursor nrGetFillCursor(XTextDocument doc) * single character, leave the left bracket. * */ - void nrCleanFillCursor(XTextDocument doc) + void cleanFillCursor(XTextDocument doc) throws NoDocumentException, WrappedTargetException; @@ -55,7 +64,7 @@ void nrCleanFillCursor(XTextDocument doc) /** * Note: create is in NamedRangeManager */ - void nrRemoveFromDocument(XTextDocument doc) + void removeFromDocument(XTextDocument doc) throws WrappedTargetException, NoDocumentException; diff --git a/src/main/java/org/jabref/model/openoffice/backend/NamedRangeManager.java b/src/main/java/org/jabref/model/openoffice/backend/NamedRangeManager.java index 536871b5c34..9bbcc752350 100644 --- a/src/main/java/org/jabref/model/openoffice/backend/NamedRangeManager.java +++ b/src/main/java/org/jabref/model/openoffice/backend/NamedRangeManager.java @@ -12,19 +12,19 @@ public interface NamedRangeManager { - NamedRange nrmCreate(XTextDocument doc, - String markName, - XTextCursor position, - boolean insertSpaceAfter, - boolean withoutBrackets) + NamedRange createNamedRange(XTextDocument doc, + String markName, + XTextCursor position, + boolean insertSpaceAfter, + boolean withoutBrackets) throws CreationException; - List nrmGetUsedNames(XTextDocument doc) + List getUsedNames(XTextDocument doc) throws NoDocumentException; - Optional nrmGetFromDocument(XTextDocument doc, String markName) + Optional getNamedRangeFromDocument(XTextDocument doc, String markName) throws NoDocumentException, WrappedTargetException; diff --git a/src/main/java/org/jabref/model/openoffice/ootext/OOTextIntoOO.java b/src/main/java/org/jabref/model/openoffice/ootext/OOTextIntoOO.java index 6e61faed147..909b088b5b3 100644 --- a/src/main/java/org/jabref/model/openoffice/ootext/OOTextIntoOO.java +++ b/src/main/java/org/jabref/model/openoffice/ootext/OOTextIntoOO.java @@ -189,135 +189,135 @@ public static void write(XTextDocument doc, XTextCursor position, OOText ootext) // Handle tags: switch (tagName) { - case "b": - formatStack.pushLayer(setCharWeight(FontWeight.BOLD)); - expectEnd.push("/" + tagName); - break; - case "i": - case "em": - formatStack.pushLayer(setCharPosture(FontSlant.ITALIC)); - expectEnd.push("/" + tagName); - break; - case "smallcaps": - formatStack.pushLayer(setCharCaseMap(CaseMap.SMALLCAPS)); - expectEnd.push("/" + tagName); - break; - case "sup": - formatStack.pushLayer(setSuperScript(formatStack)); - expectEnd.push("/" + tagName); - break; - case "sub": - formatStack.pushLayer(setSubScript(formatStack)); - expectEnd.push("/" + tagName); - break; - case "u": - formatStack.pushLayer(setCharUnderline(FontUnderline.SINGLE)); - expectEnd.push("/" + tagName); - break; - case "s": - formatStack.pushLayer(setCharStrikeout(FontStrikeout.SINGLE)); - expectEnd.push("/" + tagName); - break; - case "/p": - // nop - break; - case "p": - insertParagraphBreak(text, cursor); - cursor.collapseToEnd(); - for (OOPair pair : attributes) { - String key = pair.a; - String value = pair.b; - switch (key) { - case "oo:ParaStyleName": - //

- if (StringUtil.isNullOrEmpty(value)) { - LOGGER.debug(String.format("oo:ParaStyleName inherited")); - } else { - if (setParagraphStyle(cursor, value)) { - // Presumably tested already: - LOGGER.debug(String.format("oo:ParaStyleName=\"%s\" failed", value)); - } + case "b": + formatStack.pushLayer(setCharWeight(FontWeight.BOLD)); + expectEnd.push("/" + tagName); + break; + case "i": + case "em": + formatStack.pushLayer(setCharPosture(FontSlant.ITALIC)); + expectEnd.push("/" + tagName); + break; + case "smallcaps": + formatStack.pushLayer(setCharCaseMap(CaseMap.SMALLCAPS)); + expectEnd.push("/" + tagName); + break; + case "sup": + formatStack.pushLayer(setSuperScript(formatStack)); + expectEnd.push("/" + tagName); + break; + case "sub": + formatStack.pushLayer(setSubScript(formatStack)); + expectEnd.push("/" + tagName); + break; + case "u": + formatStack.pushLayer(setCharUnderline(FontUnderline.SINGLE)); + expectEnd.push("/" + tagName); + break; + case "s": + formatStack.pushLayer(setCharStrikeout(FontStrikeout.SINGLE)); + expectEnd.push("/" + tagName); + break; + case "/p": + // nop + break; + case "p": + insertParagraphBreak(text, cursor); + cursor.collapseToEnd(); + for (OOPair pair : attributes) { + String key = pair.a; + String value = pair.b; + switch (key) { + case "oo:ParaStyleName": + //

+ if (StringUtil.isNullOrEmpty(value)) { + LOGGER.debug(String.format("oo:ParaStyleName inherited")); + } else { + if (setParagraphStyle(cursor, value)) { + // Presumably tested already: + LOGGER.debug(String.format("oo:ParaStyleName=\"%s\" failed", value)); + } + } + break; + default: + LOGGER.warn(String.format("Unexpected attribute '%s' for <%s>", key, tagName)); + break; } - break; - default: - LOGGER.warn(String.format("Unexpected attribute '%s' for <%s>", key, tagName)); - break; } - } - break; - case "oo:referenceToPageNumberOfReferenceMark": - for (OOPair pair : attributes) { - String key = pair.a; - String value = pair.b; - switch (key) { - case "target": - UnoCrossRef.insertReferenceToPageNumberOfReferenceMark(doc, value, cursor); - break; - default: - LOGGER.warn(String.format("Unexpected attribute '%s' for <%s>", key, tagName)); - break; + break; + case "oo:referenceToPageNumberOfReferenceMark": + for (OOPair pair : attributes) { + String key = pair.a; + String value = pair.b; + switch (key) { + case "target": + UnoCrossRef.insertReferenceToPageNumberOfReferenceMark(doc, value, cursor); + break; + default: + LOGGER.warn(String.format("Unexpected attribute '%s' for <%s>", key, tagName)); + break; + } } - } - break; - case "tt": - // Note: "Example" names a character style in LibreOffice. - formatStack.pushLayer(setCharStyleName("Example")); - expectEnd.push("/" + tagName); - break; - case "span": - List> settings = new ArrayList<>(); - for (OOPair pair : attributes) { - String key = pair.a; - String value = pair.b; - switch (key) { - case "oo:CharStyleName": - // - settings.addAll(setCharStyleName(value)); - break; - case "lang": - // - // - settings.addAll(setCharLocale(value)); - break; - case "style": - // HTML-style small-caps - if ("font-variant: small-caps".equals(value)) { - settings.addAll(setCharCaseMap(CaseMap.SMALLCAPS)); - break; + break; + case "tt": + // Note: "Example" names a character style in LibreOffice. + formatStack.pushLayer(setCharStyleName("Example")); + expectEnd.push("/" + tagName); + break; + case "span": + List> settings = new ArrayList<>(); + for (OOPair pair : attributes) { + String key = pair.a; + String value = pair.b; + switch (key) { + case "oo:CharStyleName": + // + settings.addAll(setCharStyleName(value)); + break; + case "lang": + // + // + settings.addAll(setCharLocale(value)); + break; + case "style": + // HTML-style small-caps + if ("font-variant: small-caps".equals(value)) { + settings.addAll(setCharCaseMap(CaseMap.SMALLCAPS)); + break; + } + LOGGER.warn(String.format("Unexpected value %s for attribute '%s' for <%s>", + value, key, tagName)); + break; + default: + LOGGER.warn(String.format("Unexpected attribute '%s' for <%s>", key, tagName)); + break; } - LOGGER.warn(String.format("Unexpected value %s for attribute '%s' for <%s>", - value, key, tagName)); - break; - default: - LOGGER.warn(String.format("Unexpected attribute '%s' for <%s>", key, tagName)); - break; } - } - formatStack.pushLayer(settings); - expectEnd.push("/" + tagName); - break; - case "/b": - case "/i": - case "/em": - case "/tt": - case "/smallcaps": - case "/sup": - case "/sub": - case "/u": - case "/s": - case "/span": - formatStack.popLayer(); - String expected = expectEnd.pop(); - if (!tagName.equals(expected)) { - LOGGER.warn(String.format("expected '<%s>', found '<%s>' after '%s'", - expected, - tagName, - currentSubstring)); - } - break; - default: - LOGGER.warn(String.format("ignoring unknown tag '<%s>'", tagName)); - break; + formatStack.pushLayer(settings); + expectEnd.push("/" + tagName); + break; + case "/b": + case "/i": + case "/em": + case "/tt": + case "/smallcaps": + case "/sup": + case "/sub": + case "/u": + case "/s": + case "/span": + formatStack.popLayer(); + String expected = expectEnd.pop(); + if (!tagName.equals(expected)) { + LOGGER.warn(String.format("expected '<%s>', found '<%s>' after '%s'", + expected, + tagName, + currentSubstring)); + } + break; + default: + LOGGER.warn(String.format("ignoring unknown tag '<%s>'", tagName)); + break; } piv = tagMatcher.end(); diff --git a/src/main/java/org/jabref/model/openoffice/style/CitationGroup.java b/src/main/java/org/jabref/model/openoffice/style/CitationGroup.java index 3b9021eb73f..90afb872d4f 100644 --- a/src/main/java/org/jabref/model/openoffice/style/CitationGroup.java +++ b/src/main/java/org/jabref/model/openoffice/style/CitationGroup.java @@ -19,7 +19,7 @@ public class CitationGroup { /* * Identifies this citation group. */ - public final CitationGroupId cgid; + public final CitationGroupId groupId; /* * The core data, stored in the document: @@ -60,12 +60,12 @@ public class CitationGroup { private Optional citationMarker; public CitationGroup(OODataModel dataModel, - CitationGroupId cgid, + CitationGroupId groupId, CitationType citationType, List citationsInStorageOrder, Optional referenceMarkNameForLinking) { this.dataModel = dataModel; - this.cgid = cgid; + this.groupId = groupId; this.citationType = citationType; this.citationsInStorageOrder = Collections.unmodifiableList(citationsInStorageOrder); this.localOrder = OOListUtil.makeIndices(citationsInStorageOrder.size()); diff --git a/src/main/java/org/jabref/model/openoffice/style/CitationGroups.java b/src/main/java/org/jabref/model/openoffice/style/CitationGroups.java index 0bfe4a832d0..b03028b76ef 100644 --- a/src/main/java/org/jabref/model/openoffice/style/CitationGroups.java +++ b/src/main/java/org/jabref/model/openoffice/style/CitationGroups.java @@ -62,12 +62,12 @@ public void distributeToCitations(List where, T value) { for (CitationPath p : where) { - CitationGroup cg = citationGroupsUnordered.get(p.group); - if (cg == null) { + CitationGroup group = citationGroupsUnordered.get(p.group); + if (group == null) { LOGGER.warn("CitationGroups.distributeToCitations: group missing"); continue; } - Citation cit = cg.citationsInStorageOrder.get(p.storageIndexInGroup); + Citation cit = group.citationsInStorageOrder.get(p.storageIndexInGroup); fun.accept(new OOPair<>(cit, value)); } } @@ -96,8 +96,8 @@ public void lookupCitations(List databases) { // right thing. Seems to work. But what we gained from avoiding collect-and-distribute // may be lost in more complicated consistency checking in addPath. // - /// for (CitationGroup cg : getCitationGroupsUnordered()) { - /// for (Citation cit : cg.citationsInStorageOrder) { + /// for (CitationGroup group : getCitationGroupsUnordered()) { + /// for (Citation cit : group.citationsInStorageOrder) { /// cit.lookupInDatabases(databases); /// } /// } @@ -114,7 +114,7 @@ public List getCitationGroupsInGlobalOrder() { if (globalOrder.isEmpty()) { throw new IllegalStateException("getCitationGroupsInGlobalOrder: not ordered yet"); } - return OOListUtil.map(globalOrder.get(), cgid -> citationGroupsUnordered.get(cgid)); + return OOListUtil.map(globalOrder.get(), groupId -> citationGroupsUnordered.get(groupId)); } /** @@ -132,8 +132,8 @@ public void setGlobalOrder(List globalOrder) { // Propagate to each CitationGroup int i = 0; - for (CitationGroupId cgid : globalOrder) { - citationGroupsUnordered.get(cgid).setIndexInGlobalOrder(Optional.of(i)); + for (CitationGroupId groupId : globalOrder) { + citationGroupsUnordered.get(groupId).setIndexInGlobalOrder(Optional.of(i)); i++; } } @@ -146,8 +146,8 @@ public boolean hasGlobalOrder() { * Impose an order for citations within each group. */ public void imposeLocalOrder(Comparator entryComparator) { - for (CitationGroup cg : citationGroupsUnordered.values()) { - cg.imposeLocalOrder(entryComparator); + for (CitationGroup group : citationGroupsUnordered.values()) { + group.imposeLocalOrder(entryComparator); } } @@ -157,11 +157,11 @@ public void imposeLocalOrder(Comparator entryComparator) { */ public CitedKeys getCitedKeysUnordered() { LinkedHashMap res = new LinkedHashMap<>(); - for (CitationGroup cg : citationGroupsUnordered.values()) { + for (CitationGroup group : citationGroupsUnordered.values()) { int storageIndexInGroup = 0; - for (Citation cit : cg.citationsInStorageOrder) { + for (Citation cit : group.citationsInStorageOrder) { String key = cit.citationKey; - CitationPath path = new CitationPath(cg.cgid, storageIndexInGroup); + CitationPath path = new CitationPath(group.groupId, storageIndexInGroup); if (res.containsKey(key)) { res.get(key).addPath(path, cit); } else { @@ -181,11 +181,11 @@ public CitedKeys getCitedKeysSortedInOrderOfAppearance() { throw new IllegalStateException("getSortedCitedKeys: no globalOrder"); } LinkedHashMap res = new LinkedHashMap<>(); - for (CitationGroup cg : getCitationGroupsInGlobalOrder()) { - for (int i : cg.getLocalOrder()) { - Citation cit = cg.citationsInStorageOrder.get(i); + for (CitationGroup group : getCitationGroupsInGlobalOrder()) { + for (int i : group.getLocalOrder()) { + Citation cit = group.citationsInStorageOrder.get(i); String citationKey = cit.citationKey; - CitationPath path = new CitationPath(cg.cgid, i); + CitationPath path = new CitationPath(group.groupId, i); if (res.containsKey(citationKey)) { res.get(citationKey).addPath(path, cit); } else { @@ -257,17 +257,17 @@ public void createNumberedBibliographySortedByComparator(Comparator en * Query by CitationGroupId */ - public Optional getCitationGroup(CitationGroupId cgid) { - CitationGroup cg = citationGroupsUnordered.get(cgid); - return Optional.ofNullable(cg); + public Optional getCitationGroup(CitationGroupId groupId) { + CitationGroup group = citationGroupsUnordered.get(groupId); + return Optional.ofNullable(group); } /* * @return true if all citation groups have referenceMarkNameForLinking */ public boolean citationGroupsProvideReferenceMarkNameForLinking() { - for (CitationGroup cg : citationGroupsUnordered.values()) { - if (cg.getReferenceMarkNameForLinking().isEmpty()) { + for (CitationGroup group : citationGroupsUnordered.values()) { + if (group.getReferenceMarkNameForLinking().isEmpty()) { return false; } } @@ -278,16 +278,16 @@ public boolean citationGroupsProvideReferenceMarkNameForLinking() { * Callbacks. */ - public void afterCreateCitationGroup(CitationGroup cg) { - citationGroupsUnordered.put(cg.cgid, cg); + public void afterCreateCitationGroup(CitationGroup group) { + citationGroupsUnordered.put(group.groupId, group); globalOrder = Optional.empty(); bibliography = Optional.empty(); } - public void afterRemoveCitationGroup(CitationGroup cg) { - citationGroupsUnordered.remove(cg.cgid); - globalOrder.map(l -> l.remove(cg.cgid)); + public void afterRemoveCitationGroup(CitationGroup group) { + citationGroupsUnordered.remove(group.groupId); + globalOrder.map(l -> l.remove(group.groupId)); bibliography = Optional.empty(); } diff --git a/src/main/java/org/jabref/model/openoffice/style/CitationType.java b/src/main/java/org/jabref/model/openoffice/style/CitationType.java index 14189e903e3..567996ec56e 100644 --- a/src/main/java/org/jabref/model/openoffice/style/CitationType.java +++ b/src/main/java/org/jabref/model/openoffice/style/CitationType.java @@ -11,8 +11,8 @@ public enum CitationType { public boolean inParenthesis() { return switch (this) { - case AUTHORYEAR_PAR, INVISIBLE_CIT -> true; - case AUTHORYEAR_INTEXT -> false; + case AUTHORYEAR_PAR, INVISIBLE_CIT -> true; + case AUTHORYEAR_INTEXT -> false; }; } From bf1a990aa51187dcaa487b8d85ccb515b3ac0de2 Mon Sep 17 00:00:00 2001 From: Antal K Date: Mon, 23 Aug 2021 10:54:48 +0200 Subject: [PATCH 49/51] longer name --- .../org/jabref/gui/openoffice/ConnectionLostException.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/jabref/gui/openoffice/ConnectionLostException.java b/src/main/java/org/jabref/gui/openoffice/ConnectionLostException.java index 36fc16e5e3f..df0264be0de 100644 --- a/src/main/java/org/jabref/gui/openoffice/ConnectionLostException.java +++ b/src/main/java/org/jabref/gui/openoffice/ConnectionLostException.java @@ -5,7 +5,7 @@ */ class ConnectionLostException extends RuntimeException { - public ConnectionLostException(String s) { - super(s); + public ConnectionLostException(String msg) { + super(msg); } } From 2e803518e285d9a3b5512f332b98e05778a5057a Mon Sep 17 00:00:00 2001 From: Antal K Date: Mon, 23 Aug 2021 16:46:28 +0200 Subject: [PATCH 50/51] follow some PMD suggestions --- .../org/jabref/logic/openoffice/OOUtil.java | 15 +- .../logic/openoffice/action/EditInsert.java | 4 +- .../logic/openoffice/action/EditMerge.java | 20 +-- .../logic/openoffice/action/EditSeparate.java | 14 +- .../logic/openoffice/action/ExportCited.java | 4 +- .../openoffice/action/ManageCitations.java | 8 +- .../logic/openoffice/action/Update.java | 22 +-- .../logic/openoffice/backend/Backend52.java | 22 +-- .../logic/openoffice/backend/Codec52.java | 26 ++-- .../backend/NamedRangeReferenceMark.java | 27 ++-- .../frontend/UpdateBibliography.java | 8 +- .../frontend/UpdateCitationMarkers.java | 28 ++-- .../logic/openoffice/style/OOBibStyle.java | 140 +++++++++--------- .../style/OOBibStyleGetCitationMarker.java | 90 +++++------ .../style/OOBibStyleGetNumCitationMarker.java | 62 ++++---- .../style/OOFormatBibliography.java | 58 ++++---- .../style/OOProcessAuthorYearMarkers.java | 4 +- .../model/openoffice/CitationEntry.java | 8 +- .../openoffice/style/CitationGroupId.java | 8 +- .../openoffice/style/CitationGroups.java | 5 +- .../model/openoffice/style/CitedKeys.java | 6 +- .../model/openoffice/style/PageInfo.java | 2 +- .../model/openoffice/uno/UnoCursor.java | 4 +- .../jabref/model/openoffice/uno/UnoStyle.java | 4 +- .../jabref/model/openoffice/uno/UnoUndo.java | 12 +- .../uno/UnoUserDefinedProperty.java | 4 +- .../model/openoffice/util/OOListUtil.java | 16 +- 27 files changed, 306 insertions(+), 315 deletions(-) diff --git a/src/main/java/org/jabref/logic/openoffice/OOUtil.java b/src/main/java/org/jabref/logic/openoffice/OOUtil.java index 9efab82773c..a5976cb3342 100644 --- a/src/main/java/org/jabref/logic/openoffice/OOUtil.java +++ b/src/main/java/org/jabref/logic/openoffice/OOUtil.java @@ -127,13 +127,13 @@ public static void insertOOFormattedTextAtCurrentLocation(XText text, XTextCurso List formatting = new ArrayList<>(); // We need to extract formatting. Use a simple regexp search iteration: int piv = 0; - Matcher m = OOUtil.HTML_TAG.matcher(lText); - while (m.find()) { - String currentSubstring = lText.substring(piv, m.start()); + Matcher matcher = OOUtil.HTML_TAG.matcher(lText); + while (matcher.find()) { + String currentSubstring = lText.substring(piv, matcher.start()); if (!currentSubstring.isEmpty()) { OOUtil.insertTextAtCurrentLocation(text, cursor, currentSubstring, formatting); } - String tag = m.group(); + String tag = matcher.group(); // Handle tags: if ("".equals(tag)) { formatting.add(Formatting.BOLD); @@ -169,7 +169,7 @@ public static void insertOOFormattedTextAtCurrentLocation(XText text, XTextCurso formatting.remove(Formatting.STRIKEOUT); } - piv = m.end(); + piv = matcher.end(); } if (piv < lText.length()) { @@ -276,10 +276,9 @@ public static void insertTextAtCurrentLocation(XText text, XTextCursor cursor, S cursor.collapseToEnd(); } - public static Object getProperty(Object o, String property) + public static Object getProperty(Object object, String property) throws UnknownPropertyException, WrappedTargetException { - XPropertySet props = UnoRuntime.queryInterface( - XPropertySet.class, o); + XPropertySet props = UnoRuntime.queryInterface(XPropertySet.class, object); return props.getPropertyValue(property); } } diff --git a/src/main/java/org/jabref/logic/openoffice/action/EditInsert.java b/src/main/java/org/jabref/logic/openoffice/action/EditInsert.java index 81491bd8590..375d3c1637a 100644 --- a/src/main/java/org/jabref/logic/openoffice/action/EditInsert.java +++ b/src/main/java/org/jabref/logic/openoffice/action/EditInsert.java @@ -56,7 +56,7 @@ private static String insertEntryGetCitationKey(BibEntry entry) { * @param pageInfo A single pageInfo for a list of entries. This is what we get from the GUI. */ public static void insertCitationGroup(XTextDocument doc, - OOFrontend fr, + OOFrontend frontend, XTextCursor cursor, List entries, BibDatabase database, @@ -98,7 +98,7 @@ public static void insertCitationGroup(XTextDocument doc, try { UnoScreenRefresh.lockControllers(doc); - UpdateCitationMarkers.createAndFillCitationGroup(fr, + UpdateCitationMarkers.createAndFillCitationGroup(frontend, doc, citationKeys, pageInfos, diff --git a/src/main/java/org/jabref/logic/openoffice/action/EditMerge.java b/src/main/java/org/jabref/logic/openoffice/action/EditMerge.java index 43bdcd59e2d..6fa19e2d344 100644 --- a/src/main/java/org/jabref/logic/openoffice/action/EditMerge.java +++ b/src/main/java/org/jabref/logic/openoffice/action/EditMerge.java @@ -40,7 +40,7 @@ private EditMerge() { /* * @return true if modified document */ - public static boolean mergeCitationGroups(XTextDocument doc, OOFrontend fr, OOBibStyle style) + public static boolean mergeCitationGroups(XTextDocument doc, OOFrontend frontend, OOBibStyle style) throws CreationException, IllegalArgumentException, @@ -50,12 +50,12 @@ public static boolean mergeCitationGroups(XTextDocument doc, OOFrontend fr, OOBi PropertyVetoException, WrappedTargetException { - boolean madeModifications = false; + boolean madeModifications; try { UnoScreenRefresh.lockControllers(doc); - List joinableGroups = EditMerge.scan(doc, fr); + List joinableGroups = EditMerge.scan(doc, frontend); for (JoinableGroupData joinableGroupData : joinableGroups) { @@ -66,9 +66,9 @@ public static boolean mergeCitationGroups(XTextDocument doc, OOFrontend fr, OOBi .collect(Collectors.toList())); CitationType citationType = cgs.get(0).citationType; - List> pageInfos = fr.backend.combinePageInfos(cgs); + List> pageInfos = frontend.backend.combinePageInfos(cgs); - fr.removeCitationGroups(cgs, doc); + frontend.removeCitationGroups(cgs, doc); XTextCursor textCursor = joinableGroupData.groupCursor; textCursor.setString(""); // Also remove the spaces between. @@ -76,7 +76,7 @@ public static boolean mergeCitationGroups(XTextDocument doc, OOFrontend fr, OOBi /* insertSpaceAfter: no, it is already there (or could be) */ boolean insertSpaceAfter = false; - UpdateCitationMarkers.createAndFillCitationGroup(fr, + UpdateCitationMarkers.createAndFillCitationGroup(frontend, doc, citationKeys, pageInfos, @@ -226,7 +226,7 @@ private static boolean checkAddToGroup(ScanState state, CitationGroup group, XTe couldExpand = thisCharCursor.goRight((short) 1, true); String thisChar = thisCharCursor.getString(); thisCharCursor.collapseToEnd(); - if (thisChar.isEmpty() || thisChar.equals("\n") || !thisChar.trim().isEmpty()) { + if (thisChar.isEmpty() || "\n".equals(thisChar) || !thisChar.trim().isEmpty()) { couldExpand = false; if (!thisChar.isEmpty()) { thisCharCursor.goLeft((short) 1, false); @@ -290,13 +290,13 @@ private static void addToCurrentGroup(ScanState state, CitationGroup group, XTex /** * Scan the document for joinable groups. Return those found. */ - private static List scan(XTextDocument doc, OOFrontend fr) + private static List scan(XTextDocument doc, OOFrontend frontend) throws NoDocumentException, WrappedTargetException { List result = new ArrayList<>(); - List cgs = fr.getCitationGroupsSortedWithinPartitions(doc, false /* mapFootnotesToFootnoteMarks */); + List cgs = frontend.getCitationGroupsSortedWithinPartitions(doc, false /* mapFootnotesToFootnoteMarks */); if (cgs.isEmpty()) { return result; } @@ -305,7 +305,7 @@ private static List scan(XTextDocument doc, OOFrontend fr) for (CitationGroup group : cgs) { - XTextRange currentRange = (fr.getMarkRange(doc, group) + XTextRange currentRange = (frontend.getMarkRange(doc, group) .orElseThrow(IllegalStateException::new)); /* diff --git a/src/main/java/org/jabref/logic/openoffice/action/EditSeparate.java b/src/main/java/org/jabref/logic/openoffice/action/EditSeparate.java index fba50a93c6e..409969b213f 100644 --- a/src/main/java/org/jabref/logic/openoffice/action/EditSeparate.java +++ b/src/main/java/org/jabref/logic/openoffice/action/EditSeparate.java @@ -29,7 +29,7 @@ private EditSeparate() { } public static boolean separateCitations(XTextDocument doc, - OOFrontend fr, + OOFrontend frontend, List databases, OOBibStyle style) throws @@ -47,17 +47,17 @@ public static boolean separateCitations(XTextDocument doc, // decide the visually last Citation in the group. Unless the // style changed since refresh this is the last on the screen // as well. - fr.citationGroups.lookupCitations(databases); - fr.citationGroups.imposeLocalOrder(OOProcess.comparatorForMulticite(style)); + frontend.citationGroups.lookupCitations(databases); + frontend.citationGroups.imposeLocalOrder(OOProcess.comparatorForMulticite(style)); - List cgs = fr.citationGroups.getCitationGroupsUnordered(); + List cgs = frontend.citationGroups.getCitationGroupsUnordered(); try { UnoScreenRefresh.lockControllers(doc); for (CitationGroup group : cgs) { - XTextRange range1 = (fr + XTextRange range1 = (frontend .getMarkRange(doc, group) .orElseThrow(IllegalStateException::new)); XTextCursor textCursor = range1.getText().createTextCursorByRange(range1); @@ -67,7 +67,7 @@ public static boolean separateCitations(XTextDocument doc, continue; } - fr.removeCitationGroup(group, doc); + frontend.removeCitationGroup(group, doc); // Now we own the content of cits // Create a citation group for each citation. @@ -76,7 +76,7 @@ public static boolean separateCitations(XTextDocument doc, boolean insertSpaceAfter = (i != last); Citation cit = cits.get(i); - UpdateCitationMarkers.createAndFillCitationGroup(fr, + UpdateCitationMarkers.createAndFillCitationGroup(frontend, doc, List.of(cit.citationKey), List.of(cit.getPageInfo()), diff --git a/src/main/java/org/jabref/logic/openoffice/action/ExportCited.java b/src/main/java/org/jabref/logic/openoffice/action/ExportCited.java index 26ed654de21..ba806426051 100644 --- a/src/main/java/org/jabref/logic/openoffice/action/ExportCited.java +++ b/src/main/java/org/jabref/logic/openoffice/action/ExportCited.java @@ -51,8 +51,8 @@ public static GenerateDatabaseResult generateDatabase(XTextDocument doc, List unresolvedKeys = new ArrayList<>(); diff --git a/src/main/java/org/jabref/logic/openoffice/action/ManageCitations.java b/src/main/java/org/jabref/logic/openoffice/action/ManageCitations.java index 9782261f612..c8234df9ca0 100644 --- a/src/main/java/org/jabref/logic/openoffice/action/ManageCitations.java +++ b/src/main/java/org/jabref/logic/openoffice/action/ManageCitations.java @@ -21,8 +21,8 @@ public static List getCitationEntries(XTextDocument doc) throws NoDocumentException, WrappedTargetException { - OOFrontend fr = new OOFrontend(doc); - return fr.getCitationEntries(doc); + OOFrontend frontend = new OOFrontend(doc); + return frontend.getCitationEntries(doc); } public static void applyCitationEntries(XTextDocument doc, List citationEntries) @@ -32,7 +32,7 @@ public static void applyCitationEntries(XTextDocument doc, List c IllegalTypeException, WrappedTargetException, IllegalArgumentException { - OOFrontend fr = new OOFrontend(doc); - fr.applyCitationEntries(doc, citationEntries); + OOFrontend frontend = new OOFrontend(doc); + frontend.applyCitationEntries(doc, citationEntries); } } diff --git a/src/main/java/org/jabref/logic/openoffice/action/Update.java b/src/main/java/org/jabref/logic/openoffice/action/Update.java index 49f79d6d2e7..ba840e88b70 100644 --- a/src/main/java/org/jabref/logic/openoffice/action/Update.java +++ b/src/main/java/org/jabref/logic/openoffice/action/Update.java @@ -29,7 +29,7 @@ private Update() { * @return the list of unresolved citation keys */ private static List updateDocument(XTextDocument doc, - OOFrontend fr, + OOFrontend frontend, List databases, OOBibStyle style, FunctionalTextViewCursor fcursor, @@ -43,25 +43,25 @@ private static List updateDocument(XTextDocument doc, final boolean useLockControllers = true; - fr.imposeGlobalOrder(doc, fcursor); - OOProcess.produceCitationMarkers(fr.citationGroups, databases, style); + frontend.imposeGlobalOrder(doc, fcursor); + OOProcess.produceCitationMarkers(frontend.citationGroups, databases, style); try { if (useLockControllers) { UnoScreenRefresh.lockControllers(doc); } - UpdateCitationMarkers.applyNewCitationMarkers(doc, fr, style); + UpdateCitationMarkers.applyNewCitationMarkers(doc, frontend, style); if (doUpdateBibliography) { UpdateBibliography.rebuildBibTextSection(doc, - fr, - fr.citationGroups.getBibliography().get(), + frontend, + frontend.citationGroups.getBibliography().get(), style, alwaysAddCitedOnPages); } - return fr.citationGroups.getUnresolvedKeys(); + return frontend.citationGroups.getUnresolvedKeys(); } finally { if (useLockControllers && UnoScreenRefresh.hasControllersLocked(doc)) { UnoScreenRefresh.unlockControllers(doc); @@ -93,7 +93,7 @@ public SyncOptions setAlwaysAddCitedOnPages(boolean value) { } public static List synchronizeDocument(XTextDocument doc, - OOFrontend fr, + OOFrontend frontend, OOBibStyle style, FunctionalTextViewCursor fcursor, SyncOptions syncOptions) @@ -104,7 +104,7 @@ public static List synchronizeDocument(XTextDocument doc, com.sun.star.lang.IllegalArgumentException { return Update.updateDocument(doc, - fr, + frontend, syncOptions.databases, style, fcursor, @@ -125,9 +125,9 @@ public static List resyncDocument(XTextDocument doc, WrappedTargetException, com.sun.star.lang.IllegalArgumentException { - OOFrontend fr = new OOFrontend(doc); + OOFrontend frontend = new OOFrontend(doc); - return Update.synchronizeDocument(doc, fr, style, fcursor, syncOptions); + return Update.synchronizeDocument(doc, frontend, style, fcursor, syncOptions); } } diff --git a/src/main/java/org/jabref/logic/openoffice/backend/Backend52.java b/src/main/java/org/jabref/logic/openoffice/backend/Backend52.java index 0016e5e4510..4a0d8d05e0e 100644 --- a/src/main/java/org/jabref/logic/openoffice/backend/Backend52.java +++ b/src/main/java/org/jabref/logic/openoffice/backend/Backend52.java @@ -196,17 +196,6 @@ public CitationGroup createCitationGroup(XTextDocument doc, throw new IllegalArgumentException(); } - /* - * Backend52 uses reference marks to (1) mark the location of the citation in the text and (2) to encode - * the citation keys and citation type in the name of the reference mark. The name of the reference mark - * has to be unique in the document. - */ - String markName = Codec52.getUniqueMarkName(new HashSet<>(citationStorageManager.getUsedNames(doc)), - citationKeys, - citationType); - - CitationGroupId groupId = new CitationGroupId(markName); - final int numberOfCitations = citationKeys.size(); final int last = numberOfCitations - 1; @@ -236,6 +225,17 @@ public CitationGroup createCitationGroup(XTextDocument doc, } } + /* + * Backend52 uses reference marks to (1) mark the location of the citation in the text and (2) to encode + * the citation keys and citation type in the name of the reference mark. The name of the reference mark + * has to be unique in the document. + */ + final String markName = Codec52.getUniqueMarkName(new HashSet<>(citationStorageManager.getUsedNames(doc)), + citationKeys, + citationType); + + final CitationGroupId groupId = new CitationGroupId(markName); + /* * Apply to document */ diff --git a/src/main/java/org/jabref/logic/openoffice/backend/Codec52.java b/src/main/java/org/jabref/logic/openoffice/backend/Codec52.java index f567a3e459e..45f3bd3ca04 100644 --- a/src/main/java/org/jabref/logic/openoffice/backend/Codec52.java +++ b/src/main/java/org/jabref/logic/openoffice/backend/Codec52.java @@ -32,16 +32,16 @@ private Codec52() { */ public static class ParsedMarkName { /** "", "0", "1" ... */ - public final String i; + public final String index; /** in-text-citation type */ public final CitationType citationType; /** Citation keys embedded in the reference mark. */ public final List citationKeys; - ParsedMarkName(String i, CitationType citationType, List citationKeys) { - Objects.requireNonNull(i); + ParsedMarkName(String index, CitationType citationType, List citationKeys) { + Objects.requireNonNull(index); Objects.requireNonNull(citationKeys); - this.i = i; + this.index = index; this.citationType = citationType; this.citationKeys = citationKeys; } @@ -50,8 +50,8 @@ public static class ParsedMarkName { /** * Integer representation was written into the document in JabRef52, keep it for compatibility. */ - private static CitationType citationTypeFromInt(int i) { - return switch (i) { + private static CitationType citationTypeFromInt(int code) { + return switch (code) { case 1 -> CitationType.AUTHORYEAR_PAR; case 2 -> CitationType.AUTHORYEAR_INTEXT; case 3 -> CitationType.INVISIBLE_CIT; @@ -59,8 +59,8 @@ private static CitationType citationTypeFromInt(int i) { }; } - private static int citationTypeToInt(CitationType i) { - return switch (i) { + private static int citationTypeToInt(CitationType type) { + return switch (type) { case AUTHORYEAR_PAR -> 1; case AUTHORYEAR_INTEXT -> 2; case INVISIBLE_CIT -> 3; @@ -86,12 +86,12 @@ public static String getUniqueMarkName(Set usedNames, String citationKeysPart = String.join(",", citationKeys); - int i = 0; + int index = 0; int citTypeCode = citationTypeToInt(citationType); String name = BIB_CITATION + '_' + citTypeCode + '_' + citationKeysPart; while (usedNames.contains(name)) { - name = BIB_CITATION + i + '_' + citTypeCode + '_' + citationKeysPart; - i++; + name = BIB_CITATION + index + '_' + citTypeCode + '_' + citationKeysPart; + index++; } return name; } @@ -110,10 +110,10 @@ public static Optional parseMarkName(String refMarkName) { } List keys = Arrays.asList(citeMatcher.group(3).split(",")); - String i = citeMatcher.group(1); + String index = citeMatcher.group(1); int citTypeCode = Integer.parseInt(citeMatcher.group(2)); CitationType citationType = citationTypeFromInt(citTypeCode); - return (Optional.of(new Codec52.ParsedMarkName(i, citationType, keys))); + return (Optional.of(new Codec52.ParsedMarkName(index, citationType, keys))); } /** diff --git a/src/main/java/org/jabref/logic/openoffice/backend/NamedRangeReferenceMark.java b/src/main/java/org/jabref/logic/openoffice/backend/NamedRangeReferenceMark.java index cfabb1e2de0..324b984377c 100644 --- a/src/main/java/org/jabref/logic/openoffice/backend/NamedRangeReferenceMark.java +++ b/src/main/java/org/jabref/logic/openoffice/backend/NamedRangeReferenceMark.java @@ -35,14 +35,14 @@ class NamedRangeReferenceMark implements NamedRange { private static final Logger LOGGER = LoggerFactory.getLogger(NamedRangeReferenceMark.class); - private String id; /* reference mark name */ + private String rangeId; /* reference mark name */ - private NamedRangeReferenceMark(String id) { - this.id = id; + private NamedRangeReferenceMark(String rangeId) { + this.rangeId = rangeId; } String getId() { - return id; + return rangeId; } /** @@ -156,7 +156,7 @@ public void removeFromDocument(XTextDocument doc) @Override public String getRangeName() { - return id; + return rangeId; } /** @@ -217,8 +217,6 @@ public XTextCursor getFillCursor(XTextDocument doc) String name = this.getRangeName(); - final String left = NamedRangeReferenceMark.REFERENCE_MARK_LEFT_BRACKET; - final String right = NamedRangeReferenceMark.REFERENCE_MARK_RIGHT_BRACKET; final boolean debugThisFun = false; XTextCursor full = null; @@ -279,6 +277,8 @@ public XTextCursor getFillCursor(XTextDocument doc) beta.goRight((short) (fullText.length() - 2), true); LOGGER.debug("getFillCursor: beta(1) covers '{}'", beta.getString()); + final String left = NamedRangeReferenceMark.REFERENCE_MARK_LEFT_BRACKET; + final String right = NamedRangeReferenceMark.REFERENCE_MARK_RIGHT_BRACKET; final short rightLength = (short) right.length(); if (fullText.startsWith(left) && fullText.endsWith(right)) { beta.setString(""); @@ -345,7 +345,6 @@ public XTextCursor getFillCursor(XTextDocument doc) */ public static void checkFillCursor(XTextCursor cursor) { final String left = REFERENCE_MARK_LEFT_BRACKET; - final String right = REFERENCE_MARK_RIGHT_BRACKET; XTextCursor alpha = cursor.getText().createTextCursorByRange(cursor); alpha.collapseToStart(); @@ -364,6 +363,7 @@ public static void checkFillCursor(XTextCursor cursor) { } } + final String right = REFERENCE_MARK_RIGHT_BRACKET; final short rightLength = (short) right.length(); if (rightLength > 0) { omega.goRight(rightLength, true); @@ -395,27 +395,26 @@ public void cleanFillCursor(XTextDocument doc) // removeBracketsFromEmpty is intended to force removal if we are working on an "Empty citation" (INVISIBLE_CIT). final boolean removeBracketsFromEmpty = false; - final String left = REFERENCE_MARK_LEFT_BRACKET; - final String right = REFERENCE_MARK_RIGHT_BRACKET; - final short leftLength = (short) left.length(); - final short rightLength = (short) right.length(); - String name = this.getRangeName(); XTextCursor full = this.getRawCursor(doc).orElseThrow(IllegalStateException::new); final String fullText = full.getString(); - final int fullTextLength = fullText.length(); + final String left = REFERENCE_MARK_LEFT_BRACKET; if (!fullText.startsWith(left)) { String msg = String.format("cleanFillCursor: (%s) does not start with REFERENCE_MARK_LEFT_BRACKET", name); throw new IllegalStateException(msg); } + final String right = REFERENCE_MARK_RIGHT_BRACKET; if (!fullText.endsWith(right)) { String msg = String.format("cleanFillCursor: (%s) does not end with REFERENCE_MARK_RIGHT_BRACKET", name); throw new IllegalStateException(msg); } + final int fullTextLength = fullText.length(); + final short leftLength = (short) left.length(); + final short rightLength = (short) right.length(); final int contentLength = (fullTextLength - (leftLength + rightLength)); if (contentLength < 0) { String msg = String.format("cleanFillCursor: length(%s) < 0", name); diff --git a/src/main/java/org/jabref/logic/openoffice/frontend/UpdateBibliography.java b/src/main/java/org/jabref/logic/openoffice/frontend/UpdateBibliography.java index f7b3d14def6..ebda8359ba5 100644 --- a/src/main/java/org/jabref/logic/openoffice/frontend/UpdateBibliography.java +++ b/src/main/java/org/jabref/logic/openoffice/frontend/UpdateBibliography.java @@ -40,7 +40,7 @@ public static Optional getBibliographyRange(XTextDocument doc) * Rebuilds the bibliography. */ public static void rebuildBibTextSection(XTextDocument doc, - OOFrontend fr, + OOFrontend frontend, CitedKeys bibliography, OOBibStyle style, boolean alwaysAddCitedOnPages) @@ -52,7 +52,7 @@ public static void rebuildBibTextSection(XTextDocument doc, clearBibTextSectionContent2(doc); populateBibTextSection(doc, - fr, + frontend, bibliography, style, alwaysAddCitedOnPages); @@ -105,7 +105,7 @@ private static void clearBibTextSectionContent2(XTextDocument doc) * Assumes the section named BIB_SECTION_NAME exists. */ private static void populateBibTextSection(XTextDocument doc, - OOFrontend fr, + OOFrontend frontend, CitedKeys bibliography, OOBibStyle style, boolean alwaysAddCitedOnPages) @@ -121,7 +121,7 @@ private static void populateBibTextSection(XTextDocument doc, // emit the title of the bibliography OOTextIntoOO.removeDirectFormatting(cursor); - OOText bibliographyText = OOFormatBibliography.formatBibliography(fr.citationGroups, + OOText bibliographyText = OOFormatBibliography.formatBibliography(frontend.citationGroups, bibliography, style, alwaysAddCitedOnPages); diff --git a/src/main/java/org/jabref/logic/openoffice/frontend/UpdateCitationMarkers.java b/src/main/java/org/jabref/logic/openoffice/frontend/UpdateCitationMarkers.java index 55f04775094..834fce1d0ce 100644 --- a/src/main/java/org/jabref/logic/openoffice/frontend/UpdateCitationMarkers.java +++ b/src/main/java/org/jabref/logic/openoffice/frontend/UpdateCitationMarkers.java @@ -40,18 +40,18 @@ private UpdateCitationMarkers() { * After each fillCitationMarkInCursor call check if we lost the * BIB_SECTION_NAME bookmark and recreate it if we did. * - * @param fr + * @param frontend * * @param style Bibliography style to use. * */ - public static void applyNewCitationMarkers(XTextDocument doc, OOFrontend fr, OOBibStyle style) + public static void applyNewCitationMarkers(XTextDocument doc, OOFrontend frontend, OOBibStyle style) throws NoDocumentException, CreationException, WrappedTargetException { - CitationGroups citationGroups = fr.citationGroups; + CitationGroups citationGroups = frontend.citationGroups; for (CitationGroup group : citationGroups.getCitationGroupsUnordered()) { @@ -66,11 +66,11 @@ public static void applyNewCitationMarkers(XTextDocument doc, OOFrontend fr, OOB if (withText && marker.isPresent()) { - XTextCursor cursor = fr.getFillCursorForCitationGroup(doc, group); + XTextCursor cursor = frontend.getFillCursorForCitationGroup(doc, group); fillCitationMarkInCursor(doc, cursor, marker.get(), withText, style); - fr.cleanFillCursorForCitationGroup(doc, group); + frontend.cleanFillCursorForCitationGroup(doc, group); } } @@ -118,7 +118,7 @@ public static void fillCitationMarkInCursor(XTextDocument doc, * coming after. But is not wanted when we recreate a * reference mark. */ - public static void createAndFillCitationGroup(OOFrontend fr, + public static void createAndFillCitationGroup(OOFrontend frontend, XTextDocument doc, List citationKeys, List> pageInfos, @@ -140,21 +140,21 @@ public static void createAndFillCitationGroup(OOFrontend fr, if (pageInfos.size() != citationKeys.size()) { throw new IllegalArgumentException("pageInfos.size != citationKeys.size"); } - CitationGroup group = fr.createCitationGroup(doc, - citationKeys, - pageInfos, - citationType, - position, - insertSpaceAfter); + CitationGroup group = frontend.createCitationGroup(doc, + citationKeys, + pageInfos, + citationType, + position, + insertSpaceAfter); final boolean withText = citationType.withText(); if (withText) { - XTextCursor fillCursor = fr.getFillCursorForCitationGroup(doc, group); + XTextCursor fillCursor = frontend.getFillCursorForCitationGroup(doc, group); UpdateCitationMarkers.fillCitationMarkInCursor(doc, fillCursor, citationText, withText, style); - fr.cleanFillCursorForCitationGroup(doc, group); + frontend.cleanFillCursorForCitationGroup(doc, group); } position.collapseToEnd(); } diff --git a/src/main/java/org/jabref/logic/openoffice/style/OOBibStyle.java b/src/main/java/org/jabref/logic/openoffice/style/OOBibStyle.java index 082d94a7fdf..5108c94c8b3 100644 --- a/src/main/java/org/jabref/logic/openoffice/style/OOBibStyle.java +++ b/src/main/java/org/jabref/logic/openoffice/style/OOBibStyle.java @@ -291,20 +291,20 @@ private boolean isUpToDate() { } } - private void readFormatFile(Reader in) throws IOException { + private void readFormatFile(Reader input) throws IOException { // First read all the contents of the file: - StringBuilder sb = new StringBuilder(); - int c; - while ((c = in.read()) != -1) { - sb.append((char) c); + StringBuilder stringBuilder = new StringBuilder(); + int chr; + while ((chr = input.read()) != -1) { + stringBuilder.append((char) chr); } // Store a local copy for viewing - localCopy = sb.toString(); + localCopy = stringBuilder.toString(); // Break into separate lines: - String[] lines = sb.toString().split("\n"); + String[] lines = stringBuilder.toString().split("\n"); BibStyleMode mode = BibStyleMode.NONE; for (String line1 : lines) { @@ -386,11 +386,12 @@ private void handleStructureLine(String line) { if ((index > 0) && (index < (line.length() - 1))) { try { - String formatString = line.substring(index + 1); + final String typeName = line.substring(0, index); + final String formatString = line.substring(index + 1); Layout layout = new LayoutHelper(new StringReader(formatString), this.prefs).getLayoutFromText(); - EntryType type = EntryTypeFactory.parse(line.substring(0, index)); + EntryType type = EntryTypeFactory.parse(typeName); - if (!isDefaultLayoutPresent && line.substring(0, index).equals(OOBibStyle.DEFAULT_MARK)) { + if (!isDefaultLayoutPresent && OOBibStyle.DEFAULT_MARK.equals(typeName)) { isDefaultLayoutPresent = true; defaultBibLayout = layout; } else { @@ -437,11 +438,11 @@ private void handleJournalsLine(String line) { } public Layout getReferenceFormat(EntryType type) { - Layout l = bibLayout.get(type); - if (l == null) { + Layout layout = bibLayout.get(type); + if (layout == null) { return defaultBibLayout; } else { - return l; + return layout; } } @@ -464,7 +465,7 @@ public String getNumCitationMarker(List number, int minGroupingCount, b // Sort the numbers: List lNum = new ArrayList<>(number); Collections.sort(lNum); - StringBuilder sb = new StringBuilder(bracketBefore); + StringBuilder stringBuilder = new StringBuilder(bracketBefore); int combineFrom = -1; int written = 0; for (int i = 0; i < lNum.size(); i++) { @@ -476,9 +477,9 @@ public String getNumCitationMarker(List number, int minGroupingCount, b } else { // Add single entry: if (i > 0) { - sb.append(getStringCitProperty(CITATION_SEPARATOR)); + stringBuilder.append(getStringCitProperty(CITATION_SEPARATOR)); } - sb.append(lNum.get(i) > 0 ? String.valueOf(lNum.get(i)) : OOBibStyle.UNDEFINED_CITATION_MARKER); + stringBuilder.append(lNum.get(i) > 0 ? String.valueOf(lNum.get(i)) : OOBibStyle.UNDEFINED_CITATION_MARKER); written++; } } else { @@ -486,20 +487,20 @@ public String getNumCitationMarker(List number, int minGroupingCount, b // Check if it ends here: if ((i == (lNum.size() - 1)) || (lNum.get(i + 1) != (i1 + 1))) { if (written > 0) { - sb.append(getStringCitProperty(CITATION_SEPARATOR)); + stringBuilder.append(getStringCitProperty(CITATION_SEPARATOR)); } if ((minGroupingCount > 0) && (((i1 + 1) - combineFrom) >= minGroupingCount)) { - sb.append(combineFrom); - sb.append(getStringCitProperty(GROUPED_NUMBERS_SEPARATOR)); - sb.append(i1); + stringBuilder.append(combineFrom); + stringBuilder.append(getStringCitProperty(GROUPED_NUMBERS_SEPARATOR)); + stringBuilder.append(i1); written++; } else { // Either we should never group, or there aren't enough // entries in this case to group. Output all: for (int jj = combineFrom; jj <= i1; jj++) { - sb.append(jj); + stringBuilder.append(jj); if (jj < i1) { - sb.append(getStringCitProperty(CITATION_SEPARATOR)); + stringBuilder.append(getStringCitProperty(CITATION_SEPARATOR)); } written++; } @@ -509,8 +510,8 @@ public String getNumCitationMarker(List number, int minGroupingCount, b // If it doesn't end here, just keep iterating. } } - sb.append(bracketAfter); - return sb.toString(); + stringBuilder.append(bracketAfter); + return stringBuilder.toString(); } /* end_old */ @@ -591,13 +592,13 @@ public String getCitationMarker(List entries, Map entries, String[] uniquefiers, int from, int to) { String separator = getStringCitProperty(UNIQUEFIER_SEPARATOR); - StringBuilder sb = new StringBuilder(uniquefiers[from]); + StringBuilder stringBuilder = new StringBuilder(uniquefiers[from]); for (int i = from + 1; i <= to; i++) { - sb.append(separator); - sb.append(uniquefiers[i]); + stringBuilder.append(separator); + stringBuilder.append(uniquefiers[i]); entries.set(i, null); } - uniquefiers[from] = sb.toString(); + uniquefiers[from] = stringBuilder.toString(); } /* end_old */ @@ -622,7 +623,7 @@ private String getAuthorYearParenthesisMarker(List entries, Map entries, Map 0) { - sb.append(citationSeparator); + stringBuilder.append(citationSeparator); } BibDatabase currentDatabase = database.get(currentEntry); @@ -641,17 +642,17 @@ private String getAuthorYearParenthesisMarker(List entries, Map entries, Map entries, Map 0 ? unlimA : maxA; if (i > 0) { - sb.append(citationSeparator); + stringBuilder.append(citationSeparator); } String author = getCitationMarkerField(currentEntry, currentDatabase, authorField); String authorString = createAuthorList(author, maxAuthors, andString, yearSep); - sb.append(authorString); - sb.append(startBrace); + stringBuilder.append(authorString); + stringBuilder.append(startBrace); String year = getCitationMarkerField(currentEntry, currentDatabase, yearField); if (year != null) { - sb.append(year); + stringBuilder.append(year); } if ((uniquefiers != null) && (uniquefiers[i] != null)) { - sb.append(uniquefiers[i]); + stringBuilder.append(uniquefiers[i]); } - sb.append(endBrace); + stringBuilder.append(endBrace); } - return sb.toString(); + return stringBuilder.toString(); } /* end_old */ @@ -757,15 +758,15 @@ private String getCitationMarkerField(BibEntry entry, BibDatabase database, Stri * @return The author name, or an empty String if inapplicable. */ private String getAuthorLastName(AuthorList al, int number) { - StringBuilder sb = new StringBuilder(); + StringBuilder stringBuilder = new StringBuilder(); if (al.getNumberOfAuthors() > number) { Author a = al.getAuthor(number); - a.getVon().filter(von -> !von.isEmpty()).ifPresent(von -> sb.append(von).append(' ')); - sb.append(a.getLast().orElse("")); + a.getVon().filter(von -> !von.isEmpty()).ifPresent(von -> stringBuilder.append(von).append(' ')); + stringBuilder.append(a.getLast().orElse("")); } - return sb.toString(); + return stringBuilder.toString(); } /* end_old */ @@ -896,12 +897,12 @@ public int compareTo(OOBibStyle other) { } @Override - public boolean equals(Object o) { - if (this == o) { + public boolean equals(Object object) { + if (this == object) { return true; } - if (o instanceof OOBibStyle) { - OOBibStyle otherStyle = (OOBibStyle) o; + if (object instanceof OOBibStyle) { + OOBibStyle otherStyle = (OOBibStyle) object; return Objects.equals(path, otherStyle.path) && Objects.equals(name, otherStyle.name) && Objects.equals(citProperties, otherStyle.citProperties) @@ -923,28 +924,28 @@ private String createAuthorList(String author, int maxAuthors, String andString, String etAlString = getStringCitProperty(ET_AL_STRING); // The String to represent authors that are not mentioned, e.g. " et al." String authorSep = getStringCitProperty(AUTHOR_SEPARATOR); // The String to add between author names except the last two, e.g. ", ". String oxfordComma = getStringCitProperty(OXFORD_COMMA); // The String to put after the second to last author in case of three or more authors - StringBuilder sb = new StringBuilder(); + StringBuilder stringBuilder = new StringBuilder(); AuthorList al = AuthorList.parse(author); if (!al.isEmpty()) { - sb.append(getAuthorLastName(al, 0)); + stringBuilder.append(getAuthorLastName(al, 0)); } if ((al.getNumberOfAuthors() > 1) && ((al.getNumberOfAuthors() <= maxAuthors) || (maxAuthors < 0))) { int j = 1; while (j < (al.getNumberOfAuthors() - 1)) { - sb.append(authorSep); - sb.append(getAuthorLastName(al, j)); + stringBuilder.append(authorSep); + stringBuilder.append(getAuthorLastName(al, j)); j++; } if (al.getNumberOfAuthors() > 2) { - sb.append(oxfordComma); + stringBuilder.append(oxfordComma); } - sb.append(andString); - sb.append(getAuthorLastName(al, al.getNumberOfAuthors() - 1)); + stringBuilder.append(andString); + stringBuilder.append(getAuthorLastName(al, al.getNumberOfAuthors() - 1)); } else if (al.getNumberOfAuthors() > maxAuthors) { - sb.append(etAlString); + stringBuilder.append(etAlString); } - sb.append(yearSep); - return sb.toString(); + stringBuilder.append(yearSep); + return stringBuilder.toString(); } /* end_old */ @@ -1078,8 +1079,8 @@ public OOText getNumCitationMarkerForBibliography(CitationMarkerNumericBibEntry return OOBibStyleGetNumCitationMarker.getNumCitationMarkerForBibliography(this, entry); } - public OOText getNormalizedCitationMarker(CitationMarkerNormEntry ce) { - return OOBibStyleGetCitationMarker.getNormalizedCitationMarker(this, ce, Optional.empty()); + public OOText getNormalizedCitationMarker(CitationMarkerNormEntry entry) { + return OOBibStyleGetCitationMarker.getNormalizedCitationMarker(this, entry, Optional.empty()); } /** @@ -1158,10 +1159,6 @@ public String getGroupedNumbersSeparator() { return getStringCitProperty(OOBibStyle.GROUPED_NUMBERS_SEPARATOR); } - private boolean getBooleanProperty(String propName) { - return (Boolean) properties.get(propName); - } - private String getStringProperty(String propName) { return (String) properties.get(propName); } @@ -1308,12 +1305,9 @@ public OOText getFormattedBibliographyTitle() { OOBibStyle style = this; OOText title = style.getReferenceHeaderText(); String parStyle = style.getReferenceHeaderParagraphFormat(); - if (parStyle != null) { - title = OOFormat.paragraph(title, parStyle); - } else { - title = OOFormat.paragraph(title); - } - return title; + return (parStyle == null + ? OOFormat.paragraph(title) + : OOFormat.paragraph(title, parStyle)); } } diff --git a/src/main/java/org/jabref/logic/openoffice/style/OOBibStyleGetCitationMarker.java b/src/main/java/org/jabref/logic/openoffice/style/OOBibStyleGetCitationMarker.java index deb1a306913..2e5227d080e 100644 --- a/src/main/java/org/jabref/logic/openoffice/style/OOBibStyleGetCitationMarker.java +++ b/src/main/java/org/jabref/logic/openoffice/style/OOBibStyleGetCitationMarker.java @@ -38,21 +38,21 @@ private OOBibStyleGetCitationMarker() { * @return The author name, or an empty String if inapplicable. */ private static String getAuthorLastName(AuthorList authorList, int number) { - StringBuilder sb = new StringBuilder(); + StringBuilder stringBuilder = new StringBuilder(); if (authorList.getNumberOfAuthors() > number) { Author author = authorList.getAuthor(number); // "von " if von exists Optional von = author.getVon(); if (von.isPresent() && !von.get().isEmpty()) { - sb.append(von.get()); - sb.append(' '); + stringBuilder.append(von.get()); + stringBuilder.append(' '); } // last name if it exists - sb.append(author.getLast().orElse("")); + stringBuilder.append(author.getLast().orElse("")); } - return sb.toString(); + return stringBuilder.toString(); } private static String markupAuthorName(OOBibStyle style, String name) { @@ -127,7 +127,7 @@ private static String formatAuthorList(OOBibStyle style, // of three or more authors: (A, B[,] and C) String oxfordComma = style.getOxfordComma(); - StringBuilder sb = new StringBuilder(); + StringBuilder stringBuilder = new StringBuilder(); final int nAuthors = authorList.getNumberOfAuthors(); @@ -149,11 +149,11 @@ private static String formatAuthorList(OOBibStyle style, : Math.min(maxAuthorsBeforeEtAl, nAuthors)); if (nAuthorsToEmit >= 1) { - sb.append(style.getAuthorsPartMarkupBefore()); - sb.append(style.getAuthorNamesListMarkupBefore()); + stringBuilder.append(style.getAuthorsPartMarkupBefore()); + stringBuilder.append(style.getAuthorNamesListMarkupBefore()); // The first author String name = getAuthorLastName(authorList, 0); - sb.append(markupAuthorName(style, name)); + stringBuilder.append(markupAuthorName(style, name)); } if (nAuthors >= 2) { @@ -162,19 +162,19 @@ private static String formatAuthorList(OOBibStyle style, // Emit last names, except for the last author int j = 1; while (j < (nAuthors - 1)) { - sb.append(authorSep); + stringBuilder.append(authorSep); String name = getAuthorLastName(authorList, j); - sb.append(markupAuthorName(style, name)); + stringBuilder.append(markupAuthorName(style, name)); j++; } // oxfordComma if at least 3 authors if (nAuthors >= 3) { - sb.append(oxfordComma); + stringBuilder.append(oxfordComma); } // Emit " and "+"LastAuthor" - sb.append(andString); + stringBuilder.append(andString); String name = getAuthorLastName(authorList, nAuthors - 1); - sb.append(markupAuthorName(style, name)); + stringBuilder.append(markupAuthorName(style, name)); } else { // Emit last names up to nAuthorsToEmit. @@ -185,9 +185,9 @@ private static String formatAuthorList(OOBibStyle style, if (maxAuthorsBeforeEtAl > 1) { int j = 1; while (j < nAuthorsToEmit) { - sb.append(authorSep); + stringBuilder.append(authorSep); String name = getAuthorLastName(authorList, j); - sb.append(markupAuthorName(style, name)); + stringBuilder.append(markupAuthorName(style, name)); j++; } } @@ -195,15 +195,15 @@ private static String formatAuthorList(OOBibStyle style, } if (nAuthorsToEmit >= 1) { - sb.append(style.getAuthorNamesListMarkupAfter()); + stringBuilder.append(style.getAuthorNamesListMarkupAfter()); } if (nAuthors >= 2 && !emitAllAuthors) { - sb.append(etAlString); + stringBuilder.append(etAlString); } - sb.append(style.getAuthorsPartMarkupAfter()); - return sb.toString(); + stringBuilder.append(style.getAuthorsPartMarkupAfter()); + return stringBuilder.toString(); } /** @@ -279,15 +279,15 @@ private static String getCitationMarkerField(OOBibStyle style, return ""; } - FieldAndContent fc = optionalFieldAndContent.get(); - String result = style.getFieldFormatter().format(fc.content); + FieldAndContent fieldAndContent = optionalFieldAndContent.get(); + String result = style.getFieldFormatter().format(fieldAndContent.content); // If the field we found is mentioned in authorFieldNames and // content has a pair of braces around it, we add a pair of // braces around the result, so that AuthorList.parse does not split // the content. final OrFields fieldsToRebrace = style.getAuthorFieldNames(); - if (fieldsToRebrace.contains(fc.field) && StringUtil.isInCurlyBrackets(fc.content)) { + if (fieldsToRebrace.contains(fieldAndContent.field) && StringUtil.isInCurlyBrackets(fieldAndContent.content)) { result = "{" + result + "}"; } return result; @@ -406,11 +406,11 @@ private static OOText getAuthorYearParenthesisMarker2(OOBibStyle style, String pageInfoSeparator = style.getPageInfoSeparator(); String uniquefierSeparator = style.getUniquefierSeparator(); - StringBuilder sb = new StringBuilder(); - sb.append(style.getCitationGroupMarkupBefore()); + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append(style.getCitationGroupMarkupBefore()); if (inParenthesis) { - sb.append(startBrace); // shared parenthesis + stringBuilder.append(startBrace); // shared parenthesis } for (int j = 0; j < entries.size(); j++) { @@ -422,19 +422,19 @@ private static OOText getAuthorYearParenthesisMarker2(OOBibStyle style, // Just add our uniqueLetter String uniqueLetter = entry.getUniqueLetter().orElse(null); if (uniqueLetter != null) { - sb.append(uniquefierSeparator); - sb.append(uniqueLetter); + stringBuilder.append(uniquefierSeparator); + stringBuilder.append(uniqueLetter); } // And close the brace, if we are the last in the group. if (!inParenthesis && endingAGroup) { - sb.append(endBrace); + stringBuilder.append(endBrace); } continue; } if (j > 0) { - sb.append(citationSeparator); + stringBuilder.append(citationSeparator); } StringBuilder pageInfoPart = new StringBuilder(""); @@ -449,9 +449,9 @@ private static OOText getAuthorYearParenthesisMarker2(OOBibStyle style, final boolean isUnresolved = entry.getLookupResult().isEmpty(); if (isUnresolved) { - sb.append(String.format("Unresolved(%s)", entry.getCitationKey())); + stringBuilder.append(String.format("Unresolved(%s)", entry.getCitationKey())); if (purpose != AuthorYearMarkerPurpose.NORMALIZED) { - sb.append(pageInfoPart); + stringBuilder.append(pageInfoPart); } } else { @@ -467,40 +467,40 @@ private static OOText getAuthorYearParenthesisMarker2(OOBibStyle style, AuthorList authorList = getAuthorList(style, db); String authorString = formatAuthorList(style, authorList, maxAuthors, andString); - sb.append(authorString); - sb.append(yearSep); + stringBuilder.append(authorString); + stringBuilder.append(yearSep); if (!inParenthesis) { - sb.append(startBrace); // parenthesis before year + stringBuilder.append(startBrace); // parenthesis before year } String year = getCitationMarkerField(style, db, yearFieldNames); if (year != null) { - sb.append(year); + stringBuilder.append(year); } if (purpose != AuthorYearMarkerPurpose.NORMALIZED) { String uniqueLetter = entry.getUniqueLetter().orElse(null); if (uniqueLetter != null) { - sb.append(uniqueLetter); + stringBuilder.append(uniqueLetter); } } if (purpose != AuthorYearMarkerPurpose.NORMALIZED) { - sb.append(pageInfoPart); + stringBuilder.append(pageInfoPart); } if (!inParenthesis && endingAGroup) { - sb.append(endBrace); // parenthesis after year + stringBuilder.append(endBrace); // parenthesis after year } } } // for j if (inParenthesis) { - sb.append(endBrace); // shared parenthesis + stringBuilder.append(endBrace); // shared parenthesis } - sb.append(style.getCitationGroupMarkupAfter()); - return OOText.fromString(sb.toString()); + stringBuilder.append(style.getCitationGroupMarkupAfter()); + return OOText.fromString(stringBuilder.toString()); } /** @@ -638,9 +638,9 @@ static OOText getNormalizedCitationMarker(OOBibStyle style, int[] nAuthorsToEmitRevised = new int[nEntries]; for (int i = 0; i < nEntries; i++) { CitationMarkerEntry entry = citationMarkerEntries.get(i); - int n = calculateNAuthorsToEmit(style, entry); - nAuthorsToEmit[i] = n; - nAuthorsToEmitRevised[i] = n; + int nAuthors = calculateNAuthorsToEmit(style, entry); + nAuthorsToEmit[i] = nAuthors; + nAuthorsToEmitRevised[i] = nAuthors; } boolean[] startsNewGroup = new boolean[nEntries]; diff --git a/src/main/java/org/jabref/logic/openoffice/style/OOBibStyleGetNumCitationMarker.java b/src/main/java/org/jabref/logic/openoffice/style/OOBibStyleGetNumCitationMarker.java index 5dce4e8caa1..60b5222cf5b 100644 --- a/src/main/java/org/jabref/logic/openoffice/style/OOBibStyleGetNumCitationMarker.java +++ b/src/main/java/org/jabref/logic/openoffice/style/OOBibStyleGetNumCitationMarker.java @@ -53,23 +53,23 @@ public static OOText getNumCitationMarkerForBibliography(OOBibStyle style, // prefer BRACKET_BEFORE_IN_LIST and BRACKET_AFTER_IN_LIST String bracketBefore = style.getBracketBeforeInListWithFallBack(); String bracketAfter = style.getBracketAfterInListWithFallBack(); - StringBuilder sb = new StringBuilder(); - sb.append(style.getCitationGroupMarkupBefore()); - sb.append(bracketBefore); + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append(style.getCitationGroupMarkupBefore()); + stringBuilder.append(bracketBefore); final Optional current = entry.getNumber(); - sb.append(current.isPresent() + stringBuilder.append(current.isPresent() ? String.valueOf(current.get()) : (OOBibStyle.UNDEFINED_CITATION_MARKER + entry.getCitationKey())); - sb.append(bracketAfter); - sb.append(style.getCitationGroupMarkupAfter()); - return OOText.fromString(sb.toString()); + stringBuilder.append(bracketAfter); + stringBuilder.append(style.getCitationGroupMarkupAfter()); + return OOText.fromString(stringBuilder.toString()); } /* * emitBlock : a helper for getNumCitationMarker2 * * Given a block containing either a single entry or two or more - * entries that are joinable into an "i-j" form, append to {@code sb} the + * entries that are joinable into an "i-j" form, append to {@code stringBuilder} the * formatted text. * * Assumes: @@ -91,7 +91,7 @@ public static OOText getNumCitationMarkerForBibliography(OOBibStyle style, private static void emitBlock(List block, OOBibStyle style, int minGroupingCount, - StringBuilder sb) { + StringBuilder stringBuilder) { final int blockSize = block.size(); if (blockSize == 0) { @@ -102,14 +102,14 @@ private static void emitBlock(List block, // Add single entry: CitationMarkerNumericEntry entry = block.get(0); final Optional num = entry.getNumber(); - sb.append(num.isEmpty() - ? (OOBibStyle.UNDEFINED_CITATION_MARKER + entry.getCitationKey()) - : String.valueOf(num.get())); + stringBuilder.append(num.isEmpty() + ? (OOBibStyle.UNDEFINED_CITATION_MARKER + entry.getCitationKey()) + : String.valueOf(num.get())); // Emit pageInfo Optional pageInfo = entry.getPageInfo(); if (pageInfo.isPresent()) { - sb.append(style.getPageInfoSeparator()); - sb.append(OOText.toString(pageInfo.get())); + stringBuilder.append(style.getPageInfoSeparator()); + stringBuilder.append(OOText.toString(pageInfo.get())); } return; } @@ -146,17 +146,17 @@ private static void emitBlock(List block, } // Emit: "first-last" - sb.append(first); - sb.append(style.getGroupedNumbersSeparator()); - sb.append(last); + stringBuilder.append(first); + stringBuilder.append(style.getGroupedNumbersSeparator()); + stringBuilder.append(last); } else { // Emit: first, first+1,..., last for (int j = 0; j < blockSize; j++) { if (j > 0) { - sb.append(style.getCitationSeparator()); + stringBuilder.append(style.getCitationSeparator()); } - sb.append(block.get(j).getNumber().get()); + stringBuilder.append(block.get(j).getNumber().get()); } } return; @@ -194,15 +194,15 @@ public static OOText getNumCitationMarker2(OOBibStyle style, final boolean joinIsDisabled = (minGroupingCount <= 0); final int nCitations = entries.size(); - String bracketBefore = style.getBracketBefore(); - String bracketAfter = style.getBracketAfter(); + final String bracketBefore = style.getBracketBefore(); + final String bracketAfter = style.getBracketAfter(); // Sort a copy of entries List sorted = OOListUtil.map(entries, e -> e); sorted.sort(OOBibStyleGetNumCitationMarker::compareCitationMarkerNumericEntry); // "[" - StringBuilder sb = new StringBuilder(bracketBefore); + StringBuilder stringBuilder = new StringBuilder(bracketBefore); /* * Original: @@ -248,12 +248,12 @@ public static OOText getNumCitationMarker2(OOBibStyle style, } } - if (nextBlock.size() > 0) { + if (!nextBlock.isEmpty()) { // emit current block if (blocksEmitted) { - sb.append(style.getCitationSeparator()); + stringBuilder.append(style.getCitationSeparator()); } - emitBlock(currentBlock, style, minGroupingCount, sb); + emitBlock(currentBlock, style, minGroupingCount, stringBuilder); blocksEmitted = true; currentBlock = nextBlock; nextBlock = new ArrayList<>(); @@ -261,21 +261,21 @@ public static OOText getNumCitationMarker2(OOBibStyle style, } - if (nextBlock.size() != 0) { + if (!nextBlock.isEmpty()) { throw new IllegalStateException("impossible: (nextBlock.size() != 0) after loop"); } - if (currentBlock.size() > 0) { + if (!currentBlock.isEmpty()) { // We are emitting a block if (blocksEmitted) { - sb.append(style.getCitationSeparator()); + stringBuilder.append(style.getCitationSeparator()); } - emitBlock(currentBlock, style, minGroupingCount, sb); + emitBlock(currentBlock, style, minGroupingCount, stringBuilder); } // Emit: "]" - sb.append(bracketAfter); - return OOText.fromString(sb.toString()); + stringBuilder.append(bracketAfter); + return OOText.fromString(stringBuilder.toString()); } } diff --git a/src/main/java/org/jabref/logic/openoffice/style/OOFormatBibliography.java b/src/main/java/org/jabref/logic/openoffice/style/OOFormatBibliography.java index 4bc3fac6191..ea311193068 100644 --- a/src/main/java/org/jabref/logic/openoffice/style/OOFormatBibliography.java +++ b/src/main/java/org/jabref/logic/openoffice/style/OOFormatBibliography.java @@ -49,8 +49,8 @@ public static OOText formatBibliographyBody(CitationGroups cgs, StringBuilder stringBuilder = new StringBuilder(); - for (CitedKey ck : bibliography.values()) { - OOText entryText = formatBibliographyEntry(cgs, ck, style, alwaysAddCitedOnPages); + for (CitedKey citedKey : bibliography.values()) { + OOText entryText = formatBibliographyEntry(cgs, citedKey, style, alwaysAddCitedOnPages); stringBuilder.append(entryText.toString()); } @@ -61,29 +61,29 @@ public static OOText formatBibliographyBody(CitationGroups cgs, * @return A paragraph. Includes label and "Cited on pages". */ public static OOText formatBibliographyEntry(CitationGroups cgs, - CitedKey ck, + CitedKey citedKey, OOBibStyle style, boolean alwaysAddCitedOnPages) { - StringBuilder sb = new StringBuilder(); + StringBuilder stringBuilder = new StringBuilder(); // insert marker "[1]" if (style.isNumberEntries()) { - sb.append(style.getNumCitationMarkerForBibliography(ck).toString()); + stringBuilder.append(style.getNumCitationMarkerForBibliography(citedKey).toString()); } else { // !style.isNumberEntries() : emit no prefix // Note: We might want [citationKey] prefix for style.isCitationKeyCiteMarkers(); } // Add entry body - sb.append(formatBibliographyEntryBody(ck, style).toString()); + stringBuilder.append(formatBibliographyEntryBody(citedKey, style).toString()); // Add "Cited on pages" - if (ck.getLookupResult().isEmpty() || alwaysAddCitedOnPages) { - sb.append(formatCitedOnPages(cgs, ck).toString()); + if (citedKey.getLookupResult().isEmpty() || alwaysAddCitedOnPages) { + stringBuilder.append(formatCitedOnPages(cgs, citedKey).toString()); } // Add paragraph - OOText entryText = OOText.fromString(sb.toString()); + OOText entryText = OOText.fromString(stringBuilder.toString()); String parStyle = style.getReferenceParagraphFormat(); return OOFormat.paragraph(entryText, parStyle); } @@ -91,20 +91,20 @@ public static OOText formatBibliographyEntry(CitationGroups cgs, /** * @return just the body of a bibliography entry. No label, "Cited on pages" or paragraph. */ - public static OOText formatBibliographyEntryBody(CitedKey ck, OOBibStyle style) { - if (ck.getLookupResult().isEmpty()) { + public static OOText formatBibliographyEntryBody(CitedKey citedKey, OOBibStyle style) { + if (citedKey.getLookupResult().isEmpty()) { // Unresolved entry - return OOText.fromString(String.format("Unresolved(%s)", ck.citationKey)); + return OOText.fromString(String.format("Unresolved(%s)", citedKey.citationKey)); } else { // Resolved entry, use the layout engine - BibEntry bibentry = ck.getLookupResult().get().entry; + BibEntry bibentry = citedKey.getLookupResult().get().entry; Layout layout = style.getReferenceFormat(bibentry.getType()); layout.setPostFormatter(POSTFORMATTER); return formatFullReferenceOfBibEntry(layout, bibentry, - ck.getLookupResult().get().database, - ck.getUniqueLetter().orElse(null)); + citedKey.getLookupResult().get().database, + citedKey.getUniqueLetter().orElse(null)); } } @@ -147,27 +147,27 @@ private static OOText formatFullReferenceOfBibEntry(Layout layout, } /** - * Format links to citations of the source (ck). + * Format links to citations of the source (citedKey). * * Requires reference marks for the citation groups. * * - The links are created as references that show page numbers of the reference marks. * - We do not control the text shown, that is provided by OpenOffice. */ - private static OOText formatCitedOnPages(CitationGroups cgs, CitedKey ck) { + private static OOText formatCitedOnPages(CitationGroups cgs, CitedKey citedKey) { if (!cgs.citationGroupsProvideReferenceMarkNameForLinking()) { return OOText.fromString(""); } - StringBuilder sb = new StringBuilder(); + StringBuilder stringBuilder = new StringBuilder(); - String prefix = String.format(" (%s: ", Localization.lang("Cited on pages")); - String suffix = ")"; - sb.append(prefix); + final String prefix = String.format(" (%s: ", Localization.lang("Cited on pages")); + final String suffix = ")"; + stringBuilder.append(prefix); List citationGroups = new ArrayList<>(); - for (CitationPath p : ck.getCitationPaths()) { + for (CitationPath p : citedKey.getCitationPaths()) { CitationGroupId groupId = p.group; Optional group = cgs.getCitationGroup(groupId); if (group.isEmpty()) { @@ -183,18 +183,18 @@ private static OOText formatCitedOnPages(CitationGroups cgs, CitedKey ck) { return (aa.compareTo(bb)); }); - int i = 0; + int index = 0; for (CitationGroup group : citationGroups) { - if (i > 0) { - sb.append(", "); + if (index > 0) { + stringBuilder.append(", "); } String markName = group.getReferenceMarkNameForLinking().orElseThrow(IllegalStateException::new); OOText xref = OOFormat.formatReferenceToPageNumberOfReferenceMark(markName); - sb.append(xref.toString()); - i++; + stringBuilder.append(xref.toString()); + index++; } - sb.append(suffix); - return OOText.fromString(sb.toString()); + stringBuilder.append(suffix); + return OOText.fromString(stringBuilder.toString()); } } diff --git a/src/main/java/org/jabref/logic/openoffice/style/OOProcessAuthorYearMarkers.java b/src/main/java/org/jabref/logic/openoffice/style/OOProcessAuthorYearMarkers.java index 1aaeb538930..affbc7584ae 100644 --- a/src/main/java/org/jabref/logic/openoffice/style/OOProcessAuthorYearMarkers.java +++ b/src/main/java/org/jabref/logic/openoffice/style/OOProcessAuthorYearMarkers.java @@ -88,8 +88,8 @@ private static void createUniqueLetters(CitedKeys sortedCitedKeys, CitationGroup // according to their order in clashingKeys. int nextUniqueLetter = 'a'; for (String citationKey : clashingKeys) { - String ul = String.valueOf((char) nextUniqueLetter); - sortedCitedKeys.get(citationKey).setUniqueLetter(Optional.of(ul)); + String uniqueLetter = String.valueOf((char) nextUniqueLetter); + sortedCitedKeys.get(citationKey).setUniqueLetter(Optional.of(uniqueLetter)); nextUniqueLetter++; } } diff --git a/src/main/java/org/jabref/model/openoffice/CitationEntry.java b/src/main/java/org/jabref/model/openoffice/CitationEntry.java index 219e71eb0d1..0d702c860ab 100644 --- a/src/main/java/org/jabref/model/openoffice/CitationEntry.java +++ b/src/main/java/org/jabref/model/openoffice/CitationEntry.java @@ -37,12 +37,12 @@ public int compareTo(CitationEntry other) { } @Override - public boolean equals(Object o) { - if (this == o) { + public boolean equals(Object object) { + if (this == object) { return true; } - if (o instanceof CitationEntry) { - CitationEntry other = (CitationEntry) o; + if (object instanceof CitationEntry) { + CitationEntry other = (CitationEntry) object; return Objects.equals(this.refMarkName, other.refMarkName); } return false; diff --git a/src/main/java/org/jabref/model/openoffice/style/CitationGroupId.java b/src/main/java/org/jabref/model/openoffice/style/CitationGroupId.java index 6ba6b760cb6..baaa931214c 100644 --- a/src/main/java/org/jabref/model/openoffice/style/CitationGroupId.java +++ b/src/main/java/org/jabref/model/openoffice/style/CitationGroupId.java @@ -4,15 +4,15 @@ * Identifies a citation group in a document. */ public class CitationGroupId { - String id; - public CitationGroupId(String id) { - this.id = id; + String groupId; + public CitationGroupId(String groupId) { + this.groupId = groupId; } /** * CitationEntry needs some string identifying the group that it can pass back later. */ public String citationGroupIdAsString() { - return id; + return groupId; } } diff --git a/src/main/java/org/jabref/model/openoffice/style/CitationGroups.java b/src/main/java/org/jabref/model/openoffice/style/CitationGroups.java index b03028b76ef..e5e642fa652 100644 --- a/src/main/java/org/jabref/model/openoffice/style/CitationGroups.java +++ b/src/main/java/org/jabref/model/openoffice/style/CitationGroups.java @@ -131,10 +131,9 @@ public void setGlobalOrder(List globalOrder) { this.globalOrder = Optional.of(globalOrder); // Propagate to each CitationGroup - int i = 0; - for (CitationGroupId groupId : globalOrder) { + for (int i = 0; i < globalOrder.size(); i++) { + CitationGroupId groupId = globalOrder.get(i); citationGroupsUnordered.get(groupId).setIndexInGlobalOrder(Optional.of(i)); - i++; } } diff --git a/src/main/java/org/jabref/model/openoffice/style/CitedKeys.java b/src/main/java/org/jabref/model/openoffice/style/CitedKeys.java index 898fadbebe9..059c1b6123a 100644 --- a/src/main/java/org/jabref/model/openoffice/style/CitedKeys.java +++ b/src/main/java/org/jabref/model/openoffice/style/CitedKeys.java @@ -45,11 +45,11 @@ void sortByComparator(Comparator entryComparator) { } void numberCitedKeysInCurrentOrder() { - int i = 1; + int index = 1; for (CitedKey ck : data.values()) { if (ck.getLookupResult().isPresent()) { - ck.setNumber(Optional.of(i)); - i++; + ck.setNumber(Optional.of(index)); + index++; } else { // Unresolved citations do not get a number. ck.setNumber(Optional.empty()); diff --git a/src/main/java/org/jabref/model/openoffice/style/PageInfo.java b/src/main/java/org/jabref/model/openoffice/style/PageInfo.java index ec3a8436dcf..1ff87569a3b 100644 --- a/src/main/java/org/jabref/model/openoffice/style/PageInfo.java +++ b/src/main/java/org/jabref/model/openoffice/style/PageInfo.java @@ -19,7 +19,7 @@ public static Optional normalizePageInfo(Optional optionalText) } String str = OOText.toString(optionalText.get()); String trimmed = str.trim(); - if (trimmed.equals("")) { + if ("".equals(trimmed)) { return Optional.empty(); } return Optional.of(OOText.fromString(trimmed)); diff --git a/src/main/java/org/jabref/model/openoffice/uno/UnoCursor.java b/src/main/java/org/jabref/model/openoffice/uno/UnoCursor.java index 1770b6957c5..e761a56e12f 100644 --- a/src/main/java/org/jabref/model/openoffice/uno/UnoCursor.java +++ b/src/main/java/org/jabref/model/openoffice/uno/UnoCursor.java @@ -39,7 +39,7 @@ public static Optional getTextCursorOfTextContentAnchor(XTextConten return Optional.of(markAnchor.getText().createTextCursorByRange(markAnchor)); } - public static XTextCursor createTextCursorByRange(XTextRange r) { - return r.getText().createTextCursorByRange(r); + public static XTextCursor createTextCursorByRange(XTextRange range) { + return range.getText().createTextCursorByRange(range); } } diff --git a/src/main/java/org/jabref/model/openoffice/uno/UnoStyle.java b/src/main/java/org/jabref/model/openoffice/uno/UnoStyle.java index dece8978713..cd4065ba8b3 100644 --- a/src/main/java/org/jabref/model/openoffice/uno/UnoStyle.java +++ b/src/main/java/org/jabref/model/openoffice/uno/UnoStyle.java @@ -25,10 +25,10 @@ private static Optional getStyleFromFamily(XTextDocument doc, String fam WrappedTargetException { XStyleFamiliesSupplier fss = UnoCast.cast(XStyleFamiliesSupplier.class, doc).get(); - XNameAccess fs = UnoCast.cast(XNameAccess.class, fss.getStyleFamilies()).get(); + XNameAccess families = UnoCast.cast(XNameAccess.class, fss.getStyleFamilies()).get(); XNameContainer xFamily; try { - xFamily = UnoCast.cast(XNameContainer.class, fs.getByName(familyName)).get(); + xFamily = UnoCast.cast(XNameContainer.class, families.getByName(familyName)).get(); } catch (NoSuchElementException ex) { String msg = String.format("Style family name '%s' is not recognized", familyName); throw new java.lang.IllegalArgumentException(msg, ex); diff --git a/src/main/java/org/jabref/model/openoffice/uno/UnoUndo.java b/src/main/java/org/jabref/model/openoffice/uno/UnoUndo.java index b75bee17f3e..4f08bb0ed5c 100644 --- a/src/main/java/org/jabref/model/openoffice/uno/UnoUndo.java +++ b/src/main/java/org/jabref/model/openoffice/uno/UnoUndo.java @@ -25,17 +25,17 @@ public static Optional getXUndoManager(XTextDocument doc) { * document's undo stack is left in an inconsistent state. */ public static void enterUndoContext(XTextDocument doc, String title) { - Optional um = getXUndoManager(doc); - if (um.isPresent()) { - um.get().enterUndoContext(title); + Optional undoManager = getXUndoManager(doc); + if (undoManager.isPresent()) { + undoManager.get().enterUndoContext(title); } } public static void leaveUndoContext(XTextDocument doc) { - Optional um = getXUndoManager(doc); - if (um.isPresent()) { + Optional undoManager = getXUndoManager(doc); + if (undoManager.isPresent()) { try { - um.get().leaveUndoContext(); + undoManager.get().leaveUndoContext(); } catch (InvalidStateException ex) { throw new IllegalStateException("leaveUndoContext reported InvalidStateException"); } diff --git a/src/main/java/org/jabref/model/openoffice/uno/UnoUserDefinedProperty.java b/src/main/java/org/jabref/model/openoffice/uno/UnoUserDefinedProperty.java index 634edb0c57b..f5ec43f3211 100644 --- a/src/main/java/org/jabref/model/openoffice/uno/UnoUserDefinedProperty.java +++ b/src/main/java/org/jabref/model/openoffice/uno/UnoUserDefinedProperty.java @@ -61,8 +61,8 @@ public static Optional getStringValue(XTextDocument doc, String property throw new java.lang.IllegalArgumentException("getting UserDefinedProperties as XPropertySet failed"); } try { - String v = propertySet.get().getPropertyValue(property).toString(); - return Optional.ofNullable(v); + String value = propertySet.get().getPropertyValue(property).toString(); + return Optional.ofNullable(value); } catch (UnknownPropertyException ex) { return Optional.empty(); } diff --git a/src/main/java/org/jabref/model/openoffice/util/OOListUtil.java b/src/main/java/org/jabref/model/openoffice/util/OOListUtil.java index 1fe617ed99a..59295c798fe 100644 --- a/src/main/java/org/jabref/model/openoffice/util/OOListUtil.java +++ b/src/main/java/org/jabref/model/openoffice/util/OOListUtil.java @@ -13,19 +13,19 @@ public static List map(List list, Function fun) { return list.stream().map(e -> fun.apply(e)).collect(Collectors.toList()); } - /** Integers 0..(n-1) */ - public static List makeIndices(int n) { - return Stream.iterate(0, i -> i + 1).limit(n).collect(Collectors.toList()); + /** Integers 0..(len-1) */ + public static List makeIndices(int len) { + return Stream.iterate(0, i -> i + 1).limit(len).collect(Collectors.toList()); } /** Return indices so that list.get(indices.get(i)) is sorted. */ public static List order(List list, Comparator comparator) { - List ii = makeIndices(list.size()); - Collections.sort(ii, new Comparator() { - @Override public int compare(final Integer o1, final Integer o2) { - return comparator.compare((U) list.get(o1), (U) list.get(o2)); + List indices = makeIndices(list.size()); + Collections.sort(indices, new Comparator() { + @Override public int compare(final Integer a, final Integer b) { + return comparator.compare((U) list.get(a), (U) list.get(b)); } }); - return ii; + return indices; } } From 5272e98908b2549e5f384d60854ce42d18ae6dc2 Mon Sep 17 00:00:00 2001 From: Antal K Date: Tue, 9 Nov 2021 12:20:28 +0100 Subject: [PATCH 51/51] apply suggested changes --- .../logic/openoffice/action/EditInsert.java | 25 +++++++++-------- .../logic/openoffice/action/EditMerge.java | 28 +++++++++---------- .../logic/openoffice/action/EditSeparate.java | 22 +++++++-------- .../logic/openoffice/action/ExportCited.java | 14 +++++----- .../logic/openoffice/action/Update.java | 8 +++--- 5 files changed, 50 insertions(+), 47 deletions(-) diff --git a/src/main/java/org/jabref/logic/openoffice/action/EditInsert.java b/src/main/java/org/jabref/logic/openoffice/action/EditInsert.java index 375d3c1637a..0004d3fe6db 100644 --- a/src/main/java/org/jabref/logic/openoffice/action/EditInsert.java +++ b/src/main/java/org/jabref/logic/openoffice/action/EditInsert.java @@ -20,6 +20,7 @@ import org.jabref.model.openoffice.uno.NoDocumentException; import org.jabref.model.openoffice.uno.UnoScreenRefresh; import org.jabref.model.openoffice.util.OOListUtil; +import org.jabref.model.strings.StringUtil; import com.sun.star.beans.IllegalTypeException; import com.sun.star.beans.NotRemoveableException; @@ -73,11 +74,11 @@ public static void insertCitationGroup(XTextDocument doc, List citationKeys = OOListUtil.map(entries, EditInsert::insertEntryGetCitationKey); - final int nEntries = entries.size(); - List> pageInfos = OODataModel.fakePageInfos(pageInfo, nEntries); + final int totalEntries = entries.size(); + List> pageInfos = OODataModel.fakePageInfos(pageInfo, totalEntries); - List citations = new ArrayList<>(nEntries); - for (int i = 0; i < nEntries; i++) { + List citations = new ArrayList<>(totalEntries); + for (int i = 0; i < totalEntries; i++) { Citation cit = new Citation(citationKeys.get(i)); cit.lookupInDatabases(Collections.singletonList(database)); cit.setPageInfo(pageInfos.get(i)); @@ -85,14 +86,16 @@ public static void insertCitationGroup(XTextDocument doc, } // The text we insert - OOText citeText = - (style.isNumberEntries() - ? OOText.fromString("[-]") // A dash only. Only refresh later. - : style.createCitationMarker(citations, - citationType.inParenthesis(), - NonUniqueCitationMarker.FORGIVEN)); + OOText citeText = null; + if (style.isNumberEntries()) { + citeText = OOText.fromString("[-]"); // A dash only. Only refresh later. + } else { + citeText = style.createCitationMarker(citations, + citationType.inParenthesis(), + NonUniqueCitationMarker.FORGIVEN); + } - if ("".equals(OOText.toString(citeText))) { + if (StringUtil.isBlank(OOText.toString(citeText))) { citeText = OOText.fromString("[?]"); } diff --git a/src/main/java/org/jabref/logic/openoffice/action/EditMerge.java b/src/main/java/org/jabref/logic/openoffice/action/EditMerge.java index 6fa19e2d344..f91e8aec93d 100644 --- a/src/main/java/org/jabref/logic/openoffice/action/EditMerge.java +++ b/src/main/java/org/jabref/logic/openoffice/action/EditMerge.java @@ -59,16 +59,16 @@ public static boolean mergeCitationGroups(XTextDocument doc, OOFrontend frontend for (JoinableGroupData joinableGroupData : joinableGroups) { - List cgs = joinableGroupData.group; + List groups = joinableGroupData.group; - List newCitations = (cgs.stream() + List newCitations = (groups.stream() .flatMap(group -> group.citationsInStorageOrder.stream()) .collect(Collectors.toList())); - CitationType citationType = cgs.get(0).citationType; - List> pageInfos = frontend.backend.combinePageInfos(cgs); + CitationType citationType = groups.get(0).citationType; + List> pageInfos = frontend.backend.combinePageInfos(groups); - frontend.removeCitationGroups(cgs, doc); + frontend.removeCitationGroups(groups, doc); XTextCursor textCursor = joinableGroupData.groupCursor; textCursor.setString(""); // Also remove the spaces between. @@ -206,8 +206,8 @@ private static boolean checkAddToGroup(ScanState state, CitationGroup group, XTe // assume: currentGroupCursor.getEnd() == cursorBetween.getEnd() if (UnoTextRange.compareEnds(state.cursorBetween, state.currentGroupCursor) != 0) { - String msg = ("MergeCitationGroups: cursorBetween.end != currentGroupCursor.end"); - throw new IllegalStateException(msg); + LOGGER.warn("MergeCitationGroups: cursorBetween.end != currentGroupCursor.end"); + throw new IllegalStateException("MergeCitationGroups failed"); } /* @@ -238,8 +238,8 @@ private static boolean checkAddToGroup(ScanState state, CitationGroup group, XTe // These two should move in sync: if (UnoTextRange.compareEnds(state.cursorBetween, state.currentGroupCursor) != 0) { - String msg = ("MergeCitationGroups: cursorBetween.end != currentGroupCursor.end (during expand)"); - throw new IllegalStateException(msg); + LOGGER.warn("MergeCitationGroups: cursorBetween.end != currentGroupCursor.end (during expand)"); + throw new IllegalStateException("MergeCitationGroups failed"); } } @@ -278,8 +278,8 @@ private static void addToCurrentGroup(ScanState state, CitationGroup group, XTex state.currentGroupCursor.goRight((short) (currentRange.getString().length()), true); if (UnoTextRange.compareEnds(state.cursorBetween, state.currentGroupCursor) != 0) { - String msg = ("MergeCitationGroups: cursorBetween.end != currentGroupCursor.end"); - throw new IllegalStateException(msg); + LOGGER.warn("MergeCitationGroups: cursorBetween.end != currentGroupCursor.end"); + throw new IllegalStateException("MergeCitationGroups failed"); } /* Store data about last entry in currentGroup */ @@ -296,14 +296,14 @@ private static List scan(XTextDocument doc, OOFrontend fronte WrappedTargetException { List result = new ArrayList<>(); - List cgs = frontend.getCitationGroupsSortedWithinPartitions(doc, false /* mapFootnotesToFootnoteMarks */); - if (cgs.isEmpty()) { + List groups = frontend.getCitationGroupsSortedWithinPartitions(doc, false /* mapFootnotesToFootnoteMarks */); + if (groups.isEmpty()) { return result; } ScanState state = new ScanState(); - for (CitationGroup group : cgs) { + for (CitationGroup group : groups) { XTextRange currentRange = (frontend.getMarkRange(doc, group) .orElseThrow(IllegalStateException::new)); diff --git a/src/main/java/org/jabref/logic/openoffice/action/EditSeparate.java b/src/main/java/org/jabref/logic/openoffice/action/EditSeparate.java index 409969b213f..66630a6f8d0 100644 --- a/src/main/java/org/jabref/logic/openoffice/action/EditSeparate.java +++ b/src/main/java/org/jabref/logic/openoffice/action/EditSeparate.java @@ -50,38 +50,38 @@ public static boolean separateCitations(XTextDocument doc, frontend.citationGroups.lookupCitations(databases); frontend.citationGroups.imposeLocalOrder(OOProcess.comparatorForMulticite(style)); - List cgs = frontend.citationGroups.getCitationGroupsUnordered(); + List groups = frontend.citationGroups.getCitationGroupsUnordered(); try { UnoScreenRefresh.lockControllers(doc); - for (CitationGroup group : cgs) { + for (CitationGroup group : groups) { XTextRange range1 = (frontend .getMarkRange(doc, group) .orElseThrow(IllegalStateException::new)); XTextCursor textCursor = range1.getText().createTextCursorByRange(range1); - List cits = group.citationsInStorageOrder; - if (cits.size() <= 1) { + List citations = group.citationsInStorageOrder; + if (citations.size() <= 1) { continue; } frontend.removeCitationGroup(group, doc); - // Now we own the content of cits + // Now we own the content of citations // Create a citation group for each citation. - final int last = cits.size() - 1; - for (int i = 0; i < cits.size(); i++) { + final int last = citations.size() - 1; + for (int i = 0; i < citations.size(); i++) { boolean insertSpaceAfter = (i != last); - Citation cit = cits.get(i); + Citation citation = citations.get(i); UpdateCitationMarkers.createAndFillCitationGroup(frontend, doc, - List.of(cit.citationKey), - List.of(cit.getPageInfo()), + List.of(citation.citationKey), + List.of(citation.getPageInfo()), group.citationType, - OOText.fromString(cit.citationKey), + OOText.fromString(citation.citationKey), textCursor, style, insertSpaceAfter); diff --git a/src/main/java/org/jabref/logic/openoffice/action/ExportCited.java b/src/main/java/org/jabref/logic/openoffice/action/ExportCited.java index ba806426051..c38cd4fd4cc 100644 --- a/src/main/java/org/jabref/logic/openoffice/action/ExportCited.java +++ b/src/main/java/org/jabref/logic/openoffice/action/ExportCited.java @@ -52,8 +52,8 @@ public static GenerateDatabaseResult generateDatabase(XTextDocument doc, List unresolvedKeys = new ArrayList<>(); BibDatabase resultDatabase = new BibDatabase(); @@ -61,13 +61,13 @@ public static GenerateDatabaseResult generateDatabase(XTextDocument doc, List entriesToInsert = new ArrayList<>(); Set seen = new HashSet<>(); // Only add crossReference once. - for (CitedKey ck : cks.values()) { - if (ck.getLookupResult().isEmpty()) { - unresolvedKeys.add(ck.citationKey); + for (CitedKey citation : citationKeys.values()) { + if (citation.getLookupResult().isEmpty()) { + unresolvedKeys.add(citation.citationKey); continue; } else { - BibEntry entry = ck.getLookupResult().get().entry; - BibDatabase loopDatabase = ck.getLookupResult().get().database; + BibEntry entry = citation.getLookupResult().get().entry; + BibDatabase loopDatabase = citation.getLookupResult().get().database; // If entry found BibEntry clonedEntry = (BibEntry) entry.clone(); diff --git a/src/main/java/org/jabref/logic/openoffice/action/Update.java b/src/main/java/org/jabref/logic/openoffice/action/Update.java index ba840e88b70..80263833aec 100644 --- a/src/main/java/org/jabref/logic/openoffice/action/Update.java +++ b/src/main/java/org/jabref/logic/openoffice/action/Update.java @@ -21,6 +21,8 @@ */ public class Update { + static final boolean USE_LOCK_CONTROLLERS = true; + private Update() { /**/ } @@ -41,13 +43,11 @@ private static List updateDocument(XTextDocument doc, WrappedTargetException, com.sun.star.lang.IllegalArgumentException { - final boolean useLockControllers = true; - frontend.imposeGlobalOrder(doc, fcursor); OOProcess.produceCitationMarkers(frontend.citationGroups, databases, style); try { - if (useLockControllers) { + if (USE_LOCK_CONTROLLERS) { UnoScreenRefresh.lockControllers(doc); } @@ -63,7 +63,7 @@ private static List updateDocument(XTextDocument doc, return frontend.citationGroups.getUnresolvedKeys(); } finally { - if (useLockControllers && UnoScreenRefresh.hasControllersLocked(doc)) { + if (USE_LOCK_CONTROLLERS && UnoScreenRefresh.hasControllersLocked(doc)) { UnoScreenRefresh.unlockControllers(doc); } }