From 11341e4b145011b159873b7e6cd75f489c3426a0 Mon Sep 17 00:00:00 2001 From: Georgy Litvinov Date: Wed, 17 Nov 2021 16:23:33 +0100 Subject: [PATCH] Initialized project --- .classpath | 19 + META-INF/manifest.xml | 5 - build.gradle | 61 ++- description/description_en.txt | 3 - description/description_ru.txt | 3 - docSettings/Events.xba | 14 - docSettings/dialog.xlb | 4 - docSettings/script.xlb | 5 - oxt/Addons.xcu | 46 ++ Events.xcu => oxt/Events.xcu | 4 +- oxt/META-INF/manifest.xml | 8 + oxt/WriterWindowState.xcu | 27 ++ description.xml => oxt/description.xml | 7 +- oxt/description/description_en.txt | 2 + oxt/description/description_ru.txt | 2 + {icons => oxt/icons}/addon_icon.png | Bin oxt/icons/button_icon.png | Bin 0 -> 13321 bytes license_en.txt => oxt/license_en.txt | 2 +- license_ru.txt => oxt/license_ru.txt | 2 +- releasenotes.txt => oxt/releasenotes.txt | 2 + src/main/java/logback.xml | 24 + .../litvinovg/docsettings/DialogHelper.java | 185 ++++++++ .../docsettings/DocSettingsExtension.java | 121 +++++ .../docsettings/ExtensionConfig.java | 41 ++ .../pro/litvinovg/docsettings/FileHelper.java | 60 +++ .../pro/litvinovg/docsettings/Localizer.java | 62 +++ .../docsettings/RegistrationHandler.classes | 1 + .../docsettings/RegistrationHandler.java | 175 +++++++ .../docsettings/StandaloneApplication.java | 27 ++ .../docsettings/document/ODFDocument.java | 357 ++++++++++++++ .../exceptions/MalformedXmlException.java | 10 + .../docsettings/gui/BackupPanel.java | 202 ++++++++ .../docsettings/gui/ChangedSettings.java | 42 ++ .../litvinovg/docsettings/gui/MainWindow.java | 123 +++++ .../docsettings/gui/SettingsPanel.java | 33 ++ .../localizations/docsettings.properties | 15 + .../localizations/docsettings_ru.properties | 15 + .../litvinovg/docsettings/resources/icon.png | Bin 0 -> 13321 bytes .../docsettings/settings/Backups.java | 445 ++++++++++++++++++ .../settings/ComparatorResults.java | 66 +++ .../docsettings/settings/ODFSetting.java | 11 + .../docsettings/settings/ODFSettings.java | 24 + .../docsettings/settings/SettingFactory.java | 64 +++ .../docsettings/settings/Settings.java | 78 +++ .../settings/SettingsComparator.java | 48 ++ .../docsettings/settings/SimpleSetting.java | 53 +++ .../docsettings/settings/StorageUtils.java | 126 +++++ .../exceptions/MalformedXmlExceptionTest.java | 14 + .../processor/ODFSettingsTest.java | 14 + testFiles/settings-old.xml | 131 ++++++ testFiles/settings.xml | 131 ++++++ testFiles/testFile (copy).odt | Bin 0 -> 12954 bytes testFiles/testFile.odt | Bin 0 -> 14597 bytes 53 files changed, 2868 insertions(+), 46 deletions(-) create mode 100644 .classpath delete mode 100644 META-INF/manifest.xml delete mode 100644 description/description_en.txt delete mode 100644 description/description_ru.txt delete mode 100644 docSettings/Events.xba delete mode 100644 docSettings/dialog.xlb delete mode 100644 docSettings/script.xlb create mode 100644 oxt/Addons.xcu rename Events.xcu => oxt/Events.xcu (70%) create mode 100644 oxt/META-INF/manifest.xml create mode 100644 oxt/WriterWindowState.xcu rename description.xml => oxt/description.xml (83%) create mode 100644 oxt/description/description_en.txt create mode 100644 oxt/description/description_ru.txt rename {icons => oxt/icons}/addon_icon.png (100%) create mode 100644 oxt/icons/button_icon.png rename license_en.txt => oxt/license_en.txt (97%) rename license_ru.txt => oxt/license_ru.txt (98%) rename releasenotes.txt => oxt/releasenotes.txt (63%) create mode 100644 src/main/java/logback.xml create mode 100644 src/main/java/pro/litvinovg/docsettings/DialogHelper.java create mode 100644 src/main/java/pro/litvinovg/docsettings/DocSettingsExtension.java create mode 100644 src/main/java/pro/litvinovg/docsettings/ExtensionConfig.java create mode 100644 src/main/java/pro/litvinovg/docsettings/FileHelper.java create mode 100644 src/main/java/pro/litvinovg/docsettings/Localizer.java create mode 100644 src/main/java/pro/litvinovg/docsettings/RegistrationHandler.classes create mode 100644 src/main/java/pro/litvinovg/docsettings/RegistrationHandler.java create mode 100644 src/main/java/pro/litvinovg/docsettings/StandaloneApplication.java create mode 100644 src/main/java/pro/litvinovg/docsettings/document/ODFDocument.java create mode 100644 src/main/java/pro/litvinovg/docsettings/exceptions/MalformedXmlException.java create mode 100644 src/main/java/pro/litvinovg/docsettings/gui/BackupPanel.java create mode 100644 src/main/java/pro/litvinovg/docsettings/gui/ChangedSettings.java create mode 100644 src/main/java/pro/litvinovg/docsettings/gui/MainWindow.java create mode 100644 src/main/java/pro/litvinovg/docsettings/gui/SettingsPanel.java create mode 100644 src/main/java/pro/litvinovg/docsettings/localizations/docsettings.properties create mode 100644 src/main/java/pro/litvinovg/docsettings/localizations/docsettings_ru.properties create mode 100644 src/main/java/pro/litvinovg/docsettings/resources/icon.png create mode 100644 src/main/java/pro/litvinovg/docsettings/settings/Backups.java create mode 100644 src/main/java/pro/litvinovg/docsettings/settings/ComparatorResults.java create mode 100644 src/main/java/pro/litvinovg/docsettings/settings/ODFSetting.java create mode 100644 src/main/java/pro/litvinovg/docsettings/settings/ODFSettings.java create mode 100644 src/main/java/pro/litvinovg/docsettings/settings/SettingFactory.java create mode 100644 src/main/java/pro/litvinovg/docsettings/settings/Settings.java create mode 100644 src/main/java/pro/litvinovg/docsettings/settings/SettingsComparator.java create mode 100644 src/main/java/pro/litvinovg/docsettings/settings/SimpleSetting.java create mode 100644 src/main/java/pro/litvinovg/docsettings/settings/StorageUtils.java create mode 100644 src/test/java/pro/litvinovg/docsettings/exceptions/MalformedXmlExceptionTest.java create mode 100644 src/test/java/pro/litvinovg/docsettings/processor/ODFSettingsTest.java create mode 100644 testFiles/settings-old.xml create mode 100644 testFiles/settings.xml create mode 100644 testFiles/testFile (copy).odt create mode 100644 testFiles/testFile.odt diff --git a/.classpath b/.classpath new file mode 100644 index 0000000..7a7f31f --- /dev/null +++ b/.classpath @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/META-INF/manifest.xml b/META-INF/manifest.xml deleted file mode 100644 index b8e7ac6..0000000 --- a/META-INF/manifest.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/build.gradle b/build.gradle index be59f7b..d30d9ea 100644 --- a/build.gradle +++ b/build.gradle @@ -1,13 +1,64 @@ +apply plugin: 'java' +apply plugin: 'eclipse' +sourceCompatibility = 1.8 + +repositories { + mavenCentral() +} + +test { + useJUnitPlatform() +} + +dependencies { + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.3.1' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.3.1' + compileClasspath 'org.libreoffice:jurt:5.3.2' + compileClasspath 'org.libreoffice:juh:5.3.2' + compileClasspath 'org.libreoffice:ridl:5.3.2' + compileClasspath 'org.libreoffice:unoil:5.3.2' + compileClasspath group: 'org.slf4j', name: 'slf4j-api', version: '1.7.32' + compileClasspath 'ch.qos.logback:logback-classic:1.2.6' + compileClasspath 'ch.qos.logback:logback-core:1.2.6' + compileClasspath 'org.apache.tika:tika-core:2.1.0' +} + + +task oxtjar(type: Jar) { + archiveName 'docsettings_oxt.jar' + manifest { + attributes("Implementation-Title": rootProject.name, + "Implementation-Version": project.version, + "RegistrationClassName" : "pro.litvinovg.docsettings.RegistrationHandler", + "Class-Path" : "jasp.jar parser.jar") + } + from('src/main/java') { + include '**/*.classes' + include 'logback.xml' + include 'pro/litvinovg/docsettings/resources/*.png' + include 'pro/litvinovg/docsettings/localizations/*.properties' + } + from (sourceSets.main.output){ + include 'pro/litvinovg/**/*.class' + include 'ch/**/*' + include 'org/slf4j/**/*' + } + from { + configurations.compileClasspath.collect { it.isDirectory() ? it : zipTree(it) } + } +} + + task oxt(type: Zip) { - from './' + dependsOn = [ 'oxtjar' ] + from './oxt/' include '*' include '*/*' include '*/*/*' include '*/*/*/*' - exclude 'docSettings.oxt' exclude '.*' - exclude 'build.gradle' - exclude 'translations.ods' - exclude 'update.xml' + from ('build/libs/docsettings_oxt.jar'){ + include '*' + } archiveName 'docSettings.oxt' } diff --git a/description/description_en.txt b/description/description_en.txt deleted file mode 100644 index ac3eafb..0000000 --- a/description/description_en.txt +++ /dev/null @@ -1,3 +0,0 @@ -Extension for document settings configuration. -For now consists of workaround for layout bug since LO 6.4 -https://bugs.documentfoundation.org/show_bug.cgi?id=134782 diff --git a/description/description_ru.txt b/description/description_ru.txt deleted file mode 100644 index b586ade..0000000 --- a/description/description_ru.txt +++ /dev/null @@ -1,3 +0,0 @@ -Расширение исправления настроек документов -В данный момент содержит исправления для бага, возникшего с версии 6.4 -https://bugs.documentfoundation.org/show_bug.cgi?id=134782 diff --git a/docSettings/Events.xba b/docSettings/Events.xba deleted file mode 100644 index 9fd087c..0000000 --- a/docSettings/Events.xba +++ /dev/null @@ -1,14 +0,0 @@ - - -Sub execOnDocumentLoad - Dim fixCompatibilityOption As Boolean - fixCompatibilityOption = true - If fixCompatibilityOption Then - Dim oDocSettings As Object - oDocSettings = ThisComponent.createInstance( "com.sun.star.document.Settings" ) - If oDocSettings.supportsService("com.sun.star.text.DocumentSettings") Then - oDocSettings.setPropertyValue("AddParaSpacingToTableCells", false) - EndIf - EndIf -End Sub - \ No newline at end of file diff --git a/docSettings/dialog.xlb b/docSettings/dialog.xlb deleted file mode 100644 index f679cd9..0000000 --- a/docSettings/dialog.xlb +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/docSettings/script.xlb b/docSettings/script.xlb deleted file mode 100644 index 9077bd7..0000000 --- a/docSettings/script.xlb +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/oxt/Addons.xcu b/oxt/Addons.xcu new file mode 100644 index 0000000..5e5150b --- /dev/null +++ b/oxt/Addons.xcu @@ -0,0 +1,46 @@ + + + + + + + + service:pro.litvinovg.libreoffice.DocSettings?openGUI + + + + vnd.sun.star.extension://pro.litvinovg.libreoffice.docsettings/icons/button_icon.png + + + + + + + + + Document settings panel + + true + + + service:pro.litvinovg.libreoffice.DocSettings?openGUI + + + _self + + + com.sun.star.text.TextDocument + + + Open document settings + Открыть настройки документа + + + + + + + + + diff --git a/Events.xcu b/oxt/Events.xcu similarity index 70% rename from Events.xcu rename to oxt/Events.xcu index 95e1d37..d632a08 100644 --- a/Events.xcu +++ b/oxt/Events.xcu @@ -4,9 +4,9 @@ xmlns:xs="http://www.w3.org/2001/XMLSchema" oor:name="Events" oor:package="org.openoffice.Office"> - + - vnd.sun.star.script:docSettings.Events.execOnDocumentLoad?language=Basic&location=application + service:pro.litvinovg.libreoffice.DocSettings?onLoad diff --git a/oxt/META-INF/manifest.xml b/oxt/META-INF/manifest.xml new file mode 100644 index 0000000..5678c7f --- /dev/null +++ b/oxt/META-INF/manifest.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/oxt/WriterWindowState.xcu b/oxt/WriterWindowState.xcu new file mode 100644 index 0000000..65aa27e --- /dev/null +++ b/oxt/WriterWindowState.xcu @@ -0,0 +1,27 @@ + + + + + + + + + Document settings + Настройки документа + + + 10,0 + + + true + + + 0 + + + + + + diff --git a/description.xml b/oxt/description.xml similarity index 83% rename from description.xml rename to oxt/description.xml index 3cd203d..7ffe423 100644 --- a/description.xml +++ b/oxt/description.xml @@ -1,11 +1,11 @@ - + - Исправление свойств документов - Docement settings fix + Управление настройками документов + Docement settings management @@ -21,6 +21,7 @@ Georgy Litvinov + Георгий Литвинов diff --git a/oxt/description/description_en.txt b/oxt/description/description_en.txt new file mode 100644 index 0000000..227b0ef --- /dev/null +++ b/oxt/description/description_en.txt @@ -0,0 +1,2 @@ +Extension for document settings configuration. +Provides a way to create setttings backups and restore from it. diff --git a/oxt/description/description_ru.txt b/oxt/description/description_ru.txt new file mode 100644 index 0000000..7bfc9de --- /dev/null +++ b/oxt/description/description_ru.txt @@ -0,0 +1,2 @@ +Расширение для управления настройками документов +Предоставляет возможность создания резервных копий настроек и восстановления из них. diff --git a/icons/addon_icon.png b/oxt/icons/addon_icon.png similarity index 100% rename from icons/addon_icon.png rename to oxt/icons/addon_icon.png diff --git a/oxt/icons/button_icon.png b/oxt/icons/button_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..e89adc0733d7497413e3c7d601a88942fb7e124b GIT binary patch literal 13321 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 + + + + %d{HH:mm:ss.SSS} %-5level - %msg%n + + + + + + + + + \ No newline at end of file diff --git a/src/main/java/pro/litvinovg/docsettings/DialogHelper.java b/src/main/java/pro/litvinovg/docsettings/DialogHelper.java new file mode 100644 index 0000000..3b9deac --- /dev/null +++ b/src/main/java/pro/litvinovg/docsettings/DialogHelper.java @@ -0,0 +1,185 @@ +package pro.litvinovg.docsettings; + + +import java.io.File; + +import com.sun.star.awt.MessageBoxType; +import com.sun.star.awt.Point; +import com.sun.star.awt.XButton; +import com.sun.star.awt.XComboBox; +import com.sun.star.awt.XControl; +import com.sun.star.awt.XControlContainer; +import com.sun.star.awt.XControlModel; +import com.sun.star.awt.XDialog; +import com.sun.star.awt.XDialogEventHandler; +import com.sun.star.awt.XDialogProvider2; +import com.sun.star.awt.XFixedText; +import com.sun.star.awt.XListBox; +import com.sun.star.awt.XMessageBox; +import com.sun.star.awt.XMessageBoxFactory; +import com.sun.star.awt.XTextComponent; +import com.sun.star.awt.XToolkit; +import com.sun.star.awt.XWindow; +import com.sun.star.awt.XWindowPeer; +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.uno.Exception; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.uno.XComponentContext; + +public class DialogHelper { + + /** + * Create a dialog from an xdl file. + * + * @param xdlFile + * The filename in the `dialog` folder + * @param context + * @return XDialog + */ + public static XDialog createDialog(String xdlFile, XComponentContext context, XDialogEventHandler handler) { + Object oDialogProvider; + try { + oDialogProvider = context.getServiceManager().createInstanceWithContext("com.sun.star.awt.DialogProvider2", + context); + XDialogProvider2 xDialogProv = (XDialogProvider2) UnoRuntime.queryInterface(XDialogProvider2.class, + oDialogProvider); + File dialogFile = FileHelper.getDialogFilePath(xdlFile, context); + return xDialogProv.createDialogWithHandler(convertToURL(context, dialogFile), handler); + } catch (Exception e) { + return null; + } + } + + /** Returns a URL to be used with XDialogProvider to create a dialog */ + public static String convertToURL(XComponentContext xContext, File dialogFile) { + String sURL = null; + try { + com.sun.star.ucb.XFileIdentifierConverter xFileConverter = (com.sun.star.ucb.XFileIdentifierConverter) UnoRuntime + .queryInterface(com.sun.star.ucb.XFileIdentifierConverter.class, xContext.getServiceManager() + .createInstanceWithContext("com.sun.star.ucb.FileContentProvider", xContext)); + sURL = xFileConverter.getFileURLFromSystemPath("", dialogFile.getAbsolutePath()); + } catch (com.sun.star.uno.Exception ex) { + return null; + } + return sURL; + } + + /** Returns a button (XButton) from a dialog */ + public static XButton getButton(XDialog dialog, String componentId) { + XControlContainer xDlgContainer = (XControlContainer) UnoRuntime.queryInterface(XControlContainer.class, + dialog); + Object control = xDlgContainer.getControl(componentId); + return (XButton) UnoRuntime.queryInterface(XButton.class, control); + } + + /** Returns a text field (XTextComponent) from a dialog */ + public static XTextComponent getEditField(XDialog dialog, String componentId) { + XControlContainer xDlgContainer = (XControlContainer) UnoRuntime.queryInterface(XControlContainer.class, + dialog); + Object control = xDlgContainer.getControl(componentId); + return (XTextComponent) UnoRuntime.queryInterface(XTextComponent.class, control); + } + + /** Returns a Combo box (XComboBox) from a dialog */ + public static XComboBox getCombobox(XDialog dialog, String componentId) { + XControlContainer xDlgContainer = (XControlContainer) UnoRuntime.queryInterface(XControlContainer.class, + dialog); + Object control = xDlgContainer.getControl(componentId); + return (XComboBox) UnoRuntime.queryInterface(XComboBox.class, control); + } + + /** Returns a List box (XListBox) from a dialog */ + public static XListBox getListBox(XDialog dialog, String componentId) { + XControlContainer xDlgContainer = (XControlContainer) UnoRuntime.queryInterface(XControlContainer.class, + dialog); + Object control = xDlgContainer.getControl(componentId); + return (XListBox) UnoRuntime.queryInterface(XListBox.class, control); + } + + /** Returns a label (XFixedText) from a dialog */ + public static XFixedText getLabel(XDialog dialog, String componentId) { + XControlContainer xDlgContainer = (XControlContainer) UnoRuntime.queryInterface(XControlContainer.class, + dialog); + Object control = xDlgContainer.getControl(componentId); + return (XFixedText) UnoRuntime.queryInterface(XFixedText.class, control); + } + + public static void EnableButton(XDialog dialog, String componentId, boolean enable) { + XControlContainer xDlgContainer = (XControlContainer) UnoRuntime.queryInterface(XControlContainer.class, + dialog); + // retrieve the control that we want to disable or enable + XControl xControl = UnoRuntime.queryInterface(XControl.class, xDlgContainer.getControl(componentId)); + XPropertySet xModelPropertySet = UnoRuntime.queryInterface(XPropertySet.class, xControl.getModel()); + try { + xModelPropertySet.setPropertyValue("Enabled", Boolean.valueOf(enable)); + } catch (IllegalArgumentException | UnknownPropertyException | PropertyVetoException + | WrappedTargetException e) { + return; + } + } + + /** Set the focus to an input field */ + public static void SetFocus(XTextComponent editField) { + XWindow xControlWindow = UnoRuntime.queryInterface(XWindow.class, editField); + xControlWindow.setFocus(); + } + + public static void setPosition(XDialog dialog, int posX, int posY) { + XControlModel xDialogModel = UnoRuntime.queryInterface(XControl.class, dialog).getModel(); + XPropertySet xPropSet = UnoRuntime.queryInterface(XPropertySet.class, xDialogModel); + try { + xPropSet.setPropertyValue("PositionX", posX); + xPropSet.setPropertyValue("PositionY", posY); + } catch (com.sun.star.lang.IllegalArgumentException | UnknownPropertyException | PropertyVetoException + | WrappedTargetException e) { + return; + } + } + + public static Point getPosition(XDialog dialog) { + int posX = 0; + int posY = 0; + XControlModel xDialogModel = UnoRuntime.queryInterface(XControl.class, dialog).getModel(); + XPropertySet xPropSet = UnoRuntime.queryInterface(XPropertySet.class, xDialogModel); + try { + posX = (int) xPropSet.getPropertyValue("PositionX"); + posY = (int) xPropSet.getPropertyValue("PositionY"); + } catch (UnknownPropertyException | WrappedTargetException e) { + } + return new Point(posX, posY); + } + + public static void showInfoMessage(XComponentContext context, XDialog dialog, String message) { + showMessageBox(context, dialog, MessageBoxType.INFOBOX, "Info", message); + } + + public static void showWarningMessage(XComponentContext context, XDialog dialog, String message) { + showMessageBox(context, dialog, MessageBoxType.WARNINGBOX, "Warning", message); + } + + public static void showErrorMessage(XComponentContext context, XDialog dialog, String message) { + showMessageBox(context, dialog, MessageBoxType.ERRORBOX, "Error", message); + } + + public static void showMessageBox(XComponentContext context, XDialog dialog, MessageBoxType type, String sTitle, String sMessage) { + XToolkit xToolkit; + try { + xToolkit = UnoRuntime.queryInterface(XToolkit.class, + context.getServiceManager().createInstanceWithContext("com.sun.star.awt.Toolkit", context)); + } catch (Exception e) { + return; + } + XMessageBoxFactory xMessageBoxFactory = UnoRuntime.queryInterface(XMessageBoxFactory.class, xToolkit); + XWindowPeer xParentWindowPeer = UnoRuntime.queryInterface(XWindowPeer.class, dialog); + XMessageBox xMessageBox = xMessageBoxFactory.createMessageBox(xParentWindowPeer, type, + com.sun.star.awt.MessageBoxButtons.BUTTONS_OK, sTitle, sMessage); + if (xMessageBox == null) + return; + + xMessageBox.execute(); + } + +} diff --git a/src/main/java/pro/litvinovg/docsettings/DocSettingsExtension.java b/src/main/java/pro/litvinovg/docsettings/DocSettingsExtension.java new file mode 100644 index 0000000..d9ea1ea --- /dev/null +++ b/src/main/java/pro/litvinovg/docsettings/DocSettingsExtension.java @@ -0,0 +1,121 @@ +package pro.litvinovg.docsettings; + +import java.awt.EventQueue; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.sun.star.lang.XSingleComponentFactory; + +//import pro.litvinovg.docsettings.gui.ConfigurationWindow; +//import pro.litvinovg.docsettings.gui.ConversionExecutor; + +import com.sun.star.lib.uno.helper.Factory; +import com.sun.star.lib.uno.helper.WeakBase; +import com.sun.star.registry.XRegistryKey; +import com.sun.star.uno.XComponentContext; + +import pro.litvinovg.docsettings.document.ODFDocument; +import pro.litvinovg.docsettings.gui.MainWindow; +import pro.litvinovg.docsettings.settings.Backups; +import pro.litvinovg.docsettings.settings.Settings; + +public final class DocSettingsExtension extends WeakBase + implements com.sun.star.lang.XServiceInfo, com.sun.star.task.XJobExecutor { + private final XComponentContext context; + private static final String m_implementationName = DocSettingsExtension.class.getName(); + private static final String[] m_serviceNames = { "pro.litvinovg.libreoffice.DocSettings" }; + private static final Logger logger = LoggerFactory.getLogger(DocSettingsExtension.class); + + public DocSettingsExtension(XComponentContext componentContext) { + context = componentContext; + }; + + public static XSingleComponentFactory __getComponentFactory(String sImplementationName) { + XSingleComponentFactory xFactory = null; + + if (sImplementationName.equals(m_implementationName)) + xFactory = Factory.createComponentFactory(DocSettingsExtension.class, m_serviceNames); + return xFactory; + } + + public static boolean __writeRegistryServiceInfo(XRegistryKey xRegistryKey) { + return Factory.writeRegistryServiceInfo(m_implementationName, m_serviceNames, xRegistryKey); + } + + // com.sun.star.lang.XServiceInfo: + public String getImplementationName() { + return m_implementationName; + } + + public boolean supportsService(String sService) { + int len = m_serviceNames.length; + + for (int i = 0; i < len; i++) { + if (sService.equals(m_serviceNames[i])) + return true; + } + return false; + } + + public String[] getSupportedServiceNames() { + return m_serviceNames; + } + + // com.sun.star.task.XJobExecutor: + public void trigger(String action) { + switch (action) { + case "openGUI": + try { + MainWindow.runGUI(context); + } catch (Throwable e) { + DialogHelper.showErrorMessage(context, null, e.getLocalizedMessage()); + } + break; + case "onLoad": + try { + EventQueue.invokeLater(new Runnable() { + public void run() { + try { + onLoad(); + } catch (Exception e) { + DialogHelper.showErrorMessage(context, null, e.getLocalizedMessage()); + } + } + }); + } catch (Throwable e) { + DialogHelper.showErrorMessage(context, null, e.getLocalizedMessage()); + } + break; + default: + DialogHelper.showErrorMessage(context, null, "Unknown action: " + action); + } + } + + private void onLoad() { + ODFDocument document = null; + try { + document = new ODFDocument(context); + } catch (Exception e) { + logger.debug("Ops", e); + return; + } + Backups backups = new Backups(); + ExtensionConfig config = ExtensionConfig.getInstance(); + document.readSettings(); + document.readBackups(backups); + if (!document.hasBackups(backups)) { + backups.create(document.getCurrentSettings()); + document.writeBackups(backups); + return; + } + if (config.getAutoRestore()) { + Settings settings = backups.getLatest(); + boolean isSettingsChanged = document.compareWithBackup(settings); + if (isSettingsChanged) { + document.restoreSettings(settings); + } + } + + } +} diff --git a/src/main/java/pro/litvinovg/docsettings/ExtensionConfig.java b/src/main/java/pro/litvinovg/docsettings/ExtensionConfig.java new file mode 100644 index 0000000..050d96b --- /dev/null +++ b/src/main/java/pro/litvinovg/docsettings/ExtensionConfig.java @@ -0,0 +1,41 @@ +package pro.litvinovg.docsettings; + +import java.util.HashMap; +import java.util.prefs.BackingStoreException; +import java.util.prefs.Preferences; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ExtensionConfig { + + private static final Logger logger = LoggerFactory.getLogger(ExtensionConfig.class); + private static final String AUTO_RESTORE = "autoRestore"; + private static ExtensionConfig extensionSettings = null; + private Preferences storage; + + public static ExtensionConfig getInstance() { + if (extensionSettings != null) { + return extensionSettings; + } + extensionSettings = new ExtensionConfig(); + return extensionSettings; + } + + private ExtensionConfig() { + storage = Preferences.userRoot().node(this.getClass().getName()); + } + + public boolean getAutoRestore() { + return storage.getBoolean(AUTO_RESTORE, false); + } + + public void setAutoRestore(boolean value) { + storage.putBoolean(AUTO_RESTORE, value); + try { + storage.sync(); + } catch (BackingStoreException e) { + logger.debug("Ops", e); + } + } +} diff --git a/src/main/java/pro/litvinovg/docsettings/FileHelper.java b/src/main/java/pro/litvinovg/docsettings/FileHelper.java new file mode 100644 index 0000000..458c5d1 --- /dev/null +++ b/src/main/java/pro/litvinovg/docsettings/FileHelper.java @@ -0,0 +1,60 @@ +package pro.litvinovg.docsettings; + + +import java.io.File; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.net.URL; + +import com.sun.star.deployment.PackageInformationProvider; +import com.sun.star.deployment.XPackageInformationProvider; +import com.sun.star.uno.Exception; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.uno.XComponentContext; +import com.sun.star.util.XURLTransformer; + +public class FileHelper { + + final static String DIALOG_RESOURCES = "dialog/"; + + /** + * Returns a path to a dialog file + */ + public static File getDialogFilePath(String xdlFile, XComponentContext xContext) { + return getFilePath(DIALOG_RESOURCES + xdlFile, xContext); + } + + /** + * Returns a file path for a file in the installed extension, or null on failure. + */ + public static File getFilePath(String file, XComponentContext xContext) { + XPackageInformationProvider xPackageInformationProvider = PackageInformationProvider.get(xContext); + String location = xPackageInformationProvider.getPackageLocation("org.libreoffice.example.starterproject"); + Object oTransformer; + try { + oTransformer = xContext.getServiceManager().createInstanceWithContext("com.sun.star.util.URLTransformer", xContext); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + XURLTransformer xTransformer = (XURLTransformer)UnoRuntime.queryInterface(XURLTransformer.class, oTransformer); + com.sun.star.util.URL[] oURL = new com.sun.star.util.URL[1]; + oURL[0] = new com.sun.star.util.URL(); + oURL[0].Complete = location + "/" + file; + xTransformer.parseStrict(oURL); + URL url; + try { + url = new URL(oURL[0].Complete); + } catch (MalformedURLException e1) { + return null; + } + File f; + try { + f = new File(url.toURI()); + } catch (URISyntaxException e1) { + return null; + } + return f; + } + +} diff --git a/src/main/java/pro/litvinovg/docsettings/Localizer.java b/src/main/java/pro/litvinovg/docsettings/Localizer.java new file mode 100644 index 0000000..61bf112 --- /dev/null +++ b/src/main/java/pro/litvinovg/docsettings/Localizer.java @@ -0,0 +1,62 @@ +package pro.litvinovg.docsettings; + +import java.io.UnsupportedEncodingException; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.ResourceBundle; + +public class Localizer { + + private Locale currentLocale; + private String resourceBundle = "pro.litvinovg.docsettings.localizations.docsettings"; + private ResourceBundle translations; + private boolean isOld = false; + private static Localizer localizer = null; + private Localizer() { + currentLocale = Locale.getDefault(); + translations = ResourceBundle.getBundle(resourceBundle, currentLocale); + this.isOld = isOldJava(); + } + + public static Localizer getInstance() { + if (localizer != null) { + return localizer; + } + localizer = new Localizer(); + return localizer; + } + public static String get(String name) { + Localizer.getInstance(); + return localizer.getTranslation(name); + } + + public String getTranslation(String name) { + if (translations != null && translations.containsKey(name)) { + String translation = translations.getString(name); + if (isOld) { + try { + return new String(translation.getBytes("ISO-8859-1"), "UTF-8"); + } catch(UnsupportedEncodingException e) { + e.printStackTrace(); + } + } else { + return translation; + } + } + return name; + + } + private boolean isOldJava() { + try { + String versionString = System.getProperty("java.class.version"); + Float version = Float.parseFloat(versionString); + if (version < 53) { + return true; + } + } catch (Exception e) { + System.out.println(e.getLocalizedMessage()); + } + return false; + } +} diff --git a/src/main/java/pro/litvinovg/docsettings/RegistrationHandler.classes b/src/main/java/pro/litvinovg/docsettings/RegistrationHandler.classes new file mode 100644 index 0000000..53818d7 --- /dev/null +++ b/src/main/java/pro/litvinovg/docsettings/RegistrationHandler.classes @@ -0,0 +1 @@ +pro.litvinovg.docsettings.DocSettingsExtension \ No newline at end of file diff --git a/src/main/java/pro/litvinovg/docsettings/RegistrationHandler.java b/src/main/java/pro/litvinovg/docsettings/RegistrationHandler.java new file mode 100644 index 0000000..a0adc3e --- /dev/null +++ b/src/main/java/pro/litvinovg/docsettings/RegistrationHandler.java @@ -0,0 +1,175 @@ +/************************************************************************* + * + * The Contents of this file are made available subject to the terms of + * either of the GNU Lesser General Public License Version 2.1 + * + * Sun Microsystems Inc., October, 2000 + * + * + * GNU Lesser General Public License Version 2.1 + * ============================================= + * Copyright 2000 by Sun Microsystems, Inc. + * 901 San Antonio Road, Palo Alto, CA 94303, USA + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + * + * The Initial Developer of the Original Code is: Sun Microsystems, Inc.. + * + * Copyright: 2002 by Sun Microsystems, Inc. + * + * All Rights Reserved. + * + * Contributor(s): Cedric Bosdonnat + * + * + ************************************************************************/ +package pro.litvinovg.docsettings; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.LineNumberReader; +import java.lang.reflect.Method; +import java.util.ArrayList; + +import com.sun.star.lang.XSingleComponentFactory; +import com.sun.star.registry.XRegistryKey; + +/** + * Component main registration class. + * + *

This class should not be modified.

+ * + * @author Cedric Bosdonnat aka. cedricbosdo + * + */ +public class RegistrationHandler { + + /** + * Get a component factory for the implementations handled by this class. + * + *

This method calls all the methods of the same name from the classes listed + * in the RegistrationHandler.classes file. This method + * should not be modified.

+ * + * @param pImplementationName the name of the implementation to create. + * + * @return the factory which can create the implementation. + */ + public static XSingleComponentFactory __getComponentFactory(String sImplementationName ) { + XSingleComponentFactory xFactory = null; + + Class[] classes = findServicesImplementationClasses(); + + int i = 0; + while (i < classes.length && xFactory == null) { + Class clazz = classes[i]; + if ( sImplementationName.equals( clazz.getCanonicalName() ) ) { + try { + Class[] getTypes = new Class[]{String.class}; + Method getFactoryMethod = clazz.getMethod("__getComponentFactory", getTypes); + Object o = getFactoryMethod.invoke(null, sImplementationName); + xFactory = (XSingleComponentFactory)o; + } catch (Exception e) { + // Nothing to do: skip + System.err.println("Error happened"); + e.printStackTrace(); + } + } + i++; + } + return xFactory; + } + + /** + * Writes the services implementation informations to the UNO registry. + * + *

This method calls all the methods of the same name from the classes listed + * in the RegistrationHandler.classes file. This method + * should not be modified.

+ * + * @param pRegistryKey the root registry key where to write the informations. + * + * @return true if the informations have been successfully written + * to the registry key, false otherwise. + */ + public static boolean __writeRegistryServiceInfo(XRegistryKey xRegistryKey ) { + + Class[] classes = findServicesImplementationClasses(); + + boolean success = true; + int i = 0; + while (i < classes.length && success) { + Class clazz = classes[i]; + try { + Class[] writeTypes = new Class[]{XRegistryKey.class}; + Method getFactoryMethod = clazz.getMethod("__writeRegistryServiceInfo", writeTypes); + Object o = getFactoryMethod.invoke(null, xRegistryKey); + success = success && ((Boolean)o).booleanValue(); + } catch (Exception e) { + success = false; + e.printStackTrace(); + } + i++; + } + return success; + } + + /** + * @return all the UNO implementation classes. + */ + private static Class[] findServicesImplementationClasses() { + + ArrayList classes = new ArrayList(); + + InputStream in = RegistrationHandler.class.getResourceAsStream("RegistrationHandler.classes"); + LineNumberReader reader = new LineNumberReader(new InputStreamReader(in)); + + try { + String line = reader.readLine(); + while (line != null) { + if (!line.equals("")) { + line = line.trim(); + try { + Class clazz = Class.forName(line); + + Class[] writeTypes = new Class[]{XRegistryKey.class}; + Class[] getTypes = new Class[]{String.class}; + + Method writeRegMethod = clazz.getMethod("__writeRegistryServiceInfo", writeTypes); + Method getFactoryMethod = clazz.getMethod("__getComponentFactory", getTypes); + + if (writeRegMethod != null && getFactoryMethod != null) { + classes.add(clazz); + } + + } catch (Exception e) { + e.printStackTrace(); + } + } + line = reader.readLine(); + } + } catch (IOException e) { + e.printStackTrace(); + } finally { + try { + reader.close(); + in.close(); + } catch (Exception e) {}; + } + + return classes.toArray(new Class[classes.size()]); + } +} diff --git a/src/main/java/pro/litvinovg/docsettings/StandaloneApplication.java b/src/main/java/pro/litvinovg/docsettings/StandaloneApplication.java new file mode 100644 index 0000000..ca4bc98 --- /dev/null +++ b/src/main/java/pro/litvinovg/docsettings/StandaloneApplication.java @@ -0,0 +1,27 @@ +package pro.litvinovg.docsettings; + +import java.io.File; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import pro.litvinovg.docsettings.document.ODFDocument; +import pro.litvinovg.docsettings.settings.Backups; + +public class StandaloneApplication { + private static final Logger logger = LoggerFactory.getLogger(StandaloneApplication.class); + + public static void main(String args[]) { + if (args.length == 0 ) { + logger.debug("Error. No arguments found. Exit."); + return; + } + final String filePath = args[0]; + File file = new File(filePath); + if (!file.exists()) { + logger.debug("Error. File doesn't exist. Exit."); + return; + } + + } +} diff --git a/src/main/java/pro/litvinovg/docsettings/document/ODFDocument.java b/src/main/java/pro/litvinovg/docsettings/document/ODFDocument.java new file mode 100644 index 0000000..4494a42 --- /dev/null +++ b/src/main/java/pro/litvinovg/docsettings/document/ODFDocument.java @@ -0,0 +1,357 @@ +package pro.litvinovg.docsettings.document; + +import static pro.litvinovg.docsettings.settings.StorageUtils.getDocFromString; +import static pro.litvinovg.docsettings.settings.StorageUtils.getSettingsElement; + +import java.awt.EventQueue; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +import org.apache.tika.Tika; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import com.sun.star.beans.PropertyValue; +import com.sun.star.beans.XPropertyContainer; +import com.sun.star.beans.XPropertySet; +import com.sun.star.beans.XPropertySetInfo; +import com.sun.star.chart.TimeUnit; +import com.sun.star.document.XDocumentProperties; +import com.sun.star.document.XDocumentPropertiesSupplier; +import com.sun.star.frame.XComponentLoader; +import com.sun.star.frame.XDesktop; +import com.sun.star.frame.XDispatchHelper; +import com.sun.star.frame.XDispatchProvider; +import com.sun.star.frame.XModel; +import com.sun.star.frame.XStorable; +import com.sun.star.lang.XComponent; +import com.sun.star.lang.XMultiComponentFactory; +import com.sun.star.lang.XServiceInfo; +import com.sun.star.text.XTextDocument; +import com.sun.star.uno.UnoRuntime; +import com.sun.star.uno.XComponentContext; +import com.sun.star.util.XModifiable; + +import pro.litvinovg.docsettings.DialogHelper; +import pro.litvinovg.docsettings.Localizer; +import pro.litvinovg.docsettings.gui.MainWindow; +import pro.litvinovg.docsettings.settings.Backups; +import pro.litvinovg.docsettings.settings.ODFSetting; +import pro.litvinovg.docsettings.settings.SettingFactory; +import pro.litvinovg.docsettings.settings.Settings; +import pro.litvinovg.docsettings.settings.SettingsComparator; + +public class ODFDocument { + + + private static final Logger logger = LoggerFactory.getLogger(ODFDocument.class); + + private XComponentContext context = null; + private XDesktop xDesktop = null; + private XMultiComponentFactory multiComponentFactory = null; + private XComponent currentDocument = null; + private String fileName = ""; + public static final String SETTINGS = "settings.xml"; + public static final String META = "meta.xml"; + private Settings currentSettings = null; + + private XStorable storable = null; + + private XComponentLoader loader = null; + + private String url = null; + + public Settings getCurrentSettings() { + if (currentSettings.getSize() == 0) { + readSettings(); + } + return currentSettings; + } + + private String getSettingsContent() { + return readInternalFile(SETTINGS); + } + + private void setSettingsContent(String settingsContent) { + writeInternalFile(SETTINGS, settingsContent); + } + + private String getMetaContent() { + return readInternalFile(META); + } + + /* + * private void setMetaContent(String newMetaContent) { writeInternalFile(META, + * newMetaContent); } + */ + + public ODFDocument(XComponentContext componentContext) throws Exception { + this.currentSettings = new Settings(); + if (componentContext != null) { + context = componentContext; + multiComponentFactory = context.getServiceManager(); + + Object oDesktop = multiComponentFactory.createInstanceWithContext("com.sun.star.frame.Desktop", context); + xDesktop = UnoRuntime.queryInterface(XDesktop.class, oDesktop); + currentDocument = xDesktop.getCurrentComponent(); + if (currentDocument == null) { + logger.debug("xDesktop.getCurrentComponent() returned null"); + throw new Exception(Localizer.get("error_document_is_null")); + } + XModel model = UnoRuntime.queryInterface(XModel.class, currentDocument); + if (!isSupported(currentDocument, "com.sun.star.text.TextDocument")) { + throw new Exception(Localizer.get("error_document_type_is_not_supported")); + } + loader = UnoRuntime.queryInterface(XComponentLoader.class, xDesktop); + XTextDocument textDoc = UnoRuntime.queryInterface(XTextDocument.class, currentDocument); + if (textDoc == null) { + throw new Exception("Error. TextDoc is null."); + } + storable = UnoRuntime.queryInterface(XStorable.class, textDoc); + + if (storable == null || storable.isReadonly()) { + throw new Exception(Localizer.get("error_document_is_ro")); + } + if (!storable.hasLocation() || storable.getLocation().isEmpty()) { + throw new Exception(Localizer.get("error_document_is_not_saved")); + } + + url = model.getURL(); + URI uri = new URI(url); + File file = new File(uri); + if (!file.exists()) { + throw new Exception(Localizer.get("error_document_is_null")); + } + if (!file.canRead()) { + throw new Exception(Localizer.get("error_document_is_ro")); + } + if (!"application/vnd.oasis.opendocument.text".equals(getMimeType(file))) { + throw new Exception(Localizer.get("error_document_type_is_not_supported")); + } + fileName = file.getAbsolutePath(); + } + } + + private String getMimeType(File file) { + Tika tika = new Tika(); + InputStream is; + String mime = ""; + try { + is = new FileInputStream(file); + mime = tika.detect(is); + } catch (IOException e) { + logger.debug("Ops", e); + } + return mime; + } + + public void restoreSettings(Settings settings) { + closeDocument(); + restoreFrom(settings); + reOpenDoc(); + } + + public XPropertyContainer getUserDefinedProps(){ + XDocumentPropertiesSupplier docPropertiesSupplier = UnoRuntime.queryInterface(XDocumentPropertiesSupplier.class, currentDocument); + XDocumentProperties docProperties = docPropertiesSupplier.getDocumentProperties(); + XPropertyContainer userDefinedProperties = docProperties.getUserDefinedProperties(); + return userDefinedProperties; + } + + public void readSettings() { + flushSettings(); + try { + Node configSettings = getSettingsElement(getDocFromString(getSettingsContent())); + NodeList settingNodes = configSettings.getChildNodes(); + for (int i = 0; i < settingNodes.getLength(); i++) { + Node settingNode = settingNodes.item(i); + ODFSetting setting = SettingFactory.create(settingNode); + currentSettings.add(setting); + } + } catch (Exception e) { + logger.debug(e.getMessage()); + logger.debug("Ops!",e); + } + } + + private void flushSettings() { + currentSettings.clear(); + } + + public void restoreFrom(Settings settings) { + String newSettingsContent; + try { + newSettingsContent = settings.toSettingsContent(getSettingsContent()); + writeInternalFile(SETTINGS, newSettingsContent); + + } catch (Exception e) { + logger.debug("Restore failed", e); + } + } + + public void readBackups(Backups backups) { + backups.readBackupsFromMetadata(getUserDefinedProps()); + } + + public boolean hasBackups(Backups backups) { + return !backups.isEmpty(); + } + + public void writeBackups(Backups backups) { + backups.writeBackupsToMetadata(getUserDefinedProps()); + } + + public String getFileName() { + return fileName; + } + + public void printText(String text) { + DialogHelper.showErrorMessage(context, null, text); + } + + public void printFileName() { + DialogHelper.showErrorMessage(context, null, fileName); + } + + private String readInternalFile(String internalFile) { + Map env = new HashMap<>(); + String result = ""; + FileSystem zipfs = null; + env.put("create", "true"); + Path path = Paths.get(fileName); + URI uri = URI.create("jar:" + path.toUri()); + try { + zipfs = FileSystems.newFileSystem(uri, env); + Path settingsPath = zipfs.getPath(internalFile); + byte[] encoded = Files.readAllBytes(settingsPath); + result = new String(encoded, "UTF-8"); + } catch (IOException e) { + logger.debug(e.getMessage()); + logger.debug("Ops!",e); + e.printStackTrace(); + } finally { + try { + zipfs.close(); + } catch (IOException e) { + logger.debug("Ops", e); + e.printStackTrace(); + } + } + return result; + } + + private void writeInternalFile(String internalFile, String contents) { + Map env = new HashMap<>(); + BufferedWriter writer = null; + FileSystem zipfs = null; + env.put("create", "true"); + Path path = Paths.get(fileName); + URI uri = URI.create("jar:" + path.toUri()); + try { + zipfs = FileSystems.newFileSystem(uri, env); + Path internalPath = zipfs.getPath(internalFile); + writer = Files.newBufferedWriter(internalPath, StandardCharsets.UTF_8, StandardOpenOption.WRITE); + writer.write(contents); + } catch (IOException e) { + logger.debug(e.getMessage()); + logger.debug("Ops!",e); + } finally { + try { + writer.close(); + zipfs.close(); + } catch (IOException e) { + logger.debug(e.getLocalizedMessage()); + logger.debug("Ops!",e); + } + } + } + + + public void reOpenDoc(){ + try { + XComponent doc = loader.loadComponentFromURL(url, "_default", 0, new PropertyValue[0]); + } catch (Exception e) { + System.out.println("Failed to open the document"); + } + ; + } + + public void closeDocument() { + if (context != null) { + executedispatch("CloseDoc", new PropertyValue[0]); + } else { + logger.debug("Can't close document. No context."); + } + } + + public void reloadDocument() { + if (context != null) { + executedispatch("Reload", new PropertyValue[0]); + } else { + logger.debug("Can't reload document. No context."); + } + } + + private boolean isSupported(Object object, String service) { + XServiceInfo info = UnoRuntime.queryInterface(XServiceInfo.class, object); + if (info == null) { + return false; + } + return info.supportsService(service); + } + + private boolean executedispatch( String cmd, PropertyValue[] props) { + XDispatchHelper helper = createInstance(XDispatchHelper.class, "com.sun.star.frame.DispatchHelper"); + if (helper == null) { + logger.debug("Could not create dispatch helper for command " + cmd); + return false; + } + try { + Object oDesktop = context.getServiceManager().createInstanceWithContext("com.sun.star.frame.Desktop", context); + XDesktop xDesktop = UnoRuntime.queryInterface(XDesktop.class, oDesktop); + XDispatchProvider provider = UnoRuntime.queryInterface(XDispatchProvider.class, xDesktop.getCurrentFrame()); + helper.executeDispatch(provider, ".uno:" + cmd, "", 0, props); + return true; + } catch (java.lang.Exception e) { + logger.debug("Could not dispatch '" + cmd + "':\n " + e); + } + return false; + } + + private T createInstance(Class aType, String serviceName) { + if ((context == null)) { + logger.debug("No libreoffice context. Cant create instance of " + aType.toString()); + return null; + } + T interfaceObj = null; + try { + Object o = context.getServiceManager().createInstanceWithContext(serviceName, context); + interfaceObj = UnoRuntime.queryInterface(aType, o); + } catch (Exception e) { + logger.debug("Couldn't create interface for '" + serviceName + "': " + e); + } + return interfaceObj; + } + + public boolean compareWithBackup(Settings backedUpSettings) { + return SettingsComparator.isEqual(getCurrentSettings(), backedUpSettings); + } + +} diff --git a/src/main/java/pro/litvinovg/docsettings/exceptions/MalformedXmlException.java b/src/main/java/pro/litvinovg/docsettings/exceptions/MalformedXmlException.java new file mode 100644 index 0000000..c633172 --- /dev/null +++ b/src/main/java/pro/litvinovg/docsettings/exceptions/MalformedXmlException.java @@ -0,0 +1,10 @@ +package pro.litvinovg.docsettings.exceptions; + +public class MalformedXmlException extends Exception{ + + private static final long serialVersionUID = 1L; + + public MalformedXmlException(String message) { + super(message); + } +} diff --git a/src/main/java/pro/litvinovg/docsettings/gui/BackupPanel.java b/src/main/java/pro/litvinovg/docsettings/gui/BackupPanel.java new file mode 100644 index 0000000..89d4107 --- /dev/null +++ b/src/main/java/pro/litvinovg/docsettings/gui/BackupPanel.java @@ -0,0 +1,202 @@ +package pro.litvinovg.docsettings.gui; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.DefaultListModel; +import javax.swing.DefaultListSelectionModel; +import javax.swing.GroupLayout; +import javax.swing.GroupLayout.Alignment; +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.LayoutStyle.ComponentPlacement; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import pro.litvinovg.docsettings.ExtensionConfig; +import pro.litvinovg.docsettings.Localizer; +import pro.litvinovg.docsettings.document.ODFDocument; +import pro.litvinovg.docsettings.settings.Backups; +import pro.litvinovg.docsettings.settings.Settings; +import javax.swing.JCheckBox; +import javax.swing.JFrame; +import javax.swing.SwingConstants; + +public class BackupPanel extends JPanel { + + private ExtensionConfig config; + private ODFDocument document; + private Backups backups; + private JButton btn_close; + private JButton btn_restore; + private JButton btn_create; + private JButton btn_delete; + private JList lst_backups; + private DefaultListModel backupListModel; + private static final Logger logger = LoggerFactory.getLogger(BackupPanel.class); + private JCheckBox cb_stick_latest_backup; + private JFrame frame; + + + public BackupPanel(JFrame frame, ODFDocument document, Backups backups) { + this.frame = frame; + this.backups = backups; + this.config = ExtensionConfig.getInstance(); + this.document = document; + initComponents(); + initEvents(); + } + + private void initEvents() { + onCreateButtonClick(); + onDeleteButtonClick(); + onRestoreButtonClick(); + onCloseButtonClick(); + onCheckBoxClick(); + } + + private void initComponents() { + + backupListModel = new DefaultListModel(); + populateList(); + lst_backups = new JList(backupListModel); + lst_backups.setSelectionMode(DefaultListSelectionModel.SINGLE_SELECTION); + + JLabel lb_backups = new JLabel(Localizer.get("backup_panel_description")); + + btn_delete = new JButton(Localizer.get("delete_backup_button")); + btn_create = new JButton(Localizer.get("create_backup_button")); + btn_restore = new JButton(Localizer.get("restore_button")); + btn_close = new JButton(Localizer.get("close_window_button")); + + cb_stick_latest_backup = new JCheckBox(Localizer.get("stick_latest_backup_cb_label")); + cb_stick_latest_backup.setSelected(config.getAutoRestore()); + cb_stick_latest_backup.setVerticalAlignment(SwingConstants.TOP); + + GroupLayout groupLayout = new GroupLayout(this); + groupLayout.setHorizontalGroup( + groupLayout.createParallelGroup(Alignment.LEADING) + .addGroup(groupLayout.createSequentialGroup() + .addGap(43) + .addGroup(groupLayout.createParallelGroup(Alignment.LEADING, false) + .addComponent(lb_backups, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(groupLayout.createSequentialGroup() + .addComponent(lst_backups, GroupLayout.PREFERRED_SIZE, 354, GroupLayout.PREFERRED_SIZE) + .addGap(26) + .addGroup(groupLayout.createParallelGroup(Alignment.LEADING, false) + .addComponent(cb_stick_latest_backup, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(btn_close, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(btn_delete, Alignment.TRAILING, GroupLayout.DEFAULT_SIZE, 269, Short.MAX_VALUE) + .addComponent(btn_create, Alignment.TRAILING, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(btn_restore, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))) + .addContainerGap(288, Short.MAX_VALUE)) + ); + groupLayout.setVerticalGroup( + groupLayout.createParallelGroup(Alignment.LEADING) + .addGroup(groupLayout.createSequentialGroup() + .addContainerGap() + .addComponent(lb_backups, GroupLayout.PREFERRED_SIZE, 15, GroupLayout.PREFERRED_SIZE) + .addGap(14) + .addGroup(groupLayout.createParallelGroup(Alignment.LEADING) + .addComponent(lst_backups, GroupLayout.PREFERRED_SIZE, 384, GroupLayout.PREFERRED_SIZE) + .addGroup(groupLayout.createSequentialGroup() + .addComponent(btn_delete) + .addGap(18) + .addComponent(btn_create) + .addGap(18) + .addComponent(btn_restore) + .addGap(33) + .addComponent(cb_stick_latest_backup, GroupLayout.PREFERRED_SIZE, 83, GroupLayout.PREFERRED_SIZE) + .addGap(132) + .addComponent(btn_close))) + .addContainerGap(74, Short.MAX_VALUE)) + ); + setLayout(groupLayout); + } + + private void populateList() { + backupListModel.clear(); + int i = 0; + for(String date : backups.getBackupTimeLabels()) { + backupListModel.add(i,date); + i++; + } + } + + private void onCreateButtonClick() { + btn_create.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent arg0) { + if (document == null) { + return; + } + backups.create(document.getCurrentSettings()); + document.writeBackups(backups); + populateList(); + } + }); + } + + private void onDeleteButtonClick() { + btn_delete.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent arg0) { + if (lst_backups.isSelectionEmpty()) { + return; + } + if (document == null) { + return; + } + String selected = (String) lst_backups.getSelectedValue(); + backups.removeByDateTimeLabel(selected); + document.writeBackups(backups); + populateList(); + } + }); + } + + private void onRestoreButtonClick() { + btn_restore.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent arg0) { + if (lst_backups.isSelectionEmpty()) { + JOptionPane.showMessageDialog(frame, Localizer.get("no_backup_selected")); + return; + } + if (document == null) { + JOptionPane.showMessageDialog(frame, Localizer.get("error_document_not_found")); + return; + } + String selected = (String) lst_backups.getSelectedValue(); + Settings backup; + try { + backup = backups.getByDateTimeLabel(selected); + document.restoreSettings(backup); + MainWindow.getSingleFrame().setVisible(false); + MainWindow.getSingleFrame().dispose(); + } catch (Exception e) { + logger.debug("Restore failed", e); + JOptionPane.showMessageDialog(frame, Localizer.get("restore_failed \n" + e.getLocalizedMessage())); + } + } + }); + } + + private void onCloseButtonClick() { + btn_close.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + MainWindow.getSingleFrame().setVisible(false); + MainWindow.getSingleFrame().dispose(); + } + }); + } + + private void onCheckBoxClick() { + cb_stick_latest_backup.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + config.setAutoRestore(cb_stick_latest_backup.isSelected()); + } + }); + } +} diff --git a/src/main/java/pro/litvinovg/docsettings/gui/ChangedSettings.java b/src/main/java/pro/litvinovg/docsettings/gui/ChangedSettings.java new file mode 100644 index 0000000..75013ba --- /dev/null +++ b/src/main/java/pro/litvinovg/docsettings/gui/ChangedSettings.java @@ -0,0 +1,42 @@ +package pro.litvinovg.docsettings.gui; + +import java.awt.BorderLayout; +import java.awt.EventQueue; + +import javax.swing.JFrame; +import javax.swing.JPanel; +import javax.swing.border.EmptyBorder; + +public class ChangedSettings extends JFrame { + + private JPanel contentPane; + + /** + * Launch the application. + */ + public static void main(String[] args) { + EventQueue.invokeLater(new Runnable() { + public void run() { + try { + ChangedSettings frame = new ChangedSettings(); + frame.setVisible(true); + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } + + /** + * Create the frame. + */ + public ChangedSettings() { + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + setBounds(100, 100, 450, 300); + contentPane = new JPanel(); + contentPane.setBorder(new EmptyBorder(5, 5, 5, 5)); + contentPane.setLayout(new BorderLayout(0, 0)); + setContentPane(contentPane); + } + +} diff --git a/src/main/java/pro/litvinovg/docsettings/gui/MainWindow.java b/src/main/java/pro/litvinovg/docsettings/gui/MainWindow.java new file mode 100644 index 0000000..736a21e --- /dev/null +++ b/src/main/java/pro/litvinovg/docsettings/gui/MainWindow.java @@ -0,0 +1,123 @@ +package pro.litvinovg.docsettings.gui; + +import java.awt.EventQueue; +import java.awt.Toolkit; + +import javax.swing.JFrame; +import javax.swing.JOptionPane; + +import java.awt.GridLayout; +import javax.swing.JTabbedPane; +import javax.swing.UIManager; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.sun.star.uno.XComponentContext; + +import pro.litvinovg.docsettings.DialogHelper; +import pro.litvinovg.docsettings.DocSettingsExtension; +import pro.litvinovg.docsettings.ExtensionConfig; +import pro.litvinovg.docsettings.Localizer; +import pro.litvinovg.docsettings.document.ODFDocument; +import pro.litvinovg.docsettings.settings.Backups; + +import javax.swing.JPanel; +import java.awt.BorderLayout; +import java.awt.FlowLayout; +import javax.swing.BoxLayout; +import javax.swing.GroupLayout; +import javax.swing.GroupLayout.Alignment; + +import javax.swing.JList; + +public class MainWindow extends JFrame { + + private static final long serialVersionUID = 1L; + private static JFrame frame = null; + private Localizer localizer; + private ODFDocument document; + private Backups backups; + private static final Logger logger = LoggerFactory.getLogger(MainWindow.class); + + + public static void main(String[] args) { + EventQueue.invokeLater(new Runnable() { + public void run() { + try { + MainWindow window = new MainWindow(); + frame = window; + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } + + public static void runGUI(XComponentContext context) { + + if (frame != null) { + frame.dispose(); + } + try { + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + } catch (Throwable e) { + String message = e.getLocalizedMessage(); + JOptionPane.showMessageDialog(frame, message); + } + EventQueue.invokeLater(new Runnable() { + public void run() { + try { + MainWindow window = new MainWindow(context); + frame = window; + } catch (Throwable e) { + DialogHelper.showErrorMessage(context, null, e.getLocalizedMessage()); + logger.debug("Ops", e); + } + } + }); + + } + + public static JFrame getSingleFrame() { + return frame; + } + + public MainWindow() { + backups = new Backups(); + initialize(); + } + + public MainWindow(XComponentContext context) throws Exception { + document = new ODFDocument(context); + backups = new Backups(); + document.readSettings(); + document.readBackups(backups); + initialize(); + } + + /** + * Initialize the contents of the frame. + */ + private void initialize() { + setIconImage(Toolkit.getDefaultToolkit().getImage(MainWindow.class.getResource("/pro/litvinovg/docsettings/resources/icon.png"))); + setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + getContentPane().setLayout(new GridLayout(1, 0, 0, 0)); + localizer = Localizer.getInstance(); + this.setBounds(100, 100, 750, 500); + this.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + this.setVisible(true); + + JTabbedPane tabbedPane = new JTabbedPane(JTabbedPane.TOP); + getContentPane().add(tabbedPane); + + JPanel backupPanel = new BackupPanel(this, document, backups); + tabbedPane.addTab(localizer.getTranslation("backups_tab_name"), null, backupPanel, null); + + //JPanel settingsPanel = new SettingsPanel(this, document); + //tabbedPane.addTab(localizer.getTranslation("settings_tab_name"), null, settingsPanel, null); + + + + } +} diff --git a/src/main/java/pro/litvinovg/docsettings/gui/SettingsPanel.java b/src/main/java/pro/litvinovg/docsettings/gui/SettingsPanel.java new file mode 100644 index 0000000..49eff11 --- /dev/null +++ b/src/main/java/pro/litvinovg/docsettings/gui/SettingsPanel.java @@ -0,0 +1,33 @@ +package pro.litvinovg.docsettings.gui; + +import javax.swing.JPanel; + +import pro.litvinovg.docsettings.ExtensionConfig; +import pro.litvinovg.docsettings.Localizer; +import pro.litvinovg.docsettings.document.ODFDocument; +import java.awt.GridLayout; +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JComboBox; +import javax.swing.JFrame; + +public class SettingsPanel extends JPanel { + + private ExtensionConfig config; + private Localizer localizer; + private ODFDocument document; + private JFrame frame; + + public SettingsPanel(JFrame mainWindow, ODFDocument document) { + this.frame = mainWindow; + this.document = document; + this.config = ExtensionConfig.getInstance(); + this.localizer = Localizer.getInstance(); + setLayout(new GridLayout(1, 0, 0, 0)); + initComponents(); + } + + private void initComponents() { + + } +} diff --git a/src/main/java/pro/litvinovg/docsettings/localizations/docsettings.properties b/src/main/java/pro/litvinovg/docsettings/localizations/docsettings.properties new file mode 100644 index 0000000..c548a78 --- /dev/null +++ b/src/main/java/pro/litvinovg/docsettings/localizations/docsettings.properties @@ -0,0 +1,15 @@ +close_window_button = Close +restore_button = Restore from backup +create_backup_button = Create backup +delete_backup_button = Delete backup +settings_tab_name = Settings +backups_tab_name = Backups +backup_panel_description = Manage document settings backups +no_timestamp_found_error = No backup dates found +stick_latest_backup_cb_label = Block settings +no_backup_selected = Select backup and try again +error_document_not_found = Document not found. +error_document_is_not_saved = Document is not saved. Save document before proceeding. +error_document_is_ro = Document opened in read only mode. +error_document_is_null = Document not found +error_document_type_is_not_supported = Document type is not supported. \ No newline at end of file diff --git a/src/main/java/pro/litvinovg/docsettings/localizations/docsettings_ru.properties b/src/main/java/pro/litvinovg/docsettings/localizations/docsettings_ru.properties new file mode 100644 index 0000000..9561147 --- /dev/null +++ b/src/main/java/pro/litvinovg/docsettings/localizations/docsettings_ru.properties @@ -0,0 +1,15 @@ +close_window_button = Закрыть +restore_button = Восстановить из резервной копии +create_backup_button = Создать резервную копию +delete_backup_button = Удалить резервную копию +settings_tab_name = Настройки +backups_tab_name = Резервные копии +backup_panel_description = Управление резервными копиями настроек документов +no_timestamp_found_error = Не найдено дат резервных копий +stick_latest_backup_cb_label = Заблокировать изменение настроек +no_backup_selected = Выберите резервную копию и попробуйте снова +error_document_not_found = Ошибка. Документ не найден. +error_document_is_not_saved = Документ не сохранен. Сохраните документ и попробуйте снова. +error_document_is_ro = Документ открыт в режиме только для чтения. +error_document_is_null = Ошибка. Документ не найден. +error_document_type_is_not_supported = Тип документа не поддерживается. Сохраните документ в формате ODT. \ No newline at end of file diff --git a/src/main/java/pro/litvinovg/docsettings/resources/icon.png b/src/main/java/pro/litvinovg/docsettings/resources/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..e89adc0733d7497413e3c7d601a88942fb7e124b GIT binary patch literal 13321 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 backups = null; + + public Backups() { + backups = new TreeMap(); + } + + public void add(Long unixTimeStamp, Settings backup) { + backups.put(unixTimeStamp, backup); + } + public void clear() { + backups.clear(); + } + public boolean isEmpty() { + return backups.isEmpty(); + } + + public void create(Settings currentSettings) { + backups.put( getUnixTime(), currentSettings); + } + + public Settings getBackupByTimeStamp(Long timeStamp) { + return backups.get(timeStamp); + } + + public Settings getLatest() { + return backups.lastEntry().getValue(); + } + + public Set getTimeStamps() { + return backups.keySet(); + } + + public void readBackupsFromMetadata(XPropertyContainer userDefinedProps) { + clear(); + TreeMap> backupMap = getBackupMap(userDefinedProps); + Map serializedBackups = combineBackupStrings(backupMap); + for (Entry entry : serializedBackups.entrySet()) { + Long unixTimeStamp = entry.getKey(); + String backupString = entry.getValue(); + if (!backupString.isEmpty()) { + backupString = decodeBase64(backupString); + readBackup(unixTimeStamp, backupString); + } + } + } + + public void writeBackupsToMetadata(XPropertyContainer userDefinedProps) { + cleanBackupMetadata(userDefinedProps); + for (Entry backup : backups.entrySet()) { + try { + addBackupEntries( userDefinedProps, backup); + } catch (MalformedXmlException | ParserConfigurationException | TransformerException e) { + logger.debug("Ops", e); + } + } + } + + private void cleanBackupMetadata(XPropertyContainer userDefinedProps) { + XPropertySet propertySet = UnoRuntime.queryInterface(XPropertySet.class, userDefinedProps); + XPropertySetInfo propSetInfo = propertySet.getPropertySetInfo(); + Property[] props = propSetInfo.getProperties(); + for (int i = 0; i < props.length; i++) { + try { + String name = props[i].Name; + if (name.startsWith(BACKUP_NAME_PREFIX)) { + userDefinedProps.removeProperty(name); + } + } catch (Exception e) { + logger.debug("Ops", e); + } + } + } + + private void addBackupEntries(XPropertyContainer userDefinedProps, Entry backup) throws MalformedXmlException, ParserConfigurationException, TransformerException { + String backupString = serializeSettingsXML(backup.getValue()); + backupString = encodeBase64(backupString); + List parts = splitByOOMaxLength(backupString); + for (int i = 0; i < parts.size();i++) { + String part = parts.get(i); + String name = formatBackupMetaName(backup.getKey(),i); + try { + userDefinedProps.addProperty(name, REMOVABLE_PROPERTY, part); + } catch (IllegalArgumentException e) { + logger.debug("Ops", e); + } catch (PropertyExistException e) { + //it is ok + logger.debug("Ops", e); + } catch (IllegalTypeException e) { + logger.debug("Ops", e); + } + + } + } + + private void readBackup(Long unixTimeStamp, String backupString) { + Document backupDoc; + try { + backupDoc = getDocFromString(backupString); + Element configSettings = (Element) getSettingsElement(backupDoc); + NodeList settingNodes = configSettings.getChildNodes(); + Settings backup = readSettingsFromNodeList(settingNodes); + add(unixTimeStamp,backup); + } catch (ParserConfigurationException | SAXException | IOException e) { + logger.debug("Ops", e); + } catch (Exception e) { + logger.debug("Ops", e); + } + } + + private TreeMap> getBackupMap(XPropertyContainer userDefinedProps) { + TreeMap> backupMap = new TreeMap>(); + + XPropertySet propertySet = UnoRuntime.queryInterface(XPropertySet.class, userDefinedProps); + XPropertySetInfo propSetInfo = propertySet.getPropertySetInfo(); + Property[] props = propSetInfo.getProperties(); + for (int i = 0; i < props.length; i++) { + try { + Property prop = props[i]; + String name = prop.Name; + if (name.startsWith(BACKUP_NAME_PREFIX)) { + Long timeStamp = Long.parseLong(getTimeStampFromString(name)); + Long order = Long.parseLong(getOrderFromString(name)); + String stringPart = propertySet.getPropertyValue(prop.Name).toString(); + Map backupParts = null; + if (backupMap.containsKey(timeStamp)) { + backupParts = backupMap.get(timeStamp); + } else { + backupParts = new TreeMap(); + backupMap.put(timeStamp, backupParts); + } + backupParts.put(order, stringPart); + } + //logger.debug("Prop name "+ prop.Name + " Prop value " + propertySet.getPropertyValue(prop.Name)); + } catch (UnknownPropertyException | WrappedTargetException e) { + logger.debug("Ops", e); + } + } + return backupMap; + } + + public void readBackupsFromXML(String metaContent) { + clear(); + try { + Document doc = getDocFromString(metaContent); + Map serializedBackups = getSerializedBackups(doc); + for (Entry entry : serializedBackups.entrySet()) { + Long unixTimeStamp = entry.getKey(); + String backupString = entry.getValue(); + if (!backupString.isEmpty()) { + backupString = decodeBase64(backupString); + Document backupDoc = getDocFromString(backupString); + Element configSettings = (Element) getSettingsElement(backupDoc); + NodeList settingNodes = configSettings.getChildNodes(); + Settings backup = readSettingsFromNodeList(settingNodes); + add(unixTimeStamp,backup); + } + } + } catch (Exception e) { + logger.debug(e.getMessage()); + logger.debug("Ops!",e); + } + } + + private Settings readSettingsFromNodeList(NodeList settingNodes) throws MalformedXmlException { + Settings settings = new Settings(); + for (int i = 0; i < settingNodes.getLength(); i++) { + Node settingNode = settingNodes.item(i); + ODFSetting setting = SettingFactory.create(settingNode); + settings.add(setting); + } + return settings; + } + + public String getNewMetaContent(String metaContent) { + String cleanedMeta = getCleanedMetaContent(metaContent); + String newMetaContent = ""; + try { + Document doc = getDocFromString(cleanedMeta); + for (Entry backup : backups.entrySet()) { + addXMLBackupEntries( doc, backup.getKey(), backup.getValue()); + } + newMetaContent = serializeDoc(doc); + } catch (Exception e) { + logger.debug(e.getMessage()); + logger.debug("Ops!",e); + } + return newMetaContent; + } + + + + private void addXMLBackupEntries(Document doc, Long timeStamp, Settings settings) throws MalformedXmlException, ParserConfigurationException, TransformerException { + String backupString = serializeSettingsXML(settings); + backupString = encodeBase64(backupString); + List parts = splitByOOMaxLength(backupString); + for (int i = 0; i < parts.size();i++) { + String part = parts.get(i); + addMetadataEntry(doc,timeStamp,part, i); + } + } + + private void addMetadataEntry(Document doc, Long timeStamp, String string, int i) throws MalformedXmlException { + Element element = doc.createElement("meta:user-defined"); + String name = formatBackupMetaName(timeStamp,i); + element.setAttribute("meta:name", name); + element.setTextContent(string); + Node metaRoot = getMetaRootElement(doc); + metaRoot.appendChild(element); + } + + private String formatBackupMetaName(Long timeStamp, int i) { + String result = BACKUP_NAME_PREFIX + timeStamp.toString() + "-" + i; + return result; + } + + private List splitByOOMaxLength(String text) { + List array = new ArrayList(); + int index = 0; + while (index < text.length()) { + array.add(text.substring(index, Math.min(index + OO_XML_MAX_LENGTH, text.length()))); + index += OO_XML_MAX_LENGTH; + } + return array; + } + + private String serializeSettingsXML(Settings settings) throws ParserConfigurationException, TransformerException { + Document doc = createNewDoc(); + Element root = doc.createElementNS("urn:oasis:names:tc:opendocument:xmlns:config:1.0","config:config-item-set"); + root.setAttribute("xmlns:config", "urn:oasis:names:tc:opendocument:xmlns:config:1.0"); + root.setAttribute("xmlns:ooo", "http://openoffice.org/2004/office"); + root.setAttribute("config:name", "ooo:configuration-settings"); + doc.appendChild(root); + appendSettingNodesToDoc(settings, doc, root); + String result = serializeDoc(doc); + return result; + } + + + private String getCleanedMetaContent(String metaContent) { + String cleanedMetaContent = ""; + try { + Document doc = getDocFromString(metaContent); + NodeList nodes = getBackupEntries(doc); + removeNodes(nodes); + cleanedMetaContent = serializeDoc(doc); + } catch (Exception e) { + logger.debug(e.getMessage()); + logger.debug("Ops!",e); + } + return cleanedMetaContent; + } + + + + private Map getSerializedBackups(Document doc) throws MalformedXmlException { + NodeList nodes = getBackupEntries(doc); + TreeMap> backupStringMap = sortBackupStrings(nodes); + Map result = combineBackupStrings(backupStringMap); + return result; + } + + private NodeList getBackupEntries(Document doc) throws MalformedXmlException { + XPathFactory xPathfactory = XPathFactory.newInstance(); + XPath xpath = xPathfactory.newXPath(); + XPathExpression expr; + NodeList nodes; + try { + expr = xpath.compile("//*[local-name() ='user-defined'][@*[local-name() ='name' and starts-with(.,'"+ BACKUP_NAME_PREFIX + "')]]"); + nodes = (NodeList) expr.evaluate(doc, XPathConstants.NODESET); + } catch (XPathExpressionException e) { + logger.debug(e.getLocalizedMessage()); + throw new MalformedXmlException("Xpath expression failed while looking for backup entries"); + } + return nodes; + } + + private Map combineBackupStrings(TreeMap> backupMap) { + TreeMap result = new TreeMap(); + for(Map.Entry> entry: backupMap.entrySet()) { + Long timeStamp = entry.getKey(); + Map parts = entry.getValue(); + String combinedString = ""; + for(Map.Entry part: parts.entrySet()) { + String serlializedString = part.getValue(); + combinedString += serlializedString; + } + result.put(timeStamp, combinedString); + } + return result; + } + + private TreeMap> sortBackupStrings(NodeList nodes) throws MalformedXmlException { + TreeMap> backupMap = new TreeMap>(); + for (int i = 0; i < nodes.getLength(); i++) { + Node node = nodes.item(i); + Long timeStamp = getTimeStampFromNode(node); + Long order = getOrderFromNode(node); + String stringPart = node.getTextContent(); + Map backupParts = null; + if (backupMap.containsKey(timeStamp)) { + backupParts = backupMap.get(timeStamp); + } else { + backupParts = new TreeMap(); + backupMap.put(timeStamp, backupParts); + } + backupParts.put(order, stringPart); + } + return backupMap; + } + + private Long getTimeStampFromNode(Node node) throws MalformedXmlException { + String name = node.getAttributes().getNamedItem("meta:name").getNodeValue(); + String ts = getTimeStampFromString(name); + Long timeStamp = null; + try { + timeStamp = Long.parseLong(ts); + } catch (NumberFormatException e) { + throw new MalformedXmlException("timestamp not found for metadata backup entry " + name); + } + return timeStamp; + } + + private String getTimeStampFromString(String name) { + return name.replaceAll(BACKUP_NAME_PREFIX, "").replaceAll("-[0-9]$",""); + } + + private Long getOrderFromNode(Node node) throws MalformedXmlException { + String name = node.getAttributes().getNamedItem("meta:name").getNodeValue(); + String or = getOrderFromString(name); + Long order; + try { + order = Long.parseLong(or); + } catch (NumberFormatException e) { + throw new MalformedXmlException("order not found for metadata backup entry " + name); + } + return order; + } + + private String getOrderFromString(String name) { + return name.replaceAll(BACKUP_NAME_PREFIX + "[0-9]+-", ""); + } + + private long getUnixTime() { + return System.currentTimeMillis() / 1000L; + } + + public String[] getBackupTimeLabels() { + String[] timeLabels = new String[backups.size()]; + int i = 0; + for(Long backupTime: backups.keySet()) { + timeLabels[i] = getDateTimeLabelsFromUnix(backupTime); + i++; + } + return timeLabels; + } + + private String getDateTimeLabelsFromUnix(Long timeStamp){ + ZoneId zoneId = TimeZone.getDefault().toZoneId(); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm:ss"); + final Instant instant = Instant.ofEpochSecond(timeStamp); + LocalDateTime time = LocalDateTime.ofInstant(instant, zoneId); + return formatter.format(time); + } + + private Long getUnixFromDateTimeLabels(String formattedTime){ + ZoneId zoneId = TimeZone.getDefault().toZoneId(); + DateTimeFormatter parseFormat = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm:ss"); + LocalDateTime time = LocalDateTime.parse(formattedTime, parseFormat); + return time.atZone(zoneId).toEpochSecond(); + } + + public void removeByDateTimeLabel(String dateTimeLabel) { + if (dateTimeLabel == null || dateTimeLabel.isEmpty()) { + return; + } + Long unix = getUnixFromDateTimeLabels(dateTimeLabel); + backups.remove(unix); + } + + public Settings getByDateTimeLabel(String dateTimeLabel) throws Exception { + if (dateTimeLabel == null || dateTimeLabel.isEmpty()) { + throw new Exception("date time not found"); + } + Long unix = getUnixFromDateTimeLabels(dateTimeLabel); + Settings result = backups.get(unix); + if (result == null) { + throw new Exception("backup not found"); + } + return result; + } + + +} diff --git a/src/main/java/pro/litvinovg/docsettings/settings/ComparatorResults.java b/src/main/java/pro/litvinovg/docsettings/settings/ComparatorResults.java new file mode 100644 index 0000000..1eda21c --- /dev/null +++ b/src/main/java/pro/litvinovg/docsettings/settings/ComparatorResults.java @@ -0,0 +1,66 @@ +package pro.litvinovg.docsettings.settings; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ComparatorResults { + + private static final Logger logger = LoggerFactory.getLogger(ComparatorResults.class); + + private List removedSettings = new ArrayList(); + private List newSetting = new ArrayList(); + private Map changedSettings = new HashMap(); + + public boolean hasChanges() { + return removedSettings.isEmpty() && newSetting.isEmpty() && changedSettings.isEmpty(); + } + + public void addRemovedProperty(ODFSetting setting){ + removedSettings.add(setting); + } + + public void addNewProperty(ODFSetting setting){ + newSetting.add(setting); + } + + public void addChangedProperty(ODFSetting oldSetting,ODFSetting newSetting){ + changedSettings.put(oldSetting, newSetting); + } + + public List getRemovedProperties() { + return removedSettings; + } + + public List getAddedProperties() { + return newSetting; + } + + public Map getChangedProperties() { + return changedSettings; + } + + public void log() { + logger.info("New settings:"); + for (Iterator iterator = newSetting.iterator(); iterator.hasNext();) { + ODFSetting odfSetting = (ODFSetting) iterator.next(); + logger.debug(odfSetting.getName()); + } + logger.info("Removed settings:"); + for (Iterator iterator = removedSettings.iterator(); iterator.hasNext();) { + ODFSetting odfSetting = (ODFSetting) iterator.next(); + logger.info(odfSetting.getName()); + } + logger.info("Changed settings:"); + for (Entry entry : changedSettings.entrySet()) { + logger.info(entry.getKey().getName() + "\nChanged from "+ entry.getKey().getValue() + " to " + entry.getValue().getValue() ); + } + } + +} diff --git a/src/main/java/pro/litvinovg/docsettings/settings/ODFSetting.java b/src/main/java/pro/litvinovg/docsettings/settings/ODFSetting.java new file mode 100644 index 0000000..00cf036 --- /dev/null +++ b/src/main/java/pro/litvinovg/docsettings/settings/ODFSetting.java @@ -0,0 +1,11 @@ +package pro.litvinovg.docsettings.settings; + +import org.w3c.dom.Document; +import org.w3c.dom.Node; + +public interface ODFSetting { + public String getName(); + public String getValue(); + public String getType(); + public void addToDocumentElement(Document doc, Node root); +} diff --git a/src/main/java/pro/litvinovg/docsettings/settings/ODFSettings.java b/src/main/java/pro/litvinovg/docsettings/settings/ODFSettings.java new file mode 100644 index 0000000..bf9874e --- /dev/null +++ b/src/main/java/pro/litvinovg/docsettings/settings/ODFSettings.java @@ -0,0 +1,24 @@ +package pro.litvinovg.docsettings.settings; + + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.sun.star.uno.XComponentContext; + +import pro.litvinovg.docsettings.DialogHelper; + +public class ODFSettings { + + private XComponentContext context; + private static final Logger logger = LoggerFactory.getLogger(ODFSettings.class); + + public ODFSettings(XComponentContext context ) { + this.context = context; + } + + public void printText(String text) { + DialogHelper.showErrorMessage(context, null, text); + } + +} diff --git a/src/main/java/pro/litvinovg/docsettings/settings/SettingFactory.java b/src/main/java/pro/litvinovg/docsettings/settings/SettingFactory.java new file mode 100644 index 0000000..17a1a17 --- /dev/null +++ b/src/main/java/pro/litvinovg/docsettings/settings/SettingFactory.java @@ -0,0 +1,64 @@ +package pro.litvinovg.docsettings.settings; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Node; + +import pro.litvinovg.docsettings.exceptions.MalformedXmlException; + +import static pro.litvinovg.docsettings.settings.StorageUtils.*; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.TreeMap; + +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.TransformerException; +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathExpression; +import javax.xml.xpath.XPathExpressionException; +import javax.xml.xpath.XPathFactory; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import pro.litvinovg.docsettings.exceptions.MalformedXmlException; + +public class SettingFactory { + private static final String TYPE_INT = "int"; + private static final String TYPE_BASE64_BINARY = "base64Binary"; + private static final String TYPE_STRING = "string"; + private static final String TYPE_SHORT = "short"; + private static final String TYPE_BOOLEAN = "boolean"; + private static final Logger logger = LoggerFactory.getLogger(SettingFactory.class); + + public static ODFSetting create(Node node) throws MalformedXmlException { + Node typeNode = node.getAttributes().getNamedItem("config:type"); + if (typeNode == null) { + logger.debug("nodeType is null"); + throw new MalformedXmlException("Node " + node.getNodeName() + " config:type attribute not found."); + } + String type = typeNode.getNodeValue(); + if (type == null || type.isEmpty()) { + logger.debug("node type value is null or empty"); + throw new MalformedXmlException("Node " + node.getNodeName() + " config:type attribute not found."); + } + if (TYPE_BOOLEAN.equals(type)|| + TYPE_SHORT.equals(type)|| + TYPE_STRING.equals(type)|| + TYPE_BASE64_BINARY.equals(type)|| + TYPE_INT.equals(type)) { + return new SimpleSetting(node); + } + logger.debug("node type not recognized"); + throw new MalformedXmlException("Node " + node.getNodeName()); + } +} diff --git a/src/main/java/pro/litvinovg/docsettings/settings/Settings.java b/src/main/java/pro/litvinovg/docsettings/settings/Settings.java new file mode 100644 index 0000000..68b76a8 --- /dev/null +++ b/src/main/java/pro/litvinovg/docsettings/settings/Settings.java @@ -0,0 +1,78 @@ +package pro.litvinovg.docsettings.settings; + +import static pro.litvinovg.docsettings.settings.StorageUtils.*; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +public class Settings { + private static final Logger logger = LoggerFactory.getLogger(Settings.class); + + + private List settings = null; + + public Settings() { + this.settings = new ArrayList(); + } + + public void add(ODFSetting setting) { + settings.add(setting); + } + + public void remove(int index) { + settings.remove(index); + } + + public void remove(ODFSetting setting) { + settings.remove(setting); + } + + public int getSize() { + return settings.size(); + } + + public ODFSetting get(int num) { + return settings.get(num); + } + + public ODFSetting getByName(String name) { + for (Iterator iterator = settings.iterator(); iterator.hasNext();) { + ODFSetting odfSetting = (ODFSetting) iterator.next(); + if (name.equals(odfSetting.getName())) { + return odfSetting; + } + } + return null; + } + + public void clear() { + settings.clear(); + } + + public boolean isEmpty() { + return settings.isEmpty(); + } + + public String toSettingsContent(String content) throws Exception { + Document doc = getDocFromString(content); + Node settingsContainer = getSettingsElement(doc); + NodeList settingNodes = settingsContainer.getChildNodes(); + removeNodes(settingNodes); + appendSettingNodesToDoc(this, doc, settingsContainer); + String newSettingsContent = serializeDoc(doc); + return newSettingsContent; + } + + public void addAll(Settings inputSettings) { + for (int i = 0; i < inputSettings.getSize(); i++) { + this.add(inputSettings.get(i)); + } + } +} diff --git a/src/main/java/pro/litvinovg/docsettings/settings/SettingsComparator.java b/src/main/java/pro/litvinovg/docsettings/settings/SettingsComparator.java new file mode 100644 index 0000000..ee2331d --- /dev/null +++ b/src/main/java/pro/litvinovg/docsettings/settings/SettingsComparator.java @@ -0,0 +1,48 @@ +package pro.litvinovg.docsettings.settings; + +public class SettingsComparator { + + private static final String RSID = "Rsid"; + + public static boolean isEqual(Settings currentSettings, Settings backedUpSettings) { + Settings newSettings = new Settings(); + newSettings.addAll(currentSettings); + Settings oldSettings = new Settings(); + oldSettings.addAll(backedUpSettings); + ComparatorResults results = compare(newSettings, oldSettings); + if (results.hasChanges()) { + results.log(); + } + return !results.hasChanges(); + } + + private static ComparatorResults compare(Settings newSettings, Settings oldSettings) { + ComparatorResults results = new ComparatorResults(); + while (!newSettings.isEmpty()) { + ODFSetting newSet = newSettings.get(0); + ODFSetting oldSet = oldSettings.getByName(newSet.getName()); + if (oldSet == null) { + results.addNewProperty(newSet); + } else { + if (!(newSet.getValue()).equals(oldSet.getValue()) && !isExcluded(newSet.getName())) { + results.addChangedProperty(oldSet, newSet); + } + oldSettings.remove(oldSet); + } + newSettings.remove(newSet); + } + while (!oldSettings.isEmpty()) { + ODFSetting oldSet = oldSettings.get(0); + results.addRemovedProperty(oldSet); + } + return results; + } + + private static boolean isExcluded(String name) { + if (name.equals(RSID)) { + return true; + } + return false; + } + +} diff --git a/src/main/java/pro/litvinovg/docsettings/settings/SimpleSetting.java b/src/main/java/pro/litvinovg/docsettings/settings/SimpleSetting.java new file mode 100644 index 0000000..2fbcb0b --- /dev/null +++ b/src/main/java/pro/litvinovg/docsettings/settings/SimpleSetting.java @@ -0,0 +1,53 @@ +package pro.litvinovg.docsettings.settings; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; + +import pro.litvinovg.docsettings.exceptions.MalformedXmlException; + +public class SimpleSetting implements ODFSetting { + + private static final Logger logger = LoggerFactory.getLogger(SimpleSetting.class); + protected String name; + protected String value; + protected String type; + + public SimpleSetting(Node node) throws MalformedXmlException { + this.name = getNodeName(node); + this.value = node.getTextContent(); + this.type = node.getAttributes().getNamedItem("config:type").getNodeValue(); + } + protected String getNodeName(Node node) throws MalformedXmlException { + Node nameAttr = node.getAttributes().getNamedItem("config:name"); + if (nameAttr == null) { + logger.debug("name attribute not found while creating simple property"); + throw new MalformedXmlException("Node " + node.getNodeName() + " config:name attribute not found."); + } + return nameAttr.getNodeValue(); + } + + @Override + public String getName() { + return name; + } + + @Override + public String getValue() { + return value; + } + @Override + public String getType() { + return type; + } + @Override + public void addToDocumentElement(Document doc, Node rootElement) { + Element element = doc.createElement("config:config-item"); + element.setAttribute("config:name", getName()); + element.setAttribute("config:type", getType()); + element.setTextContent(getValue()); + rootElement.appendChild(element); + } +} diff --git a/src/main/java/pro/litvinovg/docsettings/settings/StorageUtils.java b/src/main/java/pro/litvinovg/docsettings/settings/StorageUtils.java new file mode 100644 index 0000000..02f5ee2 --- /dev/null +++ b/src/main/java/pro/litvinovg/docsettings/settings/StorageUtils.java @@ -0,0 +1,126 @@ +package pro.litvinovg.docsettings.settings; + +import java.io.IOException; +import java.io.StringReader; +import java.io.StringWriter; +import java.util.Base64; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathExpression; +import javax.xml.xpath.XPathExpressionException; +import javax.xml.xpath.XPathFactory; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +import pro.litvinovg.docsettings.exceptions.MalformedXmlException; + +public class StorageUtils { + + private static final Logger logger = LoggerFactory.getLogger(StorageUtils.class); + + public static Document getDocFromString(String xmlString) throws ParserConfigurationException, SAXException, IOException { + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + DocumentBuilder db = dbf.newDocumentBuilder(); + Document doc = db.parse(new InputSource(new StringReader(xmlString))); + return doc; + } + + public static Document createNewDoc() throws ParserConfigurationException { + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + DocumentBuilder db = dbf.newDocumentBuilder(); + Document doc = db.newDocument(); + return doc; + } + + public static String serializeDoc(Document doc) throws TransformerException { + StringWriter output = new StringWriter(); + Transformer transformer = TransformerFactory.newInstance().newTransformer(); + transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); + transformer.transform(new DOMSource(doc), new StreamResult(output)); + String str = output.toString(); + return str; + } + + public static String printNode(Node node) throws TransformerException { + StringWriter output = new StringWriter(); + Transformer transformer = TransformerFactory.newInstance().newTransformer(); + transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); + transformer.transform(new DOMSource(node), new StreamResult(output)); + String str = output.toString(); + return str; + } + + + public static String encodeBase64(String string) { + return new String(Base64.getEncoder().encode(string.getBytes())); + } + + public static String decodeBase64(String serializedMetaSettings) { + return new String(Base64.getDecoder().decode(serializedMetaSettings)); + } + + public static void removeNodes(NodeList nodes) { + for (int i = nodes.getLength()-1; i >= 0; i--) { + Node node = nodes.item(i); + node.getParentNode().removeChild(node); + } + } + + public static void appendSettingNodesToDoc(Settings settings, Document doc, Node root) { + for(int i = 0; i< settings.getSize();i++) { + ODFSetting setting = settings.get(i); + setting.addToDocumentElement(doc,root); + } + } + + public static Node getSettingsElement(Document doc) throws Exception { + XPathFactory xPathfactory = XPathFactory.newInstance(); + XPath xpath = xPathfactory.newXPath(); + XPathExpression expr = xpath.compile("//*[local-name() ='config-item-set'][@*[local-name() ='name' and .='ooo:configuration-settings']]"); + NodeList configItems = (NodeList) expr.evaluate(doc, XPathConstants.NODESET); + Node configSettings = configItems.item(0); + if (configSettings == null) { + throw new MalformedXmlException("No configuration settings element found"); + } + return configSettings; + } + + public static Node getMetaRootElement(Document doc) throws MalformedXmlException { + XPathFactory xPathfactory = XPathFactory.newInstance(); + XPath xpath = xPathfactory.newXPath(); + XPathExpression expr; + NodeList nodes; + try { + expr = xpath.compile("//*[local-name() ='meta']"); + nodes = (NodeList) expr.evaluate(doc, XPathConstants.NODESET); + if (nodes.getLength() == 0 ) { + throw new MalformedXmlException("Xpath expression failed while looking for main meta element"); + } + } catch (XPathExpressionException e) { + logger.debug(e.getLocalizedMessage()); + logger.debug("Ops!",e); + throw new MalformedXmlException("Xpath expression failed while looking for main meta element"); + } + Node result = nodes.item(0); + return result; + } + + + +} diff --git a/src/test/java/pro/litvinovg/docsettings/exceptions/MalformedXmlExceptionTest.java b/src/test/java/pro/litvinovg/docsettings/exceptions/MalformedXmlExceptionTest.java new file mode 100644 index 0000000..b359e95 --- /dev/null +++ b/src/test/java/pro/litvinovg/docsettings/exceptions/MalformedXmlExceptionTest.java @@ -0,0 +1,14 @@ +package pro.litvinovg.docsettings.exceptions; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +class MalformedXmlExceptionTest { + + @Test + void test() { + fail("Not yet implemented"); + } + +} diff --git a/src/test/java/pro/litvinovg/docsettings/processor/ODFSettingsTest.java b/src/test/java/pro/litvinovg/docsettings/processor/ODFSettingsTest.java new file mode 100644 index 0000000..0437935 --- /dev/null +++ b/src/test/java/pro/litvinovg/docsettings/processor/ODFSettingsTest.java @@ -0,0 +1,14 @@ +package pro.litvinovg.docsettings.processor; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +class ODFSettingsTest { + + @Test + void test() { + fail("Not yet implemented"); + } + +} diff --git a/testFiles/settings-old.xml b/testFiles/settings-old.xml new file mode 100644 index 0000000..dbd7bdd --- /dev/null +++ b/testFiles/settings-old.xml @@ -0,0 +1,131 @@ + + + + + 0 + 0 + 21625 + 9213 + true + false + + + view2 + 3401 + 2501 + 0 + 0 + 21624 + 9211 + 0 + 1 + false + 214 + false + true + + + + + false + + false + false + false + false + true + true + true + 0 + true + false + true + false + false + false + false + false + false + false + false + false + false + 517402 + true + false + true + true + true + true + false + false + true + false + false + false + true + false + false + false + false + false + 1 + 0 + + 0 + false + true + false + + false + false + + true + + false + true + false + false + + false + true + true + false + true + high-resolution + false + true + true + true + false + true + false + + true + false + false + false + false + false + false + false + true + false + true + 648908 + false + true + false + false + false + false + false + false + true + false + false + true + true + + + diff --git a/testFiles/settings.xml b/testFiles/settings.xml new file mode 100644 index 0000000..c457777 --- /dev/null +++ b/testFiles/settings.xml @@ -0,0 +1,131 @@ + + + + + 0 + 0 + 21625 + 9213 + true + false + + + view2 + 3401 + 2501 + 0 + 0 + 21624 + 9211 + 0 + 1 + false + 214 + false + true + + + + + false + + false + false + false + false + true + true + true + 0 + true + false + true + false + false + false + false + false + false + false + true + false + false + 517402 + false + false + true + false + true + true + false + false + true + false + true + true + false + false + false + true + false + false + 1 + 0 + + 0 + false + true + false + + false + false + + false + + false + true + false + false + + false + true + false + false + true + high-resolution + false + true + true + true + false + true + false + + true + false + false + false + false + false + false + false + true + false + true + 639374 + false + false + false + false + false + false + false + false + true + false + false + false + false + + + diff --git a/testFiles/testFile (copy).odt b/testFiles/testFile (copy).odt new file mode 100644 index 0000000000000000000000000000000000000000..f77a76bf2e268e0cc877b0154dbefe543e4a0fd5 GIT binary patch literal 12954 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 NJiW|@L5#1z{tt0xn%4jT literal 0 HcmV?d00001 diff --git a/testFiles/testFile.odt b/testFiles/testFile.odt new file mode 100644 index 0000000000000000000000000000000000000000..97f16dde3e0f33466e1558468ae723b67ef47ee3 GIT binary patch literal 14597 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 I;J82gKa}zTqyPW_ literal 0 HcmV?d00001