diff options
Diffstat (limited to 'unotools')
81 files changed, 26476 insertions, 0 deletions
diff --git a/unotools/CppunitTest_unotools_configpaths.mk b/unotools/CppunitTest_unotools_configpaths.mk new file mode 100644 index 0000000000..d5f002b9bb --- /dev/null +++ b/unotools/CppunitTest_unotools_configpaths.mk @@ -0,0 +1,21 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t; fill-column: 100 -*- +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +$(eval $(call gb_CppunitTest_CppunitTest,unotools_configpaths)) + +$(eval $(call gb_CppunitTest_add_exception_objects,unotools_configpaths, \ + unotools/qa/unit/configpaths \ +)) + +$(eval $(call gb_CppunitTest_use_libraries,unotools_configpaths, \ + sal \ + utl \ +)) + +# vim: set noet sw=4 ts=4: diff --git a/unotools/CppunitTest_unotools_fontcvt.mk b/unotools/CppunitTest_unotools_fontcvt.mk new file mode 100644 index 0000000000..16236c16c5 --- /dev/null +++ b/unotools/CppunitTest_unotools_fontcvt.mk @@ -0,0 +1,30 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +$(eval $(call gb_CppunitTest_CppunitTest,unotools_fontcvt)) + +$(eval $(call gb_CppunitTest_use_external,unotools_fontcvt,boost_headers)) + +$(eval $(call gb_CppunitTest_use_sdk_api,unotools_fontcvt)) + +$(eval $(call gb_CppunitTest_add_exception_objects,unotools_fontcvt, \ + unotools/qa/unit/testRecodeString \ +)) + +$(eval $(call gb_CppunitTest_use_libraries,unotools_fontcvt, \ + comphelper \ + cppu \ + cppuhelper \ + tl \ + sal \ + svt \ + utl \ +)) + +# vim: set noet sw=4 ts=4: diff --git a/unotools/CppunitTest_unotools_fontdefs.mk b/unotools/CppunitTest_unotools_fontdefs.mk new file mode 100644 index 0000000000..5eada2084a --- /dev/null +++ b/unotools/CppunitTest_unotools_fontdefs.mk @@ -0,0 +1,30 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +$(eval $(call gb_CppunitTest_CppunitTest,unotools_fontdefs)) + +$(eval $(call gb_CppunitTest_use_external,unotools_fontdefs,boost_headers)) + +$(eval $(call gb_CppunitTest_use_sdk_api,unotools_fontdefs)) + +$(eval $(call gb_CppunitTest_add_exception_objects,unotools_fontdefs, \ + unotools/qa/unit/testGetEnglishSearchName \ +)) + +$(eval $(call gb_CppunitTest_use_libraries,unotools_fontdefs, \ + comphelper \ + cppu \ + cppuhelper \ + tl \ + sal \ + svt \ + utl \ +)) + +# vim: set noet sw=4 ts=4: diff --git a/unotools/IwyuFilter_unotools.yaml b/unotools/IwyuFilter_unotools.yaml new file mode 100644 index 0000000000..0a3b6eeff0 --- /dev/null +++ b/unotools/IwyuFilter_unotools.yaml @@ -0,0 +1,43 @@ +--- +assumeFilename: unotools/source/i18n/resmgr.cxx +excludelist: + unotools/source/config/itemholder1.hxx: + # Base class needs complete type + - com/sun/star/lang/XEventListener.hpp + unotools/source/ucbhelper/XTempFile.hxx: + # Base class needs complete type + - com/sun/star/io/XInputStream.hpp + - com/sun/star/io/XOutputStream.hpp + - com/sun/star/io/XTempFile.hpp + - com/sun/star/io/XTruncate.hpp + - com/sun/star/beans/XFastPropertySet.hpp + - com/sun/star/beans/XPropertyAccess.hpp + - com/sun/star/beans/XPropertySet.hpp + unotools/source/config/configpaths.cxx: + # Needed for linker visibility + - unotools/configpaths.hxx + unotools/source/config/lingucfg.cxx: + # Needed for direct member access + - com/sun/star/util/XChangesBatch.hpp + unotools/source/config/useroptions.cxx: + # Needed for direct member access + - com/sun/star/container/XNameAccess.hpp + unotools/source/i18n/resmgr.cxx: + # Boost wrappers, used only here + - boost/locale.hpp + - boost/locale/gnu_gettext.hpp + unotools/source/ucbhelper/tempfile.cxx: + # Needed for non-DBG_UTIL build + - unistd.h + unotools/source/ucbhelper/ucbhelper.cxx: + # Needed for namespaced function declarations + - unotools/ucbhelper.hxx + unotools/source/ucbhelper/ucblockbytes.cxx: + # Needed for direct member access + - com/sun/star/beans/PropertyValue.hpp + unotools/source/ucbhelper/ucbstreamhelper.cxx: + # Needed for direct member access + - com/sun/star/beans/PropertyValue.hpp + unotools/source/misc/datetime.cxx: + # Needed for rtl::math::round + - rtl/math.hxx diff --git a/unotools/JunitTest_unotools_complex.mk b/unotools/JunitTest_unotools_complex.mk new file mode 100644 index 0000000000..a673404933 --- /dev/null +++ b/unotools/JunitTest_unotools_complex.mk @@ -0,0 +1,33 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +$(eval $(call gb_JunitTest_JunitTest,unotools_complex)) + +$(eval $(call gb_JunitTest_use_unoapi_jars,unotools_complex)) + +$(eval $(call gb_JunitTest_use_jars,unotools_complex,\ + test-tools \ +)) + +$(eval $(call gb_JunitTest_set_defs,unotools_complex,\ + $$(DEFS) \ +)) + +$(eval $(call gb_JunitTest_add_classes,unotools_complex,\ + complex.tempfile.TempFileUnitTest \ +)) + +$(eval $(call gb_JunitTest_add_sourcefiles,unotools_complex,\ + unotools/qa/complex/tempfile/TempFileUnitTest \ + unotools/qa/complex/tempfile/Test01 \ + unotools/qa/complex/tempfile/Test02 \ + unotools/qa/complex/tempfile/TestHelper \ +)) + +# vim: set noet sw=4 ts=4: diff --git a/unotools/Library_utl.mk b/unotools/Library_utl.mk new file mode 100644 index 0000000000..14ef6e843a --- /dev/null +++ b/unotools/Library_utl.mk @@ -0,0 +1,122 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +# declare a library +# utl is the name of the library as it is found in Repository.mk +$(eval $(call gb_Library_Library,utl)) + +$(eval $(call gb_Library_use_externals,utl,\ + boost_headers \ + boost_locale \ +)) + +$(eval $(call gb_Library_use_custom_headers,utl,\ + officecfg/registry \ +)) + +# in case UNO services are exported: declare location of component file +$(eval $(call gb_Library_set_componentfile,utl,unotools/util/utl,services)) + +$(eval $(call gb_Library_set_include,utl, \ + $$(INCLUDE) \ + -I$(SRCDIR)/unotools/inc \ +)) + +# add any additional definitions to be set for compilation here +# (e.g. -DLIB_DLLIMPLEMENTATION) +$(eval $(call gb_Library_add_defs,utl,\ + -DUNOTOOLS_DLLIMPLEMENTATION \ +)) + +$(eval $(call gb_Library_set_precompiled_header,utl,unotools/inc/pch/precompiled_utl)) + +$(eval $(call gb_Library_use_sdk_api,utl)) + +# add libraries to be linked to utl; again these names need to be given as +# specified in Repository.mk +$(eval $(call gb_Library_use_libraries,utl,\ + comphelper \ + cppu \ + cppuhelper \ + i18nlangtag \ + sal \ + salhelper \ + tl \ + ucbhelper \ +)) + +$(eval $(call gb_Library_add_exception_objects,utl,\ + unotools/source/accessibility/accessiblerelationsethelper \ +)) + +# add all source files that shall be compiled with exceptions enabled +# the name is relative to $(SRCROOT) and must not contain an extension +$(eval $(call gb_Library_add_exception_objects,utl,\ + unotools/source/config/bootstrap \ + unotools/source/config/cmdoptions \ + unotools/source/config/compatibility \ + unotools/source/config/configitem \ + unotools/source/config/configmgr \ + unotools/source/config/confignode \ + unotools/source/config/configpaths \ + unotools/source/config/configvaluecontainer \ + unotools/source/config/defaultoptions \ + unotools/source/config/docinfohelper \ + unotools/source/config/dynamicmenuoptions \ + unotools/source/config/eventcfg \ + unotools/source/config/fltrcfg \ + unotools/source/config/fontcfg \ + unotools/source/config/historyoptions \ + unotools/source/config/itemholder1 \ + unotools/source/config/lingucfg \ + unotools/source/config/moduleoptions \ + unotools/source/config/options \ + unotools/source/config/optionsdlg \ + unotools/source/config/pathoptions \ + unotools/source/config/saveopt \ + unotools/source/config/searchopt \ + unotools/source/config/securityoptions \ + unotools/source/config/syslocaleoptions \ + unotools/source/config/useroptions \ + unotools/source/config/viewoptions \ + unotools/source/i18n/calendarwrapper \ + unotools/source/i18n/caserotate \ + unotools/source/i18n/charclass \ + unotools/source/i18n/collatorwrapper \ + unotools/source/i18n/intlwrapper \ + unotools/source/i18n/localedatawrapper \ + unotools/source/i18n/nativenumberwrapper \ + unotools/source/i18n/resmgr \ + unotools/source/i18n/textsearch \ + unotools/source/i18n/transliterationwrapper \ + unotools/source/misc/closeveto \ + unotools/source/misc/datetime \ + unotools/source/misc/defaultencoding \ + unotools/source/misc/desktopterminationobserver \ + unotools/source/misc/eventlisteneradapter \ + unotools/source/misc/fontcvt \ + unotools/source/misc/fontdefs \ + unotools/source/misc/mediadescriptor \ + unotools/source/misc/sharedunocomponent \ + unotools/source/misc/syslocale \ + unotools/source/misc/wincodepage \ + unotools/source/misc/ServiceDocumenter \ + unotools/source/misc/ZipPackageHelper \ + unotools/source/streaming/streamhelper \ + unotools/source/streaming/streamwrap \ + unotools/source/ucbhelper/localfilehelper \ + unotools/source/ucbhelper/progresshandlerwrap \ + unotools/source/ucbhelper/tempfile \ + unotools/source/ucbhelper/ucbhelper \ + unotools/source/ucbhelper/ucblockbytes \ + unotools/source/ucbhelper/ucbstreamhelper \ + unotools/source/ucbhelper/xtempfile \ +)) + +# vim: set noet sw=4 ts=4: diff --git a/unotools/Makefile b/unotools/Makefile new file mode 100644 index 0000000000..0997e62848 --- /dev/null +++ b/unotools/Makefile @@ -0,0 +1,14 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +module_directory:=$(dir $(realpath $(firstword $(MAKEFILE_LIST)))) + +include $(module_directory)/../solenv/gbuild/partial_build.mk + +# vim: set noet sw=4 ts=4: diff --git a/unotools/Module_unotools.mk b/unotools/Module_unotools.mk new file mode 100644 index 0000000000..5d8b786be5 --- /dev/null +++ b/unotools/Module_unotools.mk @@ -0,0 +1,25 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +$(eval $(call gb_Module_Module,unotools)) + +$(eval $(call gb_Module_add_targets,unotools,\ + Library_utl \ +)) + +$(eval $(call gb_Module_add_subsequentcheck_targets,unotools,\ + PythonTest_unotools_python \ +)) +$(eval $(call gb_Module_add_check_targets,unotools,\ + CppunitTest_unotools_configpaths \ + CppunitTest_unotools_fontdefs \ + CppunitTest_unotools_fontcvt \ +)) + +# vim: set noet sw=4 ts=4: diff --git a/unotools/PythonTest_unotools_python.mk b/unotools/PythonTest_unotools_python.mk new file mode 100644 index 0000000000..5201cad4c2 --- /dev/null +++ b/unotools/PythonTest_unotools_python.mk @@ -0,0 +1,16 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +$(eval $(call gb_PythonTest_PythonTest,unotools_python)) + +$(eval $(call gb_PythonTest_add_modules,unotools_python,$(SRCDIR)/unotools/qa/python,\ + test_tempfile \ +)) + +# vim: set noet sw=4 ts=4: diff --git a/unotools/README.md b/unotools/README.md new file mode 100644 index 0000000000..73ce9f0897 --- /dev/null +++ b/unotools/README.md @@ -0,0 +1,3 @@ +# UNO C++ Helpers + +Helpers for C++ use of UNO. diff --git a/unotools/inc/pch/precompiled_utl.cxx b/unotools/inc/pch/precompiled_utl.cxx new file mode 100644 index 0000000000..41a7f40d9a --- /dev/null +++ b/unotools/inc/pch/precompiled_utl.cxx @@ -0,0 +1,12 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include "precompiled_utl.hxx" + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/inc/pch/precompiled_utl.hxx b/unotools/inc/pch/precompiled_utl.hxx new file mode 100644 index 0000000000..1a46cbc1a9 --- /dev/null +++ b/unotools/inc/pch/precompiled_utl.hxx @@ -0,0 +1,147 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +/* + This file has been autogenerated by update_pch.sh. It is possible to edit it + manually (such as when an include file has been moved/renamed/removed). All such + manual changes will be rewritten by the next run of update_pch.sh (which presumably + also fixes all possible problems, so it's usually better to use it). + + Generated on 2021-03-08 13:22:27 using: + ./bin/update_pch unotools utl --cutoff=3 --exclude:system --exclude:module --include:local + + If after updating build fails, use the following command to locate conflicting headers: + ./bin/update_pch_bisect ./unotools/inc/pch/precompiled_utl.hxx "make unotools.build" --find-conflicts +*/ + +#include <sal/config.h> +#if PCH_LEVEL >= 1 +#include <algorithm> +#include <cassert> +#include <cstddef> +#include <memory> +#include <optional> +#include <ostream> +#include <stddef.h> +#include <string_view> +#include <type_traits> +#include <unordered_map> +#include <utility> +#include <vector> +#endif // PCH_LEVEL >= 1 +#if PCH_LEVEL >= 2 +#include <osl/detail/file.h> +#include <osl/diagnose.h> +#include <osl/endian.h> +#include <osl/file.hxx> +#include <osl/mutex.hxx> +#include <osl/nlsupport.h> +#include <osl/process.h> +#include <osl/thread.h> +#include <osl/thread.hxx> +#include <osl/time.h> +#include <rtl/alloc.h> +#include <rtl/bootstrap.hxx> +#include <rtl/character.hxx> +#include <rtl/instance.hxx> +#include <rtl/locale.h> +#include <rtl/math.hxx> +#include <rtl/ref.hxx> +#include <rtl/string.h> +#include <rtl/string.hxx> +#include <rtl/tencinfo.h> +#include <rtl/textenc.h> +#include <rtl/uri.hxx> +#include <rtl/ustrbuf.hxx> +#include <rtl/ustring.h> +#include <rtl/ustring.hxx> +#include <sal/log.hxx> +#include <sal/macros.h> +#include <sal/saldllapi.h> +#include <sal/types.h> +#include <sal/typesizes.h> +#include <comphelper/errcode.hxx> +#endif // PCH_LEVEL >= 2 +#if PCH_LEVEL >= 3 +#include <com/sun/star/beans/NamedValue.hpp> +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/configuration/theDefaultProvider.hpp> +#include <com/sun/star/container/XHierarchicalNameAccess.hpp> +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/container/XNameContainer.hpp> +#include <com/sun/star/io/BufferSizeExceededException.hpp> +#include <com/sun/star/io/NotConnectedException.hpp> +#include <com/sun/star/io/XActiveDataSink.hpp> +#include <com/sun/star/lang/Locale.hpp> +#include <com/sun/star/lang/XComponent.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/lang/XSingleServiceFactory.hpp> +#include <com/sun/star/task/XInteractionHandler.hpp> +#include <com/sun/star/ucb/CommandAbortedException.hpp> +#include <com/sun/star/ucb/ContentCreationException.hpp> +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/Reference.h> +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/uno/RuntimeException.hpp> +#include <com/sun/star/uno/Sequence.h> +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/uno/XInterface.hpp> +#include <com/sun/star/util/Date.hpp> +#include <com/sun/star/util/Time.hpp> +#include <com/sun/star/util/XChangesBatch.hpp> +#include <comphelper/comphelperdllapi.h> +#include <comphelper/configurationhelper.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/sequence.hxx> +#include <cppu/cppudllapi.h> +#include <cppu/unotype.hxx> +#include <cppuhelper/cppuhelperdllapi.h> +#include <cppuhelper/implbase.hxx> +#include <i18nlangtag/lang.h> +#include <i18nlangtag/languagetag.hxx> +#include <i18nlangtag/mslangid.hxx> +#include <i18nutil/transliteration.hxx> +#include <o3tl/enumarray.hxx> +#include <o3tl/safeint.hxx> +#include <o3tl/typed_flags_set.hxx> +#include <o3tl/underlyingenumvalue.hxx> +#include <salhelper/condition.hxx> +#include <tools/date.hxx> +#include <tools/debug.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <tools/lineend.hxx> +#include <tools/long.hxx> +#include <tools/ref.hxx> +#include <tools/stream.hxx> +#include <tools/time.hxx> +#include <tools/toolsdllapi.h> +#include <tools/urlobj.hxx> +#include <ucbhelper/content.hxx> +#include <ucbhelper/ucbhelperdllapi.h> +#endif // PCH_LEVEL >= 3 +#if PCH_LEVEL >= 4 +#include <unotools/charclass.hxx> +#include <unotools/configitem.hxx> +#include <unotools/configmgr.hxx> +#include <unotools/configpaths.hxx> +#include <unotools/fontdefs.hxx> +#include <unotools/localedatawrapper.hxx> +#include <unotools/options.hxx> +#include <unotools/pathoptions.hxx> +#include <unotools/securityoptions.hxx> +#include <unotools/streamwrap.hxx> +#include <unotools/syslocale.hxx> +#include <unotools/syslocaleoptions.hxx> +#include <unotools/ucbhelper.hxx> +#include <unotools/unotoolsdllapi.h> +#endif // PCH_LEVEL >= 4 + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/qa/python/test_tempfile.py b/unotools/qa/python/test_tempfile.py new file mode 100644 index 0000000000..0d417dbbe9 --- /dev/null +++ b/unotools/qa/python/test_tempfile.py @@ -0,0 +1,149 @@ +#! /usr/bin/env python +# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-offset: 4 -*- +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +import unittest + +import uno +from org.libreoffice.unotest import UnoInProcess + + +class TestTempFile(unittest.TestCase): + """Test temporary file object created from com.sun.star.io.TempFile""" + + @classmethod + def setUpClass(cls): + cls._uno = UnoInProcess() + cls._uno.setUp() + + @classmethod + def tearDownClass(cls): + cls._uno.tearDown() + + def setUp(self): + self.file_data = uno.ByteSequence(b"some data") + + service_manager = self._uno.getContext().getServiceManager() + if service_manager is None: + raise RuntimeError("Cannot create service factory!") + + try: + self.file_access = service_manager.createInstance("com.sun.star.ucb.SimpleFileAccess") + if self.file_access is None: + raise RuntimeError("Cannot get simple access!") + except Exception as e: + raise RuntimeError(f"Cannot get simple file access! {e}") + + self.temp_file = service_manager.createInstance("com.sun.star.io.TempFile") + has_xtempfile_if = bool([ + 1 for type_info in self.temp_file.getTypes() + if type_info.typeName == "com.sun.star.io.XTempFile" + ]) + if not has_xtempfile_if: + raise RuntimeError("Cannot get XTempFile interface.") + + def close_temp_file(self) -> None: + stream = self.temp_file.getOutputStream() + if stream is None: + raise RuntimeError("Cannot get output stream") + stream.closeOutput() + stream = self.temp_file.getInputStream() + if stream is None: + raise RuntimeError("Cannot get input stream") + stream.closeInput() + print("Tempfile closed successfully.") + + def read_bytes_with_stream(self) -> uno.ByteSequence: + input_stream = self.temp_file.getInputStream() + if input_stream is None: + raise RuntimeError("Cannot get input stream from tempfile.") + nbytes, read_data = input_stream.readBytes(None, len(self.file_data)) + print("Read", nbytes, "bytes from tempfile successfully.") + return read_data + + def read_directly_from_temp_file(self, file_url: str) -> uno.ByteSequence: + print("Attempting to read directly from", file_url) + input_stream = self.file_access.openFileRead(file_url) + if input_stream is None: + raise RuntimeError("Cannot create input stream from URL.") + nbytes, read_data = input_stream.readBytes(None, len(self.file_data)) + print("Read", nbytes, "bytes directly from tempfile successfully.") + return read_data + + def write_bytes_with_stream(self) -> None: + output_stream = self.temp_file.getOutputStream() + if output_stream is None: + raise RuntimeError("Cannot get output stream.") + output_stream.writeBytes(self.file_data) + output_stream.flush() + print("Write", len(self.file_data), "bytes to tempfile successfully.") + + def get_temp_file_url(self) -> str: + uri = self.temp_file.Uri + if not uri: + raise RuntimeError("Temporary file not valid.") + return uri + + def get_temp_file_name(self) -> str: + file_name = self.temp_file.ResourceName + if not file_name: + raise RuntimeError("Temporary file not valid.") + return file_name + + def test_01(self): + file_uri = self.get_temp_file_url() + file_name = self.get_temp_file_name() + print("Tempfile URL:", file_uri) + print("Tempfile name:", file_name) + self.assertTrue( + file_uri.endswith(file_name.replace("\\", "/")), + "FILE NAME AND URL DO NOT MATCH.", + ) + + # write to the stream using the service. + self.write_bytes_with_stream() + + # check the result by reading from the service. + self.temp_file.seek(0) + read_data = self.read_bytes_with_stream() + self.assertEqual(self.file_data, read_data, "Tempfile outputs false data!") + + # check the result by reading from the file directly. + read_data = self.read_directly_from_temp_file(file_uri) + self.assertEqual(self.file_data, read_data, "Tempfile contains false data!") + + # close the object(by closing input and output), check that the file + # was removed. + self.temp_file.RemoveFile = False + # After tempfile is closed, file name cannot be got from a TempFile object. + file_name = self.temp_file.ResourceName + self.close_temp_file() + self.assertTrue( + self.file_access.exists(file_name), "TempFile mistakenly removed.", + ) + + # Finally, cleanup this temp file. + self.file_access.kill(file_name) + + def test_02(self): + self.write_bytes_with_stream() + file_url = self.get_temp_file_url() + # let the service not to remove the URL. + self.temp_file.RemoveFile = False + # close the tempfile by closing input and output. + self.close_temp_file() + # check that the file is still available. + read_data = self.read_directly_from_temp_file(file_url) + self.assertEqual(self.file_data, read_data, "Tempfile contains false data!") + + +if __name__ == '__main__': + unittest.main() + +# vim: set shiftwidth=4 softtabstop=4 expandtab: diff --git a/unotools/qa/unit/configpaths.cxx b/unotools/qa/unit/configpaths.cxx new file mode 100644 index 0000000000..7d9907d9e3 --- /dev/null +++ b/unotools/qa/unit/configpaths.cxx @@ -0,0 +1,115 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <sal/config.h> + +#include <cppunit/TestAssert.h> +#include <cppunit/TestFixture.h> +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/plugin/TestPlugIn.h> + +#include <unotools/configpaths.hxx> + +namespace +{ +class Test : public CppUnit::TestFixture +{ +public: + void testSplitLastFromConfigurationPath() + { + { + OUString path, last; + CPPUNIT_ASSERT(!utl::splitLastFromConfigurationPath(u"", path, last)); + CPPUNIT_ASSERT_EQUAL(OUString(""), path); + CPPUNIT_ASSERT_EQUAL(OUString(""), last); + } + { + // Already prior to 5edefc801fb48559c8064003f23d22d838710ee4 "use more string_view in + // unotools", and in discordance with the documentation, this returned true (but + // "@returns <FALSE/>, if the path was a one-level path or an invalid path"): + OUString path, last; + CPPUNIT_ASSERT(utl::splitLastFromConfigurationPath(u"/", path, last)); + CPPUNIT_ASSERT_EQUAL(OUString(""), path); + CPPUNIT_ASSERT_EQUAL(OUString(""), last); + } + { + // Already prior to 5edefc801fb48559c8064003f23d22d838710ee4 "use more string_view in + // unotools", and in discordance with the documentation, this returned true (but + // "@returns <FALSE/>, if the path was a one-level path or an invalid path"): + OUString path, last; + CPPUNIT_ASSERT(utl::splitLastFromConfigurationPath(u"/foo", path, last)); + CPPUNIT_ASSERT_EQUAL(OUString(""), path); + CPPUNIT_ASSERT_EQUAL(OUString("foo"), last); + } + { + // Already prior to 5edefc801fb48559c8064003f23d22d838710ee4 "use more string_view in + // unotools", and in discordance with the documentation, this returned true (but + // "@returns <FALSE/>, if the path was a one-level path or an invalid path"): + OUString path, last; + CPPUNIT_ASSERT(utl::splitLastFromConfigurationPath(u"/foo/", path, last)); + CPPUNIT_ASSERT_EQUAL(OUString(""), path); + CPPUNIT_ASSERT_EQUAL(OUString("foo"), last); + } + { + OUString path, last; + CPPUNIT_ASSERT(utl::splitLastFromConfigurationPath(u"/foo/bar/baz", path, last)); + CPPUNIT_ASSERT_EQUAL(OUString("/foo/bar"), path); + CPPUNIT_ASSERT_EQUAL(OUString("baz"), last); + } + { + // Trailing slash accepted for backwards compatibility (cf + // . "for backwards compatibility, ignore a final slash" comment in + // Data::resolvePathRepresentation, configmgr/source/data.cxx): + OUString path, last; + CPPUNIT_ASSERT(utl::splitLastFromConfigurationPath(u"/foo/bar/baz/", path, last)); + CPPUNIT_ASSERT_EQUAL(OUString("/foo/bar"), path); + CPPUNIT_ASSERT_EQUAL(OUString("baz"), last); + } + { + OUString path, last; + CPPUNIT_ASSERT(utl::splitLastFromConfigurationPath( + u"/foo/bar/baz['abc']/baz['de&f']", path, last)); + CPPUNIT_ASSERT_EQUAL(OUString("/foo/bar/baz['abc']"), path); + CPPUNIT_ASSERT_EQUAL(OUString("de&f"), last); + } + { + OUString path, last; + CPPUNIT_ASSERT(!utl::splitLastFromConfigurationPath(u"foo", path, last)); + CPPUNIT_ASSERT_EQUAL(OUString(""), path); + CPPUNIT_ASSERT_EQUAL(OUString("foo"), last); + } + { + // In accordance with the documentation, this sets last to "foo/" ("If + // <var>_sInPath</var> could not be parsed as a valid configuration path, this is set to + // <var>_sInPath</var>"): + OUString path, last; + CPPUNIT_ASSERT(!utl::splitLastFromConfigurationPath(u"foo/", path, last)); + CPPUNIT_ASSERT_EQUAL(OUString(""), path); + CPPUNIT_ASSERT_EQUAL(OUString("foo/"), last); + } + { + // Some broken input missing a leading slash happens to be considered OK: + OUString path, last; + CPPUNIT_ASSERT(utl::splitLastFromConfigurationPath(u"foo/bar/baz", path, last)); + CPPUNIT_ASSERT_EQUAL(OUString("foo/bar"), path); + CPPUNIT_ASSERT_EQUAL(OUString("baz"), last); + } + } + + CPPUNIT_TEST_SUITE(Test); + CPPUNIT_TEST(testSplitLastFromConfigurationPath); + CPPUNIT_TEST_SUITE_END(); +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(Test); +} + +CPPUNIT_PLUGIN_IMPLEMENT(); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/unotools/qa/unit/testGetEnglishSearchName.cxx b/unotools/qa/unit/testGetEnglishSearchName.cxx new file mode 100644 index 0000000000..f20f9c8d46 --- /dev/null +++ b/unotools/qa/unit/testGetEnglishSearchName.cxx @@ -0,0 +1,73 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + */ + +#include <cppunit/TestFixture.h> +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/plugin/TestPlugIn.h> +#include <unotools/fontdefs.hxx> + +class Test: public CppUnit::TestFixture +{ +public: + void testSingleElement(); + + CPPUNIT_TEST_SUITE(Test); + CPPUNIT_TEST(testSingleElement); + + CPPUNIT_TEST_SUITE_END(); +}; + +void Test::testSingleElement() +{ + // lowercase + OUString test1 = GetEnglishSearchFontName( u"SYMBOL" ); + CPPUNIT_ASSERT_EQUAL( OUString("symbol"),test1); + //trailing whitespaces + test1 = GetEnglishSearchFontName( u"Symbol " ); + CPPUNIT_ASSERT_EQUAL(OUString("symbol"),test1); + //no longer remove script suffixes + test1 = GetEnglishSearchFontName( u"Symbol(SIP)" ); + CPPUNIT_ASSERT_EQUAL(OUString("symbol(sip)"),test1); + test1 = GetEnglishSearchFontName( u"CM Roman CE" ); + CPPUNIT_ASSERT_EQUAL( OUString("cmromance"),test1); + //remove special characters; leave semicolon, numbers + test1 = GetEnglishSearchFontName( u"sy;mb?=ol129" ); + CPPUNIT_ASSERT_EQUAL( OUString("sy;mbol129"),test1); + + //transformation + + //for Japanese fontname + // IPAMincho + static constexpr OUStringLiteral aIPAMincho=u"ipa\u660e\u671d"; + OUString test_ja_JP1 = GetEnglishSearchFontName(aIPAMincho); + CPPUNIT_ASSERT_EQUAL( OUString("ipamincho"),test_ja_JP1); + // IPAGothic + static constexpr OUStringLiteral aIPAGothic=u"ipa\u30b4\u30b7\u30c3\u30af"; + OUString test_ja_JP2 = GetEnglishSearchFontName(aIPAGothic); + CPPUNIT_ASSERT_EQUAL( OUString("ipagothic"),test_ja_JP2); + // HiraginoKakuGothic + static constexpr OUStringLiteral aHiraginoKakuGothic=u"\u30D2\u30E9\u30AE\u30CE\u89D2\u30B4\u30B7\u30C3\u30AF"; + OUString test_ja_JP3 = GetEnglishSearchFontName(aHiraginoKakuGothic); + CPPUNIT_ASSERT_EQUAL( OUString("hiraginosans"),test_ja_JP3); + // HiraginoMincho Pro N + static constexpr OUStringLiteral aHiraginoMinchoProN=u"\u30D2\u30E9\u30AE\u30CE\u660E\u671Dpron"; + OUString test_ja_JP4 = GetEnglishSearchFontName(aHiraginoMinchoProN); + CPPUNIT_ASSERT_EQUAL( OUString("hiraginominchopron"),test_ja_JP4); + // HiraginoMaruGothic Pro N + static constexpr OUStringLiteral aHiraginoMaruGothicProN + =u"\u30D2\u30E9\u30AE\u30CE\u4E38\u30B4pron"; + OUString test_ja_JP5 = GetEnglishSearchFontName(aHiraginoMaruGothicProN); + CPPUNIT_ASSERT_EQUAL( OUString("hiraginomarugothicpron"),test_ja_JP5); +} + +CPPUNIT_TEST_SUITE_REGISTRATION(Test); +CPPUNIT_PLUGIN_IMPLEMENT(); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/qa/unit/testRecodeString.cxx b/unotools/qa/unit/testRecodeString.cxx new file mode 100644 index 0000000000..b24a4a27e0 --- /dev/null +++ b/unotools/qa/unit/testRecodeString.cxx @@ -0,0 +1,45 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + */ + +#include <config_options.h> + +#include <cppunit/TestFixture.h> +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/plugin/TestPlugIn.h> + +#include <unotools/fontcvt.hxx> +#include <unotools/fontdefs.hxx> + +class Test : public CppUnit::TestFixture +{ +public: + void testRecodeString(); + + CPPUNIT_TEST_SUITE(Test); + CPPUNIT_TEST(testRecodeString); + + CPPUNIT_TEST_SUITE_END(); +}; + +void Test::testRecodeString() +{ +// note, the below won't work with mergelibs as the class is not visible to the linker +#if !ENABLE_MERGELIBS + ConvertChar const* pConversion = ConvertChar::GetRecodeData(u"starbats", u"opensymbol"); + OUString aStr(u"u"_ustr); + pConversion->RecodeString(aStr, 0, 1); + CPPUNIT_ASSERT_EQUAL(u""_ustr, aStr); +#endif +} + +CPPUNIT_TEST_SUITE_REGISTRATION(Test); +CPPUNIT_PLUGIN_IMPLEMENT(); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/accessibility/accessiblerelationsethelper.cxx b/unotools/source/accessibility/accessiblerelationsethelper.cxx new file mode 100644 index 0000000000..323b12c535 --- /dev/null +++ b/unotools/source/accessibility/accessiblerelationsethelper.cxx @@ -0,0 +1,170 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <o3tl/safeint.hxx> +#include <unotools/accessiblerelationsethelper.hxx> +#include <comphelper/sequence.hxx> + +using namespace ::utl; +using namespace ::com::sun::star; +using namespace ::com::sun::star::accessibility; + +namespace +{ + AccessibleRelation lcl_getRelationByType( std::vector<AccessibleRelation>& raRelations, sal_Int16 aRelationType ) + { + for (const auto& aRelation: raRelations) + { + if (aRelation.RelationType == aRelationType) + return aRelation; + } + return AccessibleRelation(); + } +} +//===== internal ============================================================ + +AccessibleRelationSetHelper::AccessibleRelationSetHelper () +{ +} + +AccessibleRelationSetHelper::AccessibleRelationSetHelper (const AccessibleRelationSetHelper& rHelper) + : cppu::WeakImplHelper<XAccessibleRelationSet>(rHelper), + maRelations(rHelper.maRelations) +{ +} + +AccessibleRelationSetHelper::~AccessibleRelationSetHelper() +{ +} + +//===== XAccessibleRelationSet ============================================== + + /** Returns the number of relations in this relation set. + + @return + Returns the number of relations or zero if there are none. + */ +sal_Int32 SAL_CALL + AccessibleRelationSetHelper::getRelationCount( ) +{ + std::scoped_lock aGuard (maMutex); + + return maRelations.size(); +} + + /** Returns the relation of this relation set that is specified by + the given index. + + @param nIndex + This index specifies the relatio to return. + + @return + For a valid index, i.e. inside the range 0 to the number of + relations minus one, the returned value is the requested + relation. If the index is invalid then the returned relation + has the type INVALID. + + */ + AccessibleRelation SAL_CALL + AccessibleRelationSetHelper::getRelation( sal_Int32 nIndex ) +{ + std::scoped_lock aGuard (maMutex); + + if ((nIndex < 0) || (o3tl::make_unsigned(nIndex) >= maRelations.size())) + throw lang::IndexOutOfBoundsException(); + + return maRelations[nIndex]; +} + + /** Tests whether the relation set contains a relation matching the + specified key. + + @param aRelationType + The type of relation to look for in this set of relations. This + has to be one of the constants of + <type>AccessibleRelationType</type>. + + @return + Returns <TRUE/> if there is a (at least one) relation of the + given type and <FALSE/> if there is no such relation in the set. + */ +sal_Bool SAL_CALL + AccessibleRelationSetHelper::containsRelation( sal_Int16 aRelationType ) +{ + std::scoped_lock aGuard (maMutex); + + AccessibleRelation defaultRelation; // default is INVALID + AccessibleRelation relationByType = lcl_getRelationByType(maRelations, aRelationType); + return relationByType.RelationType != defaultRelation.RelationType; +} + + /** Retrieve and return the relation with the given relation type. + + @param aRelationType + The type of the relation to return. This has to be one of the + constants of <type>AccessibleRelationType</type>. + + @return + If a relation with the given type could be found than (a copy + of) this relation is returned. Otherwise a relation with the + type INVALID is returned. + */ +AccessibleRelation SAL_CALL + AccessibleRelationSetHelper::getRelationByType( sal_Int16 aRelationType ) +{ + std::scoped_lock aGuard (maMutex); + + return lcl_getRelationByType(maRelations, aRelationType); +} + +void AccessibleRelationSetHelper::AddRelation(const AccessibleRelation& rRelation) +{ + std::scoped_lock aGuard (maMutex); + + for (auto& aRelation: maRelations) + { + if (aRelation.RelationType == rRelation.RelationType) + { + aRelation.TargetSet = comphelper::concatSequences(aRelation.TargetSet, rRelation.TargetSet); + return; + } + } + maRelations.push_back(rRelation); +} + +//===== XTypeProvider ======================================================= + +uno::Sequence< css::uno::Type> AccessibleRelationSetHelper::getTypes() +{ + static const uno::Sequence< css::uno::Type> aTypes { + cppu::UnoType<XAccessibleRelationSet>::get(), + cppu::UnoType<lang::XTypeProvider>::get() + }; + return aTypes; +} + +uno::Sequence<sal_Int8> SAL_CALL AccessibleRelationSetHelper::getImplementationId() +{ + return css::uno::Sequence<sal_Int8>(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/config/bootstrap.cxx b/unotools/source/config/bootstrap.cxx new file mode 100644 index 0000000000..6aaac63f8f --- /dev/null +++ b/unotools/source/config/bootstrap.cxx @@ -0,0 +1,720 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <string_view> + +#include <config_folders.h> + +#include <unotools/bootstrap.hxx> + +#include <rtl/ustring.hxx> +#include <rtl/ustrbuf.hxx> +#include <sal/log.hxx> +#include <osl/file.hxx> +#include <osl/diagnose.h> + +#include <rtl/bootstrap.hxx> +#include <osl/process.h> + +// #define this to true, if remembering defaults is not supported properly +#define RTL_BOOTSTRAP_DEFAULTS_BROKEN true + +constexpr OUString BOOTSTRAP_ITEM_PRODUCT_KEY = u"ProductKey"_ustr; +constexpr OUStringLiteral BOOTSTRAP_ITEM_VERSIONFILE = u"Location"; +constexpr OUStringLiteral BOOTSTRAP_ITEM_BUILDID = u"buildid"; + +constexpr OUStringLiteral BOOTSTRAP_ITEM_BASEINSTALLATION = u"BRAND_BASE_DIR"; +constexpr OUStringLiteral BOOTSTRAP_ITEM_USERINSTALLATION = u"UserInstallation"; + +constexpr OUString BOOTSTRAP_ITEM_USERDIR = u"UserDataDir"_ustr; + +constexpr OUStringLiteral BOOTSTRAP_DEFAULT_BASEINSTALL = u"$SYSBINDIR/.."; + +constexpr OUString BOOTSTRAP_DIRNAME_USERDIR = u"user"_ustr; + +typedef char const * AsciiString; + +namespace utl +{ + +// Implementation class: Bootstrap::Impl + +static OUString makeImplName() +{ + OUString uri; + rtl::Bootstrap::get( "BRAND_BASE_DIR", uri); + return uri + "/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("bootstrap"); +} + +class Bootstrap::Impl +{ + const OUString m_aImplName; +public: // struct to cache the result of a path lookup + struct PathData + { + OUString path; + PathStatus status; + + PathData() + : status(DATA_UNKNOWN) + {} + }; +public: // data members + // base install data + PathData aBaseInstall_; + + // user install data + PathData aUserInstall_; + + // INI files + PathData aBootstrapINI_; + PathData aVersionINI_; + + // overall status + Status status_; + +public: // construction and initialization + Impl() : m_aImplName(makeImplName()) + { + initialize(); + } + + void initialize(); + + // access helper + OUString getBootstrapValue(OUString const& _sName, OUString const& _sDefault) const; + + const OUString& getImplName() const { return m_aImplName; } + +private: // implementation + bool initBaseInstallationData(rtl::Bootstrap const & _rData); + bool initUserInstallationData(rtl::Bootstrap const & _rData); +}; + +namespace +{ + Bootstrap::Impl& theImpl() + { + static Bootstrap::Impl SINGLETON; + return SINGLETON; + } +} + +const Bootstrap::Impl& Bootstrap::data() +{ + return theImpl(); +} + +bool Bootstrap::getProcessWorkingDir(OUString &rUrl) +{ + rUrl.clear(); + OUString s("$OOO_CWD"); + rtl::Bootstrap::expandMacros(s); + if (s.isEmpty()) + { + if (osl_getProcessWorkingDir(&rUrl.pData) == osl_Process_E_None) + return true; + } + else if (s[0] == '1') + { + rUrl = s.copy(1); + return true; + } + else if (s[0] == '2' && + (osl::FileBase::getFileURLFromSystemPath(s.copy(1), rUrl) == + osl::FileBase::E_None)) + { + return true; + } + return false; +} + +void Bootstrap::reloadData() +{ + theImpl().initialize(); +} + +// helper + +typedef Bootstrap::PathStatus PathStatus; + +sal_Unicode const cURLSeparator = '/'; + +// path status utility function +static PathStatus implCheckStatusOfURL(OUString const& _sURL, osl::DirectoryItem& aDirItem) +{ + using namespace osl; + + PathStatus eStatus = Bootstrap::DATA_UNKNOWN; + + if (!_sURL.isEmpty()) + { + switch( DirectoryItem::get(_sURL, aDirItem) ) + { + case DirectoryItem::E_None: // Success + eStatus = Bootstrap::PATH_EXISTS; + break; + + case DirectoryItem::E_NOENT: // No such file or directory + eStatus = Bootstrap::PATH_VALID; + break; + + case DirectoryItem::E_INVAL: // the format of the parameters was not valid + case DirectoryItem::E_NAMETOOLONG: // File name too long + case DirectoryItem::E_NOTDIR: // A component of the path prefix of path is not a directory + eStatus = Bootstrap::DATA_INVALID; + break; + + // how to handle these ? + case DirectoryItem::E_LOOP: // Too many symbolic links encountered + case DirectoryItem::E_ACCES: // permission denied + // any other error - what to do ? + default: + eStatus = Bootstrap::DATA_UNKNOWN; + break; + } + } + else + { + eStatus = Bootstrap::DATA_MISSING; + } + + return eStatus; +} + +static bool implNormalizeURL(OUString & _sURL, osl::DirectoryItem& aDirItem) +{ + using namespace osl; + + OSL_PRECOND(aDirItem.is(), "Opened DirItem required"); + + static const sal_uInt32 cosl_FileStatus_Mask = osl_FileStatus_Mask_FileURL; + + FileStatus aFileStatus(cosl_FileStatus_Mask); + + if (aDirItem.getFileStatus(aFileStatus) != DirectoryItem::E_None) + return false; + + OUString aNormalizedURL = aFileStatus.getFileURL(); + + if (aNormalizedURL.isEmpty()) + return false; + + // #109863# sal/osl returns final slash for file URLs contradicting + // the URL/URI RFCs. + if ( !aNormalizedURL.endsWith(OUStringChar(cURLSeparator)) ) + _sURL = aNormalizedURL; + else + _sURL = aNormalizedURL.copy( 0, aNormalizedURL.getLength()-1 ); + + return true; +} + +static bool implEnsureAbsolute(OUString & _rsURL) // also strips embedded dots !! +{ + using osl::File; + + OUString sBasePath; + OSL_VERIFY(Bootstrap::getProcessWorkingDir(sBasePath)); + + OUString sAbsolute; + if ( File::E_None == File::getAbsoluteFileURL(sBasePath, _rsURL, sAbsolute)) + { + _rsURL = sAbsolute; + return true; + } + else + { + OSL_FAIL("Could not get absolute file URL for URL"); + return false; + } +} + +static bool implMakeAbsoluteURL(OUString & _rsPathOrURL) +{ + using namespace osl; + + bool bURL; + + OUString sOther; + // check if it already was normalized + if ( File::E_None == File::getSystemPathFromFileURL(_rsPathOrURL, sOther) ) + { + bURL = true; + } + + else if ( File::E_None == File::getFileURLFromSystemPath(_rsPathOrURL, sOther) ) + { + _rsPathOrURL = sOther; + bURL = true; + } + else + bURL = false; + + return bURL && implEnsureAbsolute(_rsPathOrURL); +} + +static PathStatus dbgCheckStatusOfURL(OUString const& _sURL) +{ + using namespace osl; + + DirectoryItem aDirItem; + + return implCheckStatusOfURL(_sURL,aDirItem); +} + +static PathStatus checkStatusAndNormalizeURL(OUString & _sURL) +{ + using namespace osl; + + PathStatus eStatus = Bootstrap::DATA_UNKNOWN; + + if (_sURL.isEmpty()) + eStatus = Bootstrap::DATA_MISSING; + + else if ( !implMakeAbsoluteURL(_sURL) ) + eStatus = Bootstrap::DATA_INVALID; + + else + { + DirectoryItem aDirItem; + + eStatus = implCheckStatusOfURL(_sURL,aDirItem); + + if (eStatus == Bootstrap::PATH_EXISTS && !implNormalizeURL(_sURL,aDirItem)) + OSL_FAIL("Unexpected failure getting actual URL for existing object"); + } + return eStatus; +} + +// helpers to build and check a nested URL +static PathStatus getDerivedPath( + OUString& _rURL, + OUString const& _aBaseURL, PathStatus _aBaseStatus, + std::u16string_view _sRelativeURL, + rtl::Bootstrap const & _rData, OUString const& _sBootstrapParameter + ) +{ + OUString sDerivedURL; + OSL_PRECOND(!_rData.getFrom(_sBootstrapParameter,sDerivedURL),"Setting for derived path is already defined"); + OSL_PRECOND(!_sRelativeURL.empty() && _sRelativeURL[0] != cURLSeparator,"Invalid Relative URL"); + + PathStatus aStatus = _aBaseStatus; + + // do we have a base path ? + if (!_aBaseURL.isEmpty()) + { + OSL_PRECOND(!_aBaseURL.endsWith(OUStringChar(cURLSeparator)), "Unexpected: base URL ends in slash"); + + sDerivedURL = _aBaseURL + OUStringChar(cURLSeparator) + _sRelativeURL; + + // a derived (nested) URL can only exist or have a lesser status, if the parent exists + if (aStatus == Bootstrap::PATH_EXISTS) + aStatus = checkStatusAndNormalizeURL(sDerivedURL); + + else // the relative appendix must be valid + OSL_ASSERT(aStatus != Bootstrap::PATH_VALID || dbgCheckStatusOfURL(sDerivedURL) == Bootstrap::PATH_VALID); + + _rData.getFrom(_sBootstrapParameter, _rURL, sDerivedURL); + + OSL_ENSURE(sDerivedURL == _rURL,"Could not set derived URL via Bootstrap default parameter"); + SAL_WARN_IF( !(RTL_BOOTSTRAP_DEFAULTS_BROKEN || (_rData.getFrom(_sBootstrapParameter,sDerivedURL) && sDerivedURL==_rURL)), + "unotools.config", + "Use of default did not affect bootstrap value"); + } + else + { + // clear the result + _rURL = _aBaseURL; + + // if we have no data it can't be a valid path + OSL_ASSERT( aStatus > Bootstrap::PATH_VALID ); + } + + return aStatus; +} + +static PathStatus getDerivedPath( + OUString& _rURL, + Bootstrap::Impl::PathData const& _aBaseData, + std::u16string_view _sRelativeURL, + rtl::Bootstrap const & _rData, OUString const& _sBootstrapParameter + ) +{ + return getDerivedPath(_rURL,_aBaseData.path,_aBaseData.status,_sRelativeURL,_rData,_sBootstrapParameter); +} + +static OUString getExecutableBaseName() +{ + OUString sExecutable; + + if (osl_Process_E_None == osl_getExecutableFile(&sExecutable.pData)) + { + // split the executable name + sal_Int32 nSepIndex = sExecutable.lastIndexOf(cURLSeparator); + + sExecutable = sExecutable.copy(nSepIndex + 1); + + // ... and get the basename (strip the extension) + sal_Unicode const cExtensionSep = '.'; + + sal_Int32 const nExtIndex = sExecutable.lastIndexOf(cExtensionSep); + sal_Int32 const nExtLength = sExecutable.getLength() - nExtIndex - 1; + if (0 < nExtIndex && nExtLength < 4) + sExecutable = sExecutable.copy(0,nExtIndex); + } + else + SAL_WARN("unotools.config", "Cannot get executable name: osl_getExecutableFile failed"); + + return sExecutable; +} + +static Bootstrap::PathStatus updateStatus(Bootstrap::Impl::PathData & _rResult) +{ + _rResult.status = checkStatusAndNormalizeURL(_rResult.path); + return _rResult.status; +} + +static Bootstrap::PathStatus implGetBootstrapFile(rtl::Bootstrap const & _rData, Bootstrap::Impl::PathData & _rBootstrapFile) +{ + _rData.getIniName(_rBootstrapFile.path); + + return updateStatus(_rBootstrapFile); +} + +static Bootstrap::PathStatus implGetVersionFile(rtl::Bootstrap const & _rData, Bootstrap::Impl::PathData & _rVersionFile) +{ + _rData.getFrom(BOOTSTRAP_ITEM_VERSIONFILE, _rVersionFile.path); + + return updateStatus(_rVersionFile); +} + +// Error reporting + +char const IS_MISSING[] = "is missing"; +char const IS_INVALID[] = "is corrupt"; +char const PERIOD[] = ". "; + +static void addFileError(OUStringBuffer& _rBuf, std::u16string_view _aPath, AsciiString _sWhat) +{ + std::u16string_view sSimpleFileName = _aPath.substr(1 +_aPath.rfind(cURLSeparator)); + + _rBuf.append("The configuration file"); + _rBuf.append(OUString::Concat(" '") + sSimpleFileName + "' "); + _rBuf.appendAscii(_sWhat).append(PERIOD); +} + +static void addMissingDirectoryError(OUStringBuffer& _rBuf, std::u16string_view _aPath) +{ + _rBuf.append(OUString::Concat("The configuration directory '") + _aPath + "' " + + IS_MISSING + PERIOD); +} + +static void addUnexpectedError(OUStringBuffer& _rBuf, AsciiString _sExtraInfo = nullptr) +{ + if (nullptr == _sExtraInfo) + _sExtraInfo = "An internal failure occurred"; + + _rBuf.appendAscii(_sExtraInfo).append(PERIOD); +} + +static Bootstrap::FailureCode describeError(OUStringBuffer& _rBuf, Bootstrap::Impl const& _rData) +{ + Bootstrap::FailureCode eErrCode = Bootstrap::INVALID_BOOTSTRAP_DATA; + + _rBuf.append("The program cannot be started. "); + + switch (_rData.aUserInstall_.status) + { + case Bootstrap::PATH_EXISTS: + switch (_rData.aBaseInstall_.status) + { + case Bootstrap::PATH_VALID: + addMissingDirectoryError(_rBuf, _rData.aBaseInstall_.path); + eErrCode = Bootstrap::MISSING_INSTALL_DIRECTORY; + break; + + case Bootstrap::DATA_INVALID: + addUnexpectedError(_rBuf,"The installation path is invalid"); + break; + + case Bootstrap::DATA_MISSING: + addUnexpectedError(_rBuf,"The installation path is not available"); + break; + + case Bootstrap::PATH_EXISTS: // seems to be all fine (?) + addUnexpectedError(_rBuf,""); + break; + + default: OSL_ASSERT(false); + addUnexpectedError(_rBuf); + break; + } + break; + + case Bootstrap::PATH_VALID: + addMissingDirectoryError(_rBuf, _rData.aUserInstall_.path); + eErrCode = Bootstrap::MISSING_USER_DIRECTORY; + break; + + // else fall through + case Bootstrap::DATA_INVALID: + if (_rData.aVersionINI_.status == Bootstrap::PATH_EXISTS) + { + addFileError(_rBuf, _rData.aVersionINI_.path, IS_INVALID); + eErrCode = Bootstrap::INVALID_VERSION_FILE_ENTRY; + break; + } + [[fallthrough]]; + + case Bootstrap::DATA_MISSING: + switch (_rData.aVersionINI_.status) + { + case Bootstrap::PATH_EXISTS: + addFileError(_rBuf, _rData.aVersionINI_.path, "does not support the current version"); + eErrCode = Bootstrap::MISSING_VERSION_FILE_ENTRY; + break; + + case Bootstrap::PATH_VALID: + addFileError(_rBuf, _rData.aVersionINI_.path, IS_MISSING); + eErrCode = Bootstrap::MISSING_VERSION_FILE; + break; + + default: + switch (_rData.aBootstrapINI_.status) + { + case Bootstrap::PATH_EXISTS: + addFileError(_rBuf, _rData.aBootstrapINI_.path, IS_INVALID); + + if (_rData.aVersionINI_.status == Bootstrap::DATA_MISSING) + eErrCode = Bootstrap::MISSING_BOOTSTRAP_FILE_ENTRY; + else + eErrCode = Bootstrap::INVALID_BOOTSTRAP_FILE_ENTRY; + break; + + case Bootstrap::DATA_INVALID: OSL_ASSERT(false); [[fallthrough]]; + case Bootstrap::PATH_VALID: + addFileError(_rBuf, _rData.aBootstrapINI_.path, IS_MISSING); + eErrCode = Bootstrap::MISSING_BOOTSTRAP_FILE; + break; + + default: + addUnexpectedError(_rBuf); + break; + } + break; + } + break; + + default: OSL_ASSERT(false); + addUnexpectedError(_rBuf); + break; + } + + return eErrCode; +} + + +OUString Bootstrap::getProductKey() +{ + OUString const sDefaultProductKey = getExecutableBaseName(); + + return data().getBootstrapValue( BOOTSTRAP_ITEM_PRODUCT_KEY, sDefaultProductKey ); +} + +OUString Bootstrap::getProductKey(OUString const& _sDefault) +{ + return data().getBootstrapValue( BOOTSTRAP_ITEM_PRODUCT_KEY, _sDefault ); +} + +OUString Bootstrap::getBuildIdData(OUString const& _sDefault) +{ + // try to open version.ini (versionrc) + OUString uri; + rtl::Bootstrap::get( "BRAND_BASE_DIR", uri); + rtl::Bootstrap aData( uri + "/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("version") ); + if ( aData.getHandle() == nullptr ) + // version.ini (versionrc) doesn't exist + return _sDefault; + + // read value + OUString sBuildId; + aData.getFrom(BOOTSTRAP_ITEM_BUILDID,sBuildId,_sDefault); + return sBuildId; +} + +Bootstrap::PathStatus Bootstrap::locateBaseInstallation(OUString& _rURL) +{ + Impl::PathData const& aPathData = data().aBaseInstall_; + + _rURL = aPathData.path; + return aPathData.status; +} + +Bootstrap::PathStatus Bootstrap::locateUserInstallation(OUString& _rURL) +{ + Impl::PathData const& aPathData = data().aUserInstall_; + + _rURL = aPathData.path; + return aPathData.status; +} + +Bootstrap::PathStatus Bootstrap::locateUserData(OUString& _rURL) +{ + OUString const csUserDirItem(BOOTSTRAP_ITEM_USERDIR); + + rtl::Bootstrap aData( data().getImplName() ); + + if ( aData.getFrom(csUserDirItem, _rURL) ) + { + return checkStatusAndNormalizeURL(_rURL); + } + else + { + return getDerivedPath(_rURL, data().aUserInstall_ ,BOOTSTRAP_DIRNAME_USERDIR, aData, csUserDirItem); + } +} + +Bootstrap::PathStatus Bootstrap::locateBootstrapFile(OUString& _rURL) +{ + Impl::PathData const& aPathData = data().aBootstrapINI_; + + _rURL = aPathData.path; + return aPathData.status; +} + +Bootstrap::PathStatus Bootstrap::locateVersionFile(OUString& _rURL) +{ + Impl::PathData const& aPathData = data().aVersionINI_; + + _rURL = aPathData.path; + return aPathData.status; +} + +Bootstrap::Status Bootstrap::checkBootstrapStatus(OUString& _rDiagnosticMessage, FailureCode& _rErrCode) +{ + Impl const& aData = data(); + + Status result = aData.status_; + + // maybe do further checks here + + OUStringBuffer sErrorBuffer; + if (result != DATA_OK) + _rErrCode = describeError(sErrorBuffer,aData); + + else + _rErrCode = NO_FAILURE; + + _rDiagnosticMessage = sErrorBuffer.makeStringAndClear(); + + return result; +} + + +bool Bootstrap::Impl::initBaseInstallationData(rtl::Bootstrap const & _rData) +{ + _rData.getFrom(BOOTSTRAP_ITEM_BASEINSTALLATION, aBaseInstall_.path, BOOTSTRAP_DEFAULT_BASEINSTALL); + + bool bResult = (PATH_EXISTS == updateStatus(aBaseInstall_)); + + implGetBootstrapFile(_rData, aBootstrapINI_); + + return bResult; +} + +bool Bootstrap::Impl::initUserInstallationData(rtl::Bootstrap const & _rData) +{ + if (_rData.getFrom(BOOTSTRAP_ITEM_USERINSTALLATION, aUserInstall_.path)) + { + updateStatus(aUserInstall_); + } + else + { + // should we do just this + aUserInstall_.status = DATA_MISSING; + + // ... or this - look for a single-user user directory ? + OUString const csUserDirItem(BOOTSTRAP_ITEM_USERDIR); + OUString sDummy; + // look for $BASEINSTALLATION/user only if default UserDir setting is used + if (! _rData.getFrom(csUserDirItem, sDummy)) + { + if ( PATH_EXISTS == getDerivedPath(sDummy, aBaseInstall_, BOOTSTRAP_DIRNAME_USERDIR, _rData, csUserDirItem) ) + aUserInstall_ = aBaseInstall_; + } + } + + bool bResult = (PATH_EXISTS == aUserInstall_.status); + + implGetVersionFile(_rData, aVersionINI_); + + return bResult; +} + +void Bootstrap::Impl::initialize() +{ + rtl::Bootstrap aData( m_aImplName ); + + if (!initBaseInstallationData(aData)) + { + status_ = INVALID_BASE_INSTALL; + } + else if (!initUserInstallationData(aData)) + { + status_ = INVALID_USER_INSTALL; + + if (aUserInstall_.status >= DATA_MISSING) + { + switch (aVersionINI_.status) + { + case PATH_EXISTS: + case PATH_VALID: + status_ = MISSING_USER_INSTALL; + break; + + case DATA_INVALID: + case DATA_MISSING: + status_ = INVALID_BASE_INSTALL; + break; + default: + break; + } + } + } + else + { + status_ = DATA_OK; + } +} + +OUString Bootstrap::Impl::getBootstrapValue(OUString const& _sName, OUString const& _sDefault) const +{ + rtl::Bootstrap aData( m_aImplName ); + + OUString sResult; + aData.getFrom(_sName,sResult,_sDefault); + return sResult; +} + +} // namespace utl + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/config/cmdoptions.cxx b/unotools/source/config/cmdoptions.cxx new file mode 100644 index 0000000000..015637d644 --- /dev/null +++ b/unotools/source/config/cmdoptions.cxx @@ -0,0 +1,337 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <sal/log.hxx> +#include <unotools/cmdoptions.hxx> +#include <unotools/configitem.hxx> +#include <tools/debug.hxx> +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/frame/XFrame.hpp> +#include <cppuhelper/weakref.hxx> + +#include "itemholder1.hxx" + +#include <algorithm> +#include <unordered_map> + +using namespace ::utl; +using namespace ::osl; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::beans; + +constexpr OUStringLiteral ROOTNODE_CMDOPTIONS = u"Office.Commands/Execute"; +#define PATHDELIMITER "/" + +#define SETNODE_DISABLED "Disabled" + +#define PROPERTYNAME_CMD "Command" + +namespace { + +/*-**************************************************************************************************************** + @descr support simple command option structures and operations on it +****************************************************************************************************************-*/ +class SvtCmdOptions +{ + public: + + // the only way to free memory! + void Clear() + { + m_aCommandHashMap.clear(); + } + + bool HasEntries() const + { + return ( !m_aCommandHashMap.empty() ); + } + + bool Lookup( const OUString& aCmd ) const + { + CommandHashMap::const_iterator pEntry = m_aCommandHashMap.find( aCmd ); + return ( pEntry != m_aCommandHashMap.end() ); + } + + void AddCommand( const OUString& aCmd ) + { + m_aCommandHashMap.emplace( aCmd, 0 ); + } + + private: + typedef std::unordered_map<OUString, sal_Int32> + CommandHashMap; + + CommandHashMap m_aCommandHashMap; +}; + +std::mutex& GetOwnStaticMutex() +{ + static std::mutex theCommandOptionsMutex; + return theCommandOptionsMutex; +} + +} + +typedef ::std::vector< css::uno::WeakReference< css::frame::XFrame > > SvtFrameVector; + +class SvtCommandOptions_Impl : public ConfigItem +{ + public: + + SvtCommandOptions_Impl(); + virtual ~SvtCommandOptions_Impl() override; + + /*-**************************************************************************************************** + @short called for notify of configmanager + @descr This method is called from the ConfigManager before the application ends or from the + PropertyChangeListener if the sub tree broadcasts changes. You must update your + internal values. + + @seealso baseclass ConfigItem + + @param "lPropertyNames" is the list of properties which should be updated. + *//*-*****************************************************************************************************/ + + virtual void Notify( const Sequence< OUString >& lPropertyNames ) override; + + /*-**************************************************************************************************** + @short base implementation of public interface for "SvtDynamicMenuOptions"! + @descr These class is used as static member of "SvtDynamicMenuOptions" ... + => The code exist only for one time and isn't duplicated for every instance! + *//*-*****************************************************************************************************/ + + bool HasEntriesDisabled() const; + bool LookupDisabled( const OUString& ) const; + void EstablishFrameCallback(const css::uno::Reference< css::frame::XFrame >& xFrame); + + private: + + virtual void ImplCommit() override; + + /*-**************************************************************************************************** + @short return list of key names of our configuration management which represent our module tree + @descr This method returns the current list of key names! We need it to get needed values from our + configuration management and support dynamical menu item lists! + @param "nDisabledCount", returns count of menu entries for "new" + @return A list of configuration key names is returned. + *//*-*****************************************************************************************************/ + + Sequence< OUString > impl_GetPropertyNames(); + + private: + SvtCmdOptions m_aDisabledCommands; + SvtFrameVector m_lFrames; +}; + +// constructor + +SvtCommandOptions_Impl::SvtCommandOptions_Impl() + // Init baseclasses first + : ConfigItem( ROOTNODE_CMDOPTIONS ) + // Init member then... +{ + // Get names and values of all accessible menu entries and fill internal structures. + // See impl_GetPropertyNames() for further information. + Sequence< OUString > lNames = impl_GetPropertyNames (); + Sequence< Any > lValues = GetProperties ( lNames ); + + // Safe impossible cases. + // We need values from ALL configuration keys. + // Follow assignment use order of values in relation to our list of key names! + DBG_ASSERT( !(lNames.getLength()!=lValues.getLength()), "SvtCommandOptions_Impl::SvtCommandOptions_Impl()\nI miss some values of configuration keys!\n" ); + + // Copy values from list in right order to our internal member. + // Attention: List for names and values have an internal construction pattern! + sal_Int32 nItem = 0; + OUString sCmd; + + // Get names/values for disabled commands. + for( nItem=0; nItem < lNames.getLength(); ++nItem ) + { + // Currently only one value + lValues[nItem] >>= sCmd; + m_aDisabledCommands.AddCommand( sCmd ); + } + +/*TODO: Not used in the moment! see Notify() ... + // Enable notification mechanism of our baseclass. + // We need it to get information about changes outside these class on our used configuration keys! */ + Sequence<OUString> aNotifySeq { "Disabled" }; + EnableNotification( aNotifySeq, true ); +} + +// destructor + +SvtCommandOptions_Impl::~SvtCommandOptions_Impl() +{ + assert(!IsModified()); // should have been committed +} + +// public method + +void SvtCommandOptions_Impl::Notify( const Sequence< OUString >& ) +{ + std::unique_lock aGuard( GetOwnStaticMutex() ); + + Sequence< OUString > lNames = impl_GetPropertyNames (); + Sequence< Any > lValues = GetProperties ( lNames ); + + // Safe impossible cases. + // We need values from ALL configuration keys. + // Follow assignment use order of values in relation to our list of key names! + DBG_ASSERT( !(lNames.getLength()!=lValues.getLength()), "SvtCommandOptions_Impl::Notify()\nI miss some values of configuration keys!\n" ); + + // Copy values from list in right order to our internal member. + // Attention: List for names and values have an internal construction pattern! + sal_Int32 nItem = 0; + OUString sCmd; + + m_aDisabledCommands.Clear(); + + // Get names/values for disabled commands. + for( nItem=0; nItem < lNames.getLength(); ++nItem ) + { + // Currently only one value + lValues[nItem] >>= sCmd; + m_aDisabledCommands.AddCommand( sCmd ); + } + + // don't forget to update all existing frames and her might cached dispatch objects! + // But look for already killed frames. We hold weak references instead of hard ones ... + for (SvtFrameVector::iterator pIt = m_lFrames.begin(); pIt != m_lFrames.end(); ) + { + css::uno::Reference< css::frame::XFrame > xFrame(pIt->get(), css::uno::UNO_QUERY); + if (xFrame.is()) + { + aGuard.unlock(); // because we can call back into ourself + xFrame->contextChanged(); + aGuard.lock(); + ++pIt; + } + else + pIt = m_lFrames.erase(pIt); + } +} + +// public method + +void SvtCommandOptions_Impl::ImplCommit() +{ + SAL_WARN("unotools.config","SvtCommandOptions_Impl::ImplCommit(): Not implemented yet!"); +} + +// public method + +bool SvtCommandOptions_Impl::HasEntriesDisabled() const +{ + return m_aDisabledCommands.HasEntries(); +} + +// public method + +bool SvtCommandOptions_Impl::LookupDisabled( const OUString& aCommand ) const +{ + return m_aDisabledCommands.Lookup( aCommand ); +} + +// public method + +void SvtCommandOptions_Impl::EstablishFrameCallback(const css::uno::Reference< css::frame::XFrame >& xFrame) +{ + // check if frame already exists inside list + // ignore double registrations + // every frame must be notified one times only! + css::uno::WeakReference< css::frame::XFrame > xWeak(xFrame); + SvtFrameVector::const_iterator pIt = ::std::find(m_lFrames.begin(), m_lFrames.end(), xWeak); + if (pIt == m_lFrames.end()) + m_lFrames.push_back(xWeak); +} + +// private method + +Sequence< OUString > SvtCommandOptions_Impl::impl_GetPropertyNames() +{ + // First get ALL names of current existing list items in configuration! + Sequence< OUString > lDisabledItems = GetNodeNames( SETNODE_DISABLED, utl::ConfigNameFormat::LocalPath ); + + // Expand all keys + for (OUString& rItem : asNonConstRange(lDisabledItems)) + rItem = SETNODE_DISABLED PATHDELIMITER + rItem + PATHDELIMITER PROPERTYNAME_CMD; + + // Return result. + return lDisabledItems; +} + +namespace { + +std::weak_ptr<SvtCommandOptions_Impl> g_pCommandOptions; + +} + +SvtCommandOptions::SvtCommandOptions() +{ + // Global access, must be guarded (multithreading!). + std::unique_lock aGuard( GetOwnStaticMutex() ); + + m_pImpl = g_pCommandOptions.lock(); + if( !m_pImpl ) + { + m_pImpl = std::make_shared<SvtCommandOptions_Impl>(); + g_pCommandOptions = m_pImpl; + aGuard.unlock(); // because holdConfigItem will call this constructor + ItemHolder1::holdConfigItem(EItem::CmdOptions); + } +} + +SvtCommandOptions::~SvtCommandOptions() +{ + // Global access, must be guarded (multithreading!) + std::unique_lock aGuard( GetOwnStaticMutex() ); + + m_pImpl.reset(); +} + +// public method + +bool SvtCommandOptions::HasEntriesDisabled() const +{ + std::unique_lock aGuard( GetOwnStaticMutex() ); + return m_pImpl->HasEntriesDisabled(); +} + +// public method + +bool SvtCommandOptions::LookupDisabled( const OUString& aCommandURL ) const +{ + std::unique_lock aGuard( GetOwnStaticMutex() ); + return m_pImpl->LookupDisabled( aCommandURL ); +} + +// public method + +void SvtCommandOptions::EstablishFrameCallback(const css::uno::Reference< css::frame::XFrame >& xFrame) +{ + std::unique_lock aGuard( GetOwnStaticMutex() ); + m_pImpl->EstablishFrameCallback(xFrame); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/config/compatibility.cxx b/unotools/source/config/compatibility.cxx new file mode 100644 index 0000000000..a5c8aec158 --- /dev/null +++ b/unotools/source/config/compatibility.cxx @@ -0,0 +1,415 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <unotools/compatibility.hxx> +#include <unotools/configitem.hxx> +#include <unotools/syslocale.hxx> +#include <tools/debug.hxx> +#include <sal/log.hxx> +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/lang/Locale.hpp> +#include <i18nlangtag/languagetag.hxx> + +#include "itemholder1.hxx" + +#include <algorithm> + +using namespace ::utl; +using namespace ::osl; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::beans; + +constexpr OUStringLiteral ROOTNODE_OPTIONS = u"Office.Compatibility"; +#define PATHDELIMITER "/" +#define SETNODE_ALLFILEFORMATS "AllFileFormats" + +SvtCompatibilityEntry::SvtCompatibilityEntry() + : m_aPropertyValue( SvtCompatibilityEntry::getElementCount() ) +{ + /* Should be in the start. Do not remove it. */ + setValue<OUString>( Index::Name, OUString() ); + setValue<OUString>( Index::Module, OUString() ); + + /* Editable list of default values. Sync it with the SvtCompatibilityEntry::Index enum class. */ + setValue<bool>( Index::AddSpacing, false ); + setValue<bool>( Index::AddSpacingAtPages, false ); + setValue<bool>( Index::UseOurTabStops, false ); + setValue<bool>( Index::NoExtLeading, false ); + setValue<bool>( Index::UseLineSpacing, false ); + setValue<bool>( Index::AddTableSpacing, false ); + setValue<bool>( Index::UseObjectPositioning, false ); + setValue<bool>( Index::UseOurTextWrapping, false ); + setValue<bool>( Index::ConsiderWrappingStyle, false ); + setValue<bool>( Index::ExpandWordSpace, true ); + setValue<bool>( Index::ProtectForm, false ); + setValue<bool>( Index::MsWordTrailingBlanks, false ); + setValue<bool>( Index::SubtractFlysAnchoredAtFlys, false ); + setValue<bool>( Index::EmptyDbFieldHidesPara, true ); + setValue<bool>( Index::AddTableLineSpacing, false ); + setValue<bool>( Index::UseVariableWidthNBSP, false ); +} + +OUString SvtCompatibilityEntry::getName( const Index rIdx ) +{ + static const char* sPropertyName[] = + { + /* Should be in the start. Do not remove it. */ + "Name", + "Module", + + /* Editable list of compatibility option names. Sync it with the SvtCompatibilityEntry::Index enum class. */ + "AddSpacing", + "AddSpacingAtPages", + "UseOurTabStopFormat", + "NoExternalLeading", + "UseLineSpacing", + "AddTableSpacing", + "UseObjectPositioning", + "UseOurTextWrapping", + "ConsiderWrappingStyle", + "ExpandWordSpace", + "ProtectForm", + "MsWordCompTrailingBlanks", + "SubtractFlysAnchoredAtFlys", + "EmptyDbFieldHidesPara", + "UseVariableWidthNBSP", + + "AddTableLineSpacing" // Must be the last one + }; + + /* Size of sPropertyName array not equal size of the SvtCompatibilityEntry::Index enum class */ + assert( std::size(sPropertyName) == SvtCompatibilityEntry::getElementCount() ); + + return OUString::createFromAscii( sPropertyName[ static_cast<int>(rIdx) ] ); +} + +/*-**************************************************************************************************************** + @descr support simple menu structures and operations on it +****************************************************************************************************************-*/ + +/*-**************************************************************************************************** + @short base implementation of public interface for "SvtCompatibilityOptions"! + @descr These class is used as static member of "SvtCompatibilityOptions" ... + => The code exist only for one time and isn't duplicated for every instance! +*//*-*****************************************************************************************************/ +class SvtCompatibilityOptions_Impl : public ConfigItem +{ + public: + SvtCompatibilityOptions_Impl(); + virtual ~SvtCompatibilityOptions_Impl() override; + + void AppendItem( const SvtCompatibilityEntry& aItem ); + void Clear(); + + void SetDefault( SvtCompatibilityEntry::Index rIdx, bool rValue ); + bool GetDefault( SvtCompatibilityEntry::Index rIdx ) const; + + const std::vector< SvtCompatibilityEntry > & GetOptions() const { return m_aOptions; } + bool GetOptionReadOnly( SvtCompatibilityEntry::Index rIdx ) const; + bool GetDefaultOptionReadOnly( SvtCompatibilityEntry::Index rIdx ) const; + bool HaveDefaultReadOnlyOption() const; + + /*-**************************************************************************************************** + @short called for notify of configmanager + @descr This method is called from the ConfigManager before the application ends or from the + PropertyChangeListener if the sub tree broadcasts changes. You must update your + internal values. + + @seealso baseclass ConfigItem + + @param "lPropertyNames" is the list of properties which should be updated. + *//*-*****************************************************************************************************/ + virtual void Notify( const Sequence< OUString >& lPropertyNames ) override; + + private: + virtual void ImplCommit() override; + + /*-**************************************************************************************************** + @short return list of key names of our configuration management which represent one module tree + @descr These methods return the current list of key names! We need it to get needed values from our + configuration management and support dynamical menu item lists! + @return A list of configuration key names is returned. + *//*-*****************************************************************************************************/ + Sequence< OUString > impl_GetPropertyNames( Sequence< OUString >& rItems ); + + private: + std::vector< SvtCompatibilityEntry > m_aOptions; + SvtCompatibilityEntry m_aDefOptions; +}; + +SvtCompatibilityOptions_Impl::SvtCompatibilityOptions_Impl() : ConfigItem( ROOTNODE_OPTIONS ) +{ + // Get names and values of all accessible menu entries and fill internal structures. + // See impl_GetPropertyNames() for further information. + Sequence< OUString > lNodes; + Sequence< OUString > lNames = impl_GetPropertyNames( lNodes ); + Sequence< Any > lValues = GetProperties( lNames ); + Sequence< sal_Bool > lReadOnly = GetReadOnlyStates( lNames ); + + // Safe impossible cases. + // We need values from ALL configuration keys. + // Follow assignment use order of values in relation to our list of key names! + DBG_ASSERT( !( lNames.getLength()!=lValues.getLength() ), "SvtCompatibilityOptions_Impl::SvtCompatibilityOptions_Impl()\nI miss some values of configuration keys!\n" ); + + // Get names/values for new menu. + // 4 subkeys for every item! + bool bDefaultFound = false; + sal_Int32 nDestStep = 0; + for ( const auto& rNode : std::as_const(lNodes) ) + { + SvtCompatibilityEntry aItem; + + aItem.setValue<OUString>( SvtCompatibilityEntry::Index::Name, rNode ); + + for ( int i = static_cast<int>(SvtCompatibilityEntry::Index::Module); i < static_cast<int>(SvtCompatibilityEntry::Index::INVALID); ++i ) + { + aItem.setValue( SvtCompatibilityEntry::Index(i), lValues[ nDestStep ] ); + aItem.setPropertyReadOnly( SvtCompatibilityEntry::Index(i), lReadOnly[ nDestStep ] ); + nDestStep++; + } + + m_aOptions.push_back( aItem ); + + if ( !bDefaultFound && aItem.getValue<OUString>( SvtCompatibilityEntry::Index::Name ) == SvtCompatibilityEntry::DEFAULT_ENTRY_NAME ) + { + SvtSysLocale aSysLocale; + css::lang::Locale aLocale = aSysLocale.GetLanguageTag().getLocale(); + if ( aLocale.Language == "zh" || aLocale.Language == "ja" || aLocale.Language == "ko" ) + aItem.setValue<bool>( SvtCompatibilityEntry::Index::ExpandWordSpace, false ); + + m_aDefOptions = aItem; + bDefaultFound = true; + } + } +} + +SvtCompatibilityOptions_Impl::~SvtCompatibilityOptions_Impl() +{ + assert( !IsModified() ); // should have been committed +} + +void SvtCompatibilityOptions_Impl::AppendItem( const SvtCompatibilityEntry& aItem ) +{ + m_aOptions.push_back( aItem ); + + // default item reset? + if ( aItem.getValue<OUString>( SvtCompatibilityEntry::Index::Name ) == SvtCompatibilityEntry::DEFAULT_ENTRY_NAME ) + m_aDefOptions = aItem; + + SetModified(); +} + +void SvtCompatibilityOptions_Impl::Clear() +{ + m_aOptions.clear(); + + SetModified(); +} + +void SvtCompatibilityOptions_Impl::SetDefault( SvtCompatibilityEntry::Index rIdx, bool rValue ) +{ + /* Are not set Name and Module */ + assert( rIdx != SvtCompatibilityEntry::Index::Name && rIdx != SvtCompatibilityEntry::Index::Module ); + + m_aDefOptions.setValue<bool>( rIdx, rValue ); +} + +bool SvtCompatibilityOptions_Impl::GetDefault( SvtCompatibilityEntry::Index rIdx ) const +{ + /* Are not set Name and Module */ + assert( rIdx != SvtCompatibilityEntry::Index::Name && rIdx != SvtCompatibilityEntry::Index::Module ); + + return m_aDefOptions.getValue<bool>( rIdx ); +} + +void SvtCompatibilityOptions_Impl::Notify( const Sequence< OUString >& ) +{ + SAL_WARN( "unotools.config", "SvtCompatibilityOptions_Impl::Notify() Not implemented yet! I don't know how I can handle a dynamical list of unknown properties ..." ); +} + +void SvtCompatibilityOptions_Impl::ImplCommit() +{ + // Write all properties! + // Delete complete set first. + ClearNodeSet( SETNODE_ALLFILEFORMATS ); + + Sequence< PropertyValue > lPropertyValues( SvtCompatibilityEntry::getElementCount() - 1 ); + auto lPropertyValuesRange = asNonConstRange(lPropertyValues); + sal_uInt32 nNewCount = m_aOptions.size(); + for ( sal_uInt32 nItem = 0; nItem < nNewCount; ++nItem ) + { + SvtCompatibilityEntry aItem = m_aOptions[ nItem ]; + OUString sNode = SETNODE_ALLFILEFORMATS PATHDELIMITER + aItem.getValue<OUString>( SvtCompatibilityEntry::Index::Name ) + PATHDELIMITER; + + for ( int i = static_cast<int>(SvtCompatibilityEntry::Index::Module); i < static_cast<int>(SvtCompatibilityEntry::Index::INVALID); ++i ) + { + lPropertyValuesRange[ i - 1 ].Name = sNode + SvtCompatibilityEntry::getName( SvtCompatibilityEntry::Index(i) ); + lPropertyValuesRange[ i - 1 ].Value = aItem.getValue( SvtCompatibilityEntry::Index(i) ); + } + + SetSetProperties( SETNODE_ALLFILEFORMATS, lPropertyValues ); + } +} + +Sequence< OUString > SvtCompatibilityOptions_Impl::impl_GetPropertyNames( Sequence< OUString >& rItems ) +{ + // First get ALL names of current existing list items in configuration! + rItems = GetNodeNames( SETNODE_ALLFILEFORMATS ); + + // expand list to result list ... + Sequence< OUString > lProperties( rItems.getLength() * ( SvtCompatibilityEntry::getElementCount() - 1 ) ); + auto lPropertiesRange = asNonConstRange(lProperties); + + sal_Int32 nDestStep = 0; + // Copy entries to destination and expand every item with 2 supported sub properties. + for ( const auto& rItem : std::as_const(rItems) ) + { + OUString sFixPath = SETNODE_ALLFILEFORMATS PATHDELIMITER + rItem + PATHDELIMITER; + for ( int i = static_cast<int>(SvtCompatibilityEntry::Index::Module); i < static_cast<int>(SvtCompatibilityEntry::Index::INVALID); ++i ) + { + lPropertiesRange[ nDestStep ] = sFixPath + SvtCompatibilityEntry::getName( SvtCompatibilityEntry::Index(i) ); + nDestStep++; + } + } + + // Return result. + return lProperties; +} + +bool SvtCompatibilityOptions_Impl::GetOptionReadOnly(SvtCompatibilityEntry::Index rIdx) const +{ + /* Are not set Name and Module */ + assert(rIdx != SvtCompatibilityEntry::Index::Name && rIdx != SvtCompatibilityEntry::Index::Module); + + bool bReadOnly = false; + + sal_uInt32 nNewCount = m_aOptions.size(); + for (sal_uInt32 nItem = 0; nItem < nNewCount; ++nItem) + { + SvtCompatibilityEntry aItem = m_aOptions[nItem]; + if (aItem.getValue<OUString>(SvtCompatibilityEntry::Index::Name) == SvtCompatibilityEntry::USER_ENTRY_NAME) + { + bReadOnly = aItem.getPropertyReadOnly(rIdx); + break; + } + } + + return bReadOnly; +} + +bool SvtCompatibilityOptions_Impl::GetDefaultOptionReadOnly(SvtCompatibilityEntry::Index rIdx) const +{ + /* Are not set Name and Module */ + assert(rIdx != SvtCompatibilityEntry::Index::Name && rIdx != SvtCompatibilityEntry::Index::Module); + + return m_aDefOptions.getPropertyReadOnly(rIdx); +} + +bool SvtCompatibilityOptions_Impl::HaveDefaultReadOnlyOption() const +{ + return m_aDefOptions.haveReadOnlyProperty(); +} + +namespace +{ + std::weak_ptr<SvtCompatibilityOptions_Impl> theOptions; +} + +SvtCompatibilityOptions::SvtCompatibilityOptions() +{ + // Global access, must be guarded (multithreading!). + MutexGuard aGuard( GetOwnStaticMutex() ); + + m_pImpl = theOptions.lock(); + if ( !m_pImpl ) + { + m_pImpl = std::make_shared<SvtCompatibilityOptions_Impl>(); + theOptions = m_pImpl; + ItemHolder1::holdConfigItem( EItem::Compatibility ); + } +} + +SvtCompatibilityOptions::~SvtCompatibilityOptions() +{ + // Global access, must be guarded (multithreading!) + MutexGuard aGuard( GetOwnStaticMutex() ); + m_pImpl.reset(); +} + +void SvtCompatibilityOptions::AppendItem( const SvtCompatibilityEntry& aItem ) +{ + MutexGuard aGuard( GetOwnStaticMutex() ); + m_pImpl->AppendItem( aItem ); +} + +void SvtCompatibilityOptions::Clear() +{ + MutexGuard aGuard( GetOwnStaticMutex() ); + m_pImpl->Clear(); +} + +void SvtCompatibilityOptions::SetDefault( SvtCompatibilityEntry::Index rIdx, bool rValue ) +{ + MutexGuard aGuard( GetOwnStaticMutex() ); + m_pImpl->SetDefault( rIdx, rValue ); +} + +bool SvtCompatibilityOptions::GetDefault( SvtCompatibilityEntry::Index rIdx ) const +{ + MutexGuard aGuard( GetOwnStaticMutex() ); + return m_pImpl->GetDefault( rIdx ); +} + +std::vector< SvtCompatibilityEntry > SvtCompatibilityOptions::GetList() const +{ + MutexGuard aGuard( GetOwnStaticMutex() ); + + return m_pImpl->GetOptions(); +} + +bool SvtCompatibilityOptions::GetPropertyReadOnly( SvtCompatibilityEntry::Index rIdx ) const +{ + MutexGuard aGuard(GetOwnStaticMutex()); + + return m_pImpl->GetOptionReadOnly(rIdx); +} + +bool SvtCompatibilityOptions::GetDefaultPropertyReadOnly( SvtCompatibilityEntry::Index rIdx ) const +{ + MutexGuard aGuard(GetOwnStaticMutex()); + + return m_pImpl->GetDefaultOptionReadOnly(rIdx); +} + +bool SvtCompatibilityOptions::HaveDefaultReadOnlyProperty() const +{ + MutexGuard aGuard(GetOwnStaticMutex()); + + return m_pImpl->HaveDefaultReadOnlyOption(); +} + +Mutex& SvtCompatibilityOptions::GetOwnStaticMutex() +{ + static osl::Mutex aMutex; + return aMutex; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/config/configitem.cxx b/unotools/source/config/configitem.cxx new file mode 100644 index 0000000000..d0b6a04a69 --- /dev/null +++ b/unotools/source/config/configitem.cxx @@ -0,0 +1,1190 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <sal/log.hxx> +#include <unotools/configitem.hxx> +#include <unotools/configmgr.hxx> +#include <unotools/configpaths.hxx> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/util/XChangesListener.hpp> +#include <com/sun/star/util/XChangesNotifier.hpp> +#include <com/sun/star/container/XHierarchicalNameAccess.hpp> +#include <com/sun/star/configuration/XTemplateContainer.hpp> +#include <com/sun/star/container/XNameContainer.hpp> +#include <com/sun/star/lang/XSingleServiceFactory.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/util/XChangesBatch.hpp> +#include <o3tl/deleter.hxx> +#include <osl/diagnose.h> +#include <comphelper/sequence.hxx> +#include <comphelper/solarmutex.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <cppuhelper/implbase.hxx> +#include <utility> + +using namespace utl; +using namespace com::sun::star::uno; +using namespace com::sun::star::util; +using namespace com::sun::star::lang; +using namespace com::sun::star::beans; +using namespace com::sun::star::container; +using namespace com::sun::star::configuration; + +/* + The ConfigChangeListener_Impl receives notifications from the configuration about changes that + have happened. It forwards this notification to the ConfigItem it knows a pParent by calling its + "CallNotify" method. As ConfigItems are most probably not thread safe, the SolarMutex is acquired + before doing so. +*/ + +namespace utl{ + class ConfigChangeListener_Impl : public cppu::WeakImplHelper + < + css::util::XChangesListener + > + { + public: + ConfigItem* pParent; + const Sequence< OUString > aPropertyNames; + ConfigChangeListener_Impl(ConfigItem& rItem, const Sequence< OUString >& rNames); + + //XChangesListener + virtual void SAL_CALL changesOccurred( const ChangesEvent& Event ) override; + + //XEventListener + virtual void SAL_CALL disposing( const EventObject& Source ) override; + }; +} + +namespace { + +class ValueCounter_Impl +{ + sal_Int16& rCnt; +public: + explicit ValueCounter_Impl(sal_Int16& rCounter) + : rCnt(rCounter) + { + rCnt++; + } + ~ValueCounter_Impl() + { + OSL_ENSURE(rCnt>0, "RefCount < 0 ??"); + rCnt--; + } +}; + +} + +ConfigChangeListener_Impl::ConfigChangeListener_Impl( + ConfigItem& rItem, const Sequence< OUString >& rNames) : + pParent(&rItem), + aPropertyNames(rNames) +{ +} + +void ConfigChangeListener_Impl::changesOccurred( const ChangesEvent& rEvent ) +{ + Sequence<OUString> aChangedNames(rEvent.Changes.getLength()); + OUString* pNames = aChangedNames.getArray(); + + sal_Int32 nNotify = 0; + for(const auto& rElementChange : rEvent.Changes) + { + OUString sTemp; + rElementChange.Accessor >>= sTemp; + //true if the path is completely correct or if it is longer + //i.e ...Print/Content/Graphic and .../Print + bool bFound = std::any_of(aPropertyNames.begin(), aPropertyNames.end(), + [&sTemp](const OUString& rCheckPropertyName) { return isPrefixOfConfigurationPath(sTemp, rCheckPropertyName); }); + if(bFound) + pNames[nNotify++] = sTemp; + } + if( nNotify ) + { + ::comphelper::SolarMutex *pMutex = ::comphelper::SolarMutex::get(); + if ( pMutex ) + { + osl::Guard<comphelper::SolarMutex> aMutexGuard( pMutex ); + aChangedNames.realloc(nNotify); + pParent->CallNotify(aChangedNames); + } + } +} + +void ConfigChangeListener_Impl::disposing( const EventObject& /*rSource*/ ) +{ + pParent->RemoveChangesListener(); +} + +ConfigItem::ConfigItem(OUString aSubTree, ConfigItemMode nSetMode ) : + sSubTree(std::move(aSubTree)), + m_nMode(nSetMode), + m_bIsModified(false), + m_bEnableInternalNotification(false), + m_nInValueChange(0) +{ + if (utl::ConfigManager::IsFuzzing()) + return; + + if (nSetMode & ConfigItemMode::ReleaseTree) + ConfigManager::getConfigManager().addConfigItem(*this); + else + m_xHierarchyAccess = ConfigManager::getConfigManager().addConfigItem(*this); +} + +ConfigItem::~ConfigItem() +{ + suppress_fun_call_w_exception(RemoveChangesListener()); + ConfigManager::getConfigManager().removeConfigItem(*this); +} + +void ConfigItem::CallNotify( const css::uno::Sequence<OUString>& rPropertyNames ) +{ + // the call is forwarded to the virtual Notify() method + // it is pure virtual, so all classes deriving from ConfigItem have to decide how they + // want to notify listeners + if(m_nInValueChange <= 0 || m_bEnableInternalNotification) + Notify(rPropertyNames); +} + +void ConfigItem::impl_packLocalizedProperties( const Sequence< OUString >& lInNames , + const Sequence< Any >& lInValues , + Sequence< Any >& lOutValues ) +{ + // This method should be called for special AllLocales ConfigItem-mode only! + + sal_Int32 nSourceCounter; // used to step during input lists + sal_Int32 nSourceSize; // marks end of loop over input lists + sal_Int32 nDestinationCounter; // actual position in output lists + sal_Int32 nPropertyCounter; // counter of inner loop for Sequence< PropertyValue > + sal_Int32 nPropertiesSize; // marks end of inner loop + Sequence< OUString > lPropertyNames; // list of all locales for localized entry + Sequence< PropertyValue > lProperties; // localized values of a configuration entry packed for return + Reference< XInterface > xLocalizedNode; // if cfg entry is localized ... lInValues contains an XInterface! + + // Optimise follow algorithm ... A LITTLE BIT :-) + // There exist two different possibilities: + // i ) There exist no localized entries ... => size of lOutValues will be the same like lInNames/lInValues! + // ii) There exist some (mostly one or two) localized entries ... => size of lOutValues will be the same like lInNames/lInValues! + // ... Why? If a localized value exist - the any is filled with an XInterface object (is a SetNode-service). + // We read all his child nodes and pack it into Sequence< PropertyValue >. + // The result list we pack into the return any. We never change size of lists! + nSourceSize = lInNames.getLength(); + lOutValues.realloc( nSourceSize ); + auto plOutValues = lOutValues.getArray(); + + // Algorithm: + // Copy all names and values from in to out lists. + // Look for special localized entries ... You can detect it as "XInterface" packed into an Any. + // Use this XInterface-object to read all localized values and pack it into Sequence< PropertValue >. + // Add this list to out lists then. + + nDestinationCounter = 0; + for( nSourceCounter=0; nSourceCounter<nSourceSize; ++nSourceCounter ) + { + // If item a special localized one ... convert and pack it ... + if( lInValues[nSourceCounter].getValueTypeName() == "com.sun.star.uno.XInterface" ) + { + lInValues[nSourceCounter] >>= xLocalizedNode; + Reference< XNameContainer > xSetAccess( xLocalizedNode, UNO_QUERY ); + if( xSetAccess.is() ) + { + lPropertyNames = xSetAccess->getElementNames(); + nPropertiesSize = lPropertyNames.getLength(); + lProperties.realloc( nPropertiesSize ); + auto plProperties = lProperties.getArray(); + + for( nPropertyCounter=0; nPropertyCounter<nPropertiesSize; ++nPropertyCounter ) + { + plProperties[nPropertyCounter].Name = lPropertyNames[nPropertyCounter]; + OUString sLocaleValue; + xSetAccess->getByName( lPropertyNames[nPropertyCounter] ) >>= sLocaleValue; + plProperties[nPropertyCounter].Value <<= sLocaleValue; + } + + plOutValues[nDestinationCounter] <<= lProperties; + } + } + // ... or copy normal items to return lists directly. + else + { + plOutValues[nDestinationCounter] = lInValues[nSourceCounter]; + } + ++nDestinationCounter; + } +} + +void ConfigItem::impl_unpackLocalizedProperties( const Sequence< OUString >& lInNames , + const Sequence< Any >& lInValues , + Sequence< OUString >& lOutNames , + Sequence< Any >& lOutValues) +{ + // This method should be called for special AllLocales ConfigItem-mode only! + + sal_Int32 nSourceSize; // marks end of loop over input lists + sal_Int32 nDestinationCounter; // actual position in output lists + sal_Int32 nPropertiesSize; // marks end of inner loop + OUString sNodeName; // base name of node ( e.g. "UIName/" ) ... expand to locale ( e.g. "UIName/de" ) + Sequence< PropertyValue > lProperties; // localized values of a configuration entry gotten from lInValues-Any + + // Optimise follow algorithm ... A LITTLE BIT :-) + // There exist two different possibilities: + // i ) There exist no localized entries ... => size of lOutNames/lOutValues will be the same like lInNames/lInValues! + // ii) There exist some (mostly one or two) localized entries ... => size of lOutNames/lOutValues will be some bytes greater then lInNames/lInValues. + // => I think we should make it fast for i). ii) is a special case and mustn't be SOOOO... fast. + // We should reserve same space for output list like input ones first. + // Follow algorithm looks for these borders and change it for ii) only! + // It will be faster then a "realloc()" call in every loop ... + nSourceSize = lInNames.getLength(); + + lOutNames.realloc ( nSourceSize ); + auto plOutNames = lOutNames.getArray(); + lOutValues.realloc ( nSourceSize ); + auto plOutValues = lOutValues.getArray(); + + // Algorithm: + // Copy all names and values from const to return lists. + // Look for special localized entries ... You can detect it as Sequence< PropertyValue > packed into an Any. + // Split it ... insert PropertyValue.Name to lOutNames and PropertyValue.Value to lOutValues. + + nDestinationCounter = 0; + for( sal_Int32 nSourceCounter=0; nSourceCounter<nSourceSize; ++nSourceCounter ) + { + // If item a special localized one ... split it and insert his parts to output lists ... + if( lInValues[nSourceCounter].getValueType() == cppu::UnoType<Sequence<PropertyValue>>::get() ) + { + lInValues[nSourceCounter] >>= lProperties; + nPropertiesSize = lProperties.getLength(); + + sNodeName = lInNames[nSourceCounter] + "/"; + + if( (nDestinationCounter+nPropertiesSize) > lOutNames.getLength() ) + { + lOutNames.realloc ( nDestinationCounter+nPropertiesSize ); + plOutNames = lOutNames.getArray(); + lOutValues.realloc ( nDestinationCounter+nPropertiesSize ); + plOutValues = lOutValues.getArray(); + } + + for( const auto& rProperty : std::as_const(lProperties) ) + { + plOutNames [nDestinationCounter] = sNodeName + rProperty.Name; + plOutValues[nDestinationCounter] = rProperty.Value; + ++nDestinationCounter; + } + } + // ... or copy normal items to return lists directly. + else + { + if( (nDestinationCounter+1) > lOutNames.getLength() ) + { + lOutNames.realloc ( nDestinationCounter+1 ); + plOutNames = lOutNames.getArray(); + lOutValues.realloc ( nDestinationCounter+1 ); + plOutValues = lOutValues.getArray(); + } + + plOutNames [nDestinationCounter] = lInNames [nSourceCounter]; + plOutValues[nDestinationCounter] = lInValues[nSourceCounter]; + ++nDestinationCounter; + } + } +} + +Sequence< sal_Bool > ConfigItem::GetReadOnlyStates(const css::uno::Sequence< OUString >& rNames) +{ + sal_Int32 i; + + // size of return list is fix! + // Every item must match to length of incoming name list. + sal_Int32 nCount = rNames.getLength(); + Sequence< sal_Bool > lStates(nCount); + sal_Bool* plStates = lStates.getArray(); + + // We must be sure to return a valid information every time! + // Set default to non readonly... similar to the configuration handling of this property. + std::fill_n(plStates, lStates.getLength(), false); + + // no access - no information... + Reference< XHierarchicalNameAccess > xHierarchyAccess = GetTree(); + if (!xHierarchyAccess.is()) + return lStates; + + for (i=0; i<nCount; ++i) + { + try + { + OUString sName = rNames[i]; + OUString sPath; + OUString sProperty; + + (void)::utl::splitLastFromConfigurationPath(sName,sPath,sProperty); + if (sPath.isEmpty() && sProperty.isEmpty()) + { + OSL_FAIL("ConfigItem::IsReadonly() split failed"); + continue; + } + + Reference< XInterface > xNode; + Reference< XPropertySet > xSet; + Reference< XPropertySetInfo > xInfo; + if (!sPath.isEmpty()) + { + Any aNode = xHierarchyAccess->getByHierarchicalName(sPath); + if (!(aNode >>= xNode) || !xNode.is()) + { + OSL_FAIL("ConfigItem::IsReadonly() no set available"); + continue; + } + } + else + { + xNode = xHierarchyAccess; + } + + xSet.set(xNode, UNO_QUERY); + if (xSet.is()) + { + xInfo = xSet->getPropertySetInfo(); + OSL_ENSURE(xInfo.is(), "ConfigItem::IsReadonly() getPropertySetInfo failed ..."); + } + else + { + xInfo.set(xNode, UNO_QUERY); + OSL_ENSURE(xInfo.is(), "ConfigItem::IsReadonly() UNO_QUERY failed ..."); + } + + if (!xInfo.is()) + { + OSL_FAIL("ConfigItem::IsReadonly() no prop info available"); + continue; + } + + Property aProp = xInfo->getPropertyByName(sProperty); + plStates[i] = (aProp.Attributes & PropertyAttribute::READONLY) == PropertyAttribute::READONLY; + } + catch (const Exception&) + { + } + } + + return lStates; +} + +Sequence< Any > ConfigItem::GetProperties(const Sequence< OUString >& rNames) +{ + Reference<XHierarchicalNameAccess> xHierarchyAccess = GetTree(); + if(xHierarchyAccess.is()) + return GetProperties(xHierarchyAccess, rNames, + (m_nMode & ConfigItemMode::AllLocales ) == ConfigItemMode::AllLocales); + return Sequence< Any >(rNames.getLength()); +} + +Sequence< Any > ConfigItem::GetProperties( + css::uno::Reference<css::container::XHierarchicalNameAccess> const & xHierarchyAccess, + const Sequence< OUString >& rNames, + bool bAllLocales) +{ + Sequence< Any > aRet(rNames.getLength()); + const OUString* pNames = rNames.getConstArray(); + Any* pRet = aRet.getArray(); + for(int i = 0; i < rNames.getLength(); i++) + { + try + { + pRet[i] = xHierarchyAccess->getByHierarchicalName(pNames[i]); + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( + "unotools.config", + "ignoring XHierarchicalNameAccess " << pNames[i]); + } + } + + // In special mode "ALL_LOCALES" we must convert localized values to Sequence< PropertyValue >. + if(bAllLocales) + { + Sequence< Any > lValues; + impl_packLocalizedProperties( rNames, aRet, lValues ); + aRet = lValues; + } + return aRet; +} + +bool ConfigItem::PutProperties( const Sequence< OUString >& rNames, + const Sequence< Any>& rValues) +{ + ValueCounter_Impl aCounter(m_nInValueChange); + Reference<XHierarchicalNameAccess> xHierarchyAccess = GetTree(); + Reference<XNameReplace> xTopNodeReplace(xHierarchyAccess, UNO_QUERY); + bool bRet = xHierarchyAccess.is() && xTopNodeReplace.is(); + if(bRet) + { + Sequence< OUString > lNames; + Sequence< Any > lValues; + const OUString* pNames = nullptr; + const Any* pValues = nullptr; + sal_Int32 nNameCount; + if(( m_nMode & ConfigItemMode::AllLocales ) == ConfigItemMode::AllLocales ) + { + // If ConfigItem works in "ALL_LOCALES"-mode ... we must support a Sequence< PropertyValue > + // as value of a localized configuration entry! + // How we can do that? + // We must split all PropertyValues to "Sequence< OUString >" AND "Sequence< Any >"! + impl_unpackLocalizedProperties( rNames, rValues, lNames, lValues ); + pNames = lNames.getConstArray (); + pValues = lValues.getConstArray (); + nNameCount = lNames.getLength (); + } + else + { + // This is the normal mode ... + // Use given input lists directly. + pNames = rNames.getConstArray (); + pValues = rValues.getConstArray (); + nNameCount = rNames.getLength (); + } + for(int i = 0; i < nNameCount; i++) + { + try + { + OUString sNode, sProperty; + if (splitLastFromConfigurationPath(pNames[i],sNode, sProperty)) + { + Any aNode = xHierarchyAccess->getByHierarchicalName(sNode); + + Reference<XNameAccess> xNodeAcc; + aNode >>= xNodeAcc; + Reference<XNameReplace> xNodeReplace(xNodeAcc, UNO_QUERY); + Reference<XNameContainer> xNodeCont (xNodeAcc, UNO_QUERY); + + bool bExist = (xNodeAcc.is() && xNodeAcc->hasByName(sProperty)); + if (bExist && xNodeReplace.is()) + xNodeReplace->replaceByName(sProperty, pValues[i]); + else + if (!bExist && xNodeCont.is()) + xNodeCont->insertByName(sProperty, pValues[i]); + else + bRet = false; + } + else //direct value + { + xTopNodeReplace->replaceByName(sProperty, pValues[i]); + } + } + catch (css::uno::Exception &) + { + TOOLS_WARN_EXCEPTION("unotools.config", "Exception from PutProperties"); + } + } + try + { + Reference<XChangesBatch> xBatch(xHierarchyAccess, UNO_QUERY); + xBatch->commitChanges(); + } + catch (css::uno::Exception &) + { + TOOLS_WARN_EXCEPTION("unotools.config", "Exception from commitChanges"); + } + } + + return bRet; +} + +bool ConfigItem::PutProperties( + css::uno::Reference<css::container::XHierarchicalNameAccess> const & xHierarchyAccess, + const Sequence< OUString >& rNames, + const Sequence< Any>& rValues, + bool bAllLocales) +{ + Reference<XNameReplace> xTopNodeReplace(xHierarchyAccess, UNO_QUERY); + bool bRet = xTopNodeReplace.is(); + if(bRet) + { + Sequence< OUString > lNames; + Sequence< Any > lValues; + const OUString* pNames = nullptr; + const Any* pValues = nullptr; + sal_Int32 nNameCount; + if(bAllLocales) + { + // If ConfigItem works in "ALL_LOCALES"-mode ... we must support a Sequence< PropertyValue > + // as value of a localized configuration entry! + // How we can do that? + // We must split all PropertyValues to "Sequence< OUString >" AND "Sequence< Any >"! + impl_unpackLocalizedProperties( rNames, rValues, lNames, lValues ); + pNames = lNames.getConstArray (); + pValues = lValues.getConstArray (); + nNameCount = lNames.getLength (); + } + else + { + // This is the normal mode ... + // Use given input lists directly. + pNames = rNames.getConstArray (); + pValues = rValues.getConstArray (); + nNameCount = rNames.getLength (); + } + for(int i = 0; i < nNameCount; i++) + { + try + { + OUString sNode, sProperty; + if (splitLastFromConfigurationPath(pNames[i],sNode, sProperty)) + { + Any aNode = xHierarchyAccess->getByHierarchicalName(sNode); + + Reference<XNameAccess> xNodeAcc; + aNode >>= xNodeAcc; + Reference<XNameReplace> xNodeReplace(xNodeAcc, UNO_QUERY); + Reference<XNameContainer> xNodeCont (xNodeAcc, UNO_QUERY); + + bool bExist = (xNodeAcc.is() && xNodeAcc->hasByName(sProperty)); + if (bExist && xNodeReplace.is()) + xNodeReplace->replaceByName(sProperty, pValues[i]); + else + if (!bExist && xNodeCont.is()) + xNodeCont->insertByName(sProperty, pValues[i]); + else + bRet = false; + } + else //direct value + { + xTopNodeReplace->replaceByName(sProperty, pValues[i]); + } + } + catch (css::uno::Exception &) + { + TOOLS_WARN_EXCEPTION("unotools.config", "Exception from PutProperties"); + } + } + try + { + Reference<XChangesBatch> xBatch(xHierarchyAccess, UNO_QUERY); + xBatch->commitChanges(); + } + catch (css::uno::Exception &) + { + TOOLS_WARN_EXCEPTION("unotools.config", "Exception from commitChanges"); + } + } + + return bRet; +} + +void ConfigItem::DisableNotification() +{ + OSL_ENSURE( xChangeLstnr.is(), "ConfigItem::DisableNotification: notifications not enabled currently!" ); + RemoveChangesListener(); +} + +bool ConfigItem::EnableNotification(const Sequence< OUString >& rNames, + bool bEnableInternalNotification ) +{ + OSL_ENSURE(!(m_nMode & ConfigItemMode::ReleaseTree), "notification in ConfigItemMode::ReleaseTree mode not possible"); + m_bEnableInternalNotification = bEnableInternalNotification; + Reference<XHierarchicalNameAccess> xHierarchyAccess = GetTree(); + Reference<XChangesNotifier> xChgNot(xHierarchyAccess, UNO_QUERY); + if(!xChgNot.is()) + return false; + + OSL_ENSURE(!xChangeLstnr.is(), "EnableNotification already called"); + if(xChangeLstnr.is()) + xChgNot->removeChangesListener( xChangeLstnr ); + bool bRet = true; + + try + { + xChangeLstnr = new ConfigChangeListener_Impl(*this, rNames); + xChgNot->addChangesListener( xChangeLstnr ); + } + catch (const RuntimeException&) + { + bRet = false; + } + return bRet; +} + +void ConfigItem::RemoveChangesListener() +{ + Reference<XHierarchicalNameAccess> xHierarchyAccess = GetTree(); + if(!xHierarchyAccess.is()) + return; + + Reference<XChangesNotifier> xChgNot(xHierarchyAccess, UNO_QUERY); + if(xChgNot.is() && xChangeLstnr.is()) + { + try + { + xChgNot->removeChangesListener( xChangeLstnr ); + xChangeLstnr = nullptr; + } + catch (const Exception&) + { + } + } +} + +static void lcl_normalizeLocalNames(Sequence< OUString >& _rNames, ConfigNameFormat _eFormat, Reference<XInterface> const& _xParentNode) +{ + switch (_eFormat) + { + case ConfigNameFormat::LocalNode: + // unaltered - this is our input format + break; + + case ConfigNameFormat::LocalPath: + { + Reference<XTemplateContainer> xTypeContainer(_xParentNode, UNO_QUERY); + if (xTypeContainer.is()) + { + OUString sTypeName = xTypeContainer->getElementTemplateName(); + sTypeName = sTypeName.copy(sTypeName.lastIndexOf('/')+1); + + std::transform(std::cbegin(_rNames), std::cend(_rNames), _rNames.getArray(), + [&sTypeName](const OUString& rName) -> OUString { return wrapConfigurationElementName(rName,sTypeName); }); + } + else + { + Reference<XServiceInfo> xSVI(_xParentNode, UNO_QUERY); + if (xSVI.is() && xSVI->supportsService("com.sun.star.configuration.SetAccess")) + { + std::transform(std::cbegin(_rNames), std::cend(_rNames), _rNames.getArray(), + [](const OUString& rName) -> OUString { return wrapConfigurationElementName(rName); }); + } + } + } + break; + + } +} + +Sequence< OUString > ConfigItem::GetNodeNames(const OUString& rNode) +{ + ConfigNameFormat const eDefaultFormat = ConfigNameFormat::LocalNode; // CONFIG_NAME_DEFAULT; + + return GetNodeNames(rNode, eDefaultFormat); +} + +Sequence< OUString > ConfigItem::GetNodeNames(const OUString& rNode, ConfigNameFormat eFormat) +{ + Reference<XHierarchicalNameAccess> xHierarchyAccess = GetTree(); + if(xHierarchyAccess.is()) + return GetNodeNames(xHierarchyAccess, rNode, eFormat); + return Sequence< OUString >(); +} + +Sequence< OUString > ConfigItem::GetNodeNames( + css::uno::Reference<css::container::XHierarchicalNameAccess> const & xHierarchyAccess, + const OUString& rNode, + ConfigNameFormat eFormat) +{ + Sequence< OUString > aRet; + try + { + Reference<XNameAccess> xCont; + if(!rNode.isEmpty()) + { + Any aNode = xHierarchyAccess->getByHierarchicalName(rNode); + aNode >>= xCont; + } + else + xCont.set(xHierarchyAccess, UNO_QUERY); + if(xCont.is()) + { + aRet = xCont->getElementNames(); + lcl_normalizeLocalNames(aRet,eFormat,xCont); + } + + } + catch (css::uno::Exception &) + { + TOOLS_WARN_EXCEPTION("unotools.config", "Exception from GetNodeNames"); + } + return aRet; +} + +bool ConfigItem::ClearNodeSet(const OUString& rNode) +{ + ValueCounter_Impl aCounter(m_nInValueChange); + bool bRet = false; + Reference<XHierarchicalNameAccess> xHierarchyAccess = GetTree(); + if(xHierarchyAccess.is()) + bRet = ClearNodeSet(xHierarchyAccess, rNode); + return bRet; +} + +bool ConfigItem::ClearNodeSet( + css::uno::Reference<css::container::XHierarchicalNameAccess> const & xHierarchyAccess, + const OUString& rNode) +{ + bool bRet = false; + try + { + Reference<XNameContainer> xCont; + if(!rNode.isEmpty()) + { + Any aNode = xHierarchyAccess->getByHierarchicalName(rNode); + aNode >>= xCont; + } + else + xCont.set(xHierarchyAccess, UNO_QUERY); + if(!xCont.is()) + return false; + const Sequence< OUString > aNames = xCont->getElementNames(); + Reference<XChangesBatch> xBatch(xHierarchyAccess, UNO_QUERY); + for(const OUString& rName : aNames) + { + try + { + xCont->removeByName(rName); + } + catch (css::uno::Exception &) + { + TOOLS_WARN_EXCEPTION("unotools.config", "Exception from removeByName"); + } + } + xBatch->commitChanges(); + bRet = true; + } + catch (css::uno::Exception &) + { + TOOLS_WARN_EXCEPTION("unotools.config", "Exception from ClearNodeSet"); + } + return bRet; +} + +bool ConfigItem::ClearNodeElements(const OUString& rNode, Sequence< OUString > const & rElements) +{ + ValueCounter_Impl aCounter(m_nInValueChange); + bool bRet = false; + Reference<XHierarchicalNameAccess> xHierarchyAccess = GetTree(); + if(xHierarchyAccess.is()) + { + try + { + Reference<XNameContainer> xCont; + if(!rNode.isEmpty()) + { + Any aNode = xHierarchyAccess->getByHierarchicalName(rNode); + aNode >>= xCont; + } + else + xCont.set(xHierarchyAccess, UNO_QUERY); + if(!xCont.is()) + return false; + try + { + for(const OUString& rElement : rElements) + { + xCont->removeByName(rElement); + } + Reference<XChangesBatch> xBatch(xHierarchyAccess, UNO_QUERY); + xBatch->commitChanges(); + } + catch (css::uno::Exception &) + { + TOOLS_WARN_EXCEPTION("unotools.config", "Exception from commitChanges()"); + } + bRet = true; + } + catch (css::uno::Exception &) + { + TOOLS_WARN_EXCEPTION("unotools.config", "Exception from GetNodeNames()"); + } + } + return bRet; +} + +static OUString lcl_extractSetPropertyName( const OUString& rInPath, std::u16string_view rPrefix ) +{ + OUString const sSubPath = dropPrefixFromConfigurationPath( rInPath, rPrefix); + return extractFirstFromConfigurationPath( sSubPath ); +} + +static +Sequence< OUString > lcl_extractSetPropertyNames( const Sequence< PropertyValue >& rValues, std::u16string_view rPrefix ) +{ + Sequence< OUString > aSubNodeNames(rValues.getLength()); + OUString* pSubNodeNames = aSubNodeNames.getArray(); + + OUString sLastSubNode; + sal_Int32 nSubIndex = 0; + + for(const PropertyValue& rProperty : rValues) + { + OUString const sSubPath = dropPrefixFromConfigurationPath( rProperty.Name, rPrefix); + OUString const sSubNode = extractFirstFromConfigurationPath( sSubPath ); + + if(sLastSubNode != sSubNode) + { + pSubNodeNames[nSubIndex++] = sSubNode; + } + + sLastSubNode = sSubNode; + } + aSubNodeNames.realloc(nSubIndex); + + return aSubNodeNames; +} + +// Add or change properties +bool ConfigItem::SetSetProperties( + const OUString& rNode, const Sequence< PropertyValue >& rValues) +{ + ValueCounter_Impl aCounter(m_nInValueChange); + Reference<XHierarchicalNameAccess> xHierarchyAccess = GetTree(); + if(!xHierarchyAccess.is()) + return true; + return SetSetProperties(xHierarchyAccess, rNode, rValues); +} + +// Add or change properties +bool ConfigItem::SetSetProperties( + css::uno::Reference<css::container::XHierarchicalNameAccess> const & xHierarchyAccess, + const OUString& rNode, const Sequence< PropertyValue >& rValues) +{ + bool bRet = true; + Reference<XChangesBatch> xBatch(xHierarchyAccess, UNO_QUERY); + try + { + Reference<XNameContainer> xCont; + if(!rNode.isEmpty()) + { + Any aNode = xHierarchyAccess->getByHierarchicalName(rNode); + aNode >>= xCont; + } + else + xCont.set(xHierarchyAccess, UNO_QUERY); + if(!xCont.is()) + return false; + + Reference<XSingleServiceFactory> xFac(xCont, UNO_QUERY); + + if(xFac.is()) + { + const Sequence< OUString > aSubNodeNames = lcl_extractSetPropertyNames(rValues, rNode); + + for(const auto& rSubNodeName : aSubNodeNames) + { + if(!xCont->hasByName(rSubNodeName)) + { + Reference<XInterface> xInst = xFac->createInstance(); + Any aVal; aVal <<= xInst; + xCont->insertByName(rSubNodeName, aVal); + } + //set values + } + try + { + xBatch->commitChanges(); + } + catch (css::uno::Exception &) + { + TOOLS_WARN_EXCEPTION("unotools.config", "Exception from commitChanges()"); + } + + const PropertyValue* pProperties = rValues.getConstArray(); + + Sequence< OUString > aSetNames(rValues.getLength()); + OUString* pSetNames = aSetNames.getArray(); + + Sequence< Any> aSetValues(rValues.getLength()); + Any* pSetValues = aSetValues.getArray(); + + bool bEmptyNode = rNode.isEmpty(); + for(sal_Int32 k = 0; k < rValues.getLength(); k++) + { + pSetNames[k] = pProperties[k].Name.copy( bEmptyNode ? 1 : 0); + pSetValues[k] = pProperties[k].Value; + } + bRet = PutProperties(xHierarchyAccess, aSetNames, aSetValues, /*bAllLocales*/false); + } + else + { + //if no factory is available then the node contains basic data elements + for(const PropertyValue& rValue : rValues) + { + try + { + OUString sSubNode = lcl_extractSetPropertyName( rValue.Name, rNode ); + + if(xCont->hasByName(sSubNode)) + xCont->replaceByName(sSubNode, rValue.Value); + else + xCont->insertByName(sSubNode, rValue.Value); + + OSL_ENSURE( xHierarchyAccess->hasByHierarchicalName(rValue.Name), + "Invalid config path" ); + } + catch (css::uno::Exception &) + { + TOOLS_WARN_EXCEPTION("unotools.config", "Exception from insert/replaceByName()"); + } + } + xBatch->commitChanges(); + } + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION("unotools.config", "Exception from SetSetProperties"); + bRet = false; + } + return bRet; +} + +bool ConfigItem::ReplaceSetProperties( + const OUString& rNode, const Sequence< PropertyValue >& rValues) +{ + ValueCounter_Impl aCounter(m_nInValueChange); + bool bRet = true; + Reference<XHierarchicalNameAccess> xHierarchyAccess = GetTree(); + if(xHierarchyAccess.is()) + bRet = ReplaceSetProperties(xHierarchyAccess, rNode, rValues, + ( m_nMode & ConfigItemMode::AllLocales ) == ConfigItemMode::AllLocales); + return bRet; +} + +bool ConfigItem::ReplaceSetProperties( + css::uno::Reference<css::container::XHierarchicalNameAccess> const & xHierarchyAccess, + const OUString& rNode, + const Sequence< PropertyValue >& rValues, + bool bAllLocales) +{ + bool bRet = true; + Reference<XChangesBatch> xBatch(xHierarchyAccess, UNO_QUERY); + try + { + Reference<XNameContainer> xCont; + if(!rNode.isEmpty()) + { + Any aNode = xHierarchyAccess->getByHierarchicalName(rNode); + aNode >>= xCont; + } + else + xCont.set(xHierarchyAccess, UNO_QUERY); + if(!xCont.is()) + return false; + + // JB: Change: now the same name handling for sets of simple values + const Sequence< OUString > aSubNodeNames = lcl_extractSetPropertyNames(rValues, rNode); + + Reference<XSingleServiceFactory> xFac(xCont, UNO_QUERY); + const bool isSimpleValueSet = !xFac.is(); + + //remove unknown members first + { + const Sequence<OUString> aContainerSubNodes = xCont->getElementNames(); + + for(const OUString& rContainerSubNode : aContainerSubNodes) + { + bool bFound = comphelper::findValue(aSubNodeNames, rContainerSubNode) != -1; + if(!bFound) + try + { + xCont->removeByName(rContainerSubNode); + } + catch (const Exception&) + { + if (isSimpleValueSet) + { + try + { + // #i37322#: fallback action: replace with <void/> + xCont->replaceByName(rContainerSubNode, Any()); + // fallback successful: continue looping + continue; + } + catch (Exception &) + {} // propagate original exception, if fallback fails + } + throw; + } + } + try { xBatch->commitChanges(); } + catch (css::uno::Exception &) + { + TOOLS_WARN_EXCEPTION("unotools.config", "Exception from commitChanges"); + } + } + + if(xFac.is()) // !isSimpleValueSet + { + for(const OUString& rSubNodeName : aSubNodeNames) + { + if(!xCont->hasByName(rSubNodeName)) + { + //create if not available + Reference<XInterface> xInst = xFac->createInstance(); + Any aVal; aVal <<= xInst; + xCont->insertByName(rSubNodeName, aVal); + } + } + try { xBatch->commitChanges(); } + catch (css::uno::Exception &) + { + TOOLS_WARN_EXCEPTION("unotools.config", "Exception from commitChanges"); + } + + const PropertyValue* pProperties = rValues.getConstArray(); + + Sequence< OUString > aSetNames(rValues.getLength()); + OUString* pSetNames = aSetNames.getArray(); + + Sequence< Any> aSetValues(rValues.getLength()); + Any* pSetValues = aSetValues.getArray(); + + bool bEmptyNode = rNode.isEmpty(); + for(sal_Int32 k = 0; k < rValues.getLength(); k++) + { + pSetNames[k] = pProperties[k].Name.copy( bEmptyNode ? 1 : 0); + pSetValues[k] = pProperties[k].Value; + } + bRet = PutProperties(xHierarchyAccess, aSetNames, aSetValues, bAllLocales); + } + else + { + //if no factory is available then the node contains basic data elements + for(const PropertyValue& rValue : rValues) + { + try + { + OUString sSubNode = lcl_extractSetPropertyName( rValue.Name, rNode ); + + if(xCont->hasByName(sSubNode)) + xCont->replaceByName(sSubNode, rValue.Value); + else + xCont->insertByName(sSubNode, rValue.Value); + } + catch (css::uno::Exception &) + { + TOOLS_WARN_EXCEPTION("unotools.config", "Exception from insert/replaceByName"); + } + } + xBatch->commitChanges(); + } + } + catch (const Exception& ) + { + TOOLS_WARN_EXCEPTION("unotools.config", "Exception from ReplaceSetProperties"); + bRet = false; + } + return bRet; +} + +bool ConfigItem::AddNode(const OUString& rNode, const OUString& rNewNode) +{ + ValueCounter_Impl aCounter(m_nInValueChange); + bool bRet = true; + Reference<XHierarchicalNameAccess> xHierarchyAccess = GetTree(); + if(xHierarchyAccess.is()) + { + Reference<XChangesBatch> xBatch(xHierarchyAccess, UNO_QUERY); + try + { + Reference<XNameContainer> xCont; + if(!rNode.isEmpty()) + { + Any aNode = xHierarchyAccess->getByHierarchicalName(rNode); + aNode >>= xCont; + } + else + xCont.set(xHierarchyAccess, UNO_QUERY); + if(!xCont.is()) + return false; + + Reference<XSingleServiceFactory> xFac(xCont, UNO_QUERY); + + if(xFac.is()) + { + if(!xCont->hasByName(rNewNode)) + { + Reference<XInterface> xInst = xFac->createInstance(); + Any aVal; aVal <<= xInst; + xCont->insertByName(rNewNode, aVal); + } + try + { + xBatch->commitChanges(); + } + catch (css::uno::Exception &) + { + TOOLS_WARN_EXCEPTION("unotools.config", "Exception from commitChanges"); + } + } + else + { + //if no factory is available then the node contains basic data elements + try + { + if(!xCont->hasByName(rNewNode)) + xCont->insertByName(rNewNode, Any()); + } + catch (css::uno::Exception &) + { + TOOLS_WARN_EXCEPTION("unotools.config", "Exception from AddNode"); + } + } + xBatch->commitChanges(); + } + catch (const Exception&) + { + DBG_UNHANDLED_EXCEPTION("unotools.config"); + bRet = false; + } + } + return bRet; +} + + +void ConfigItem::SetModified() +{ + m_bIsModified = true; +} + +void ConfigItem::ClearModified() +{ + m_bIsModified = false; +} + +Reference< XHierarchicalNameAccess> ConfigItem::GetTree() +{ + Reference< XHierarchicalNameAccess> xRet; + if (utl::ConfigManager::IsFuzzing()) + return xRet; + if(!m_xHierarchyAccess.is()) + xRet = ConfigManager::acquireTree(*this); + else + xRet = m_xHierarchyAccess; + return xRet; +} + +void ConfigItem::Commit() +{ + ImplCommit(); + ClearModified(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/config/configmgr.cxx b/unotools/source/config/configmgr.cxx new file mode 100644 index 0000000000..e19c7c694a --- /dev/null +++ b/unotools/source/config/configmgr.cxx @@ -0,0 +1,193 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <com/sun/star/beans/NamedValue.hpp> +#include <com/sun/star/container/XHierarchicalNameAccess.hpp> +#include <com/sun/star/configuration/theDefaultProvider.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/uno/Sequence.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <officecfg/Setup.hxx> +#include <rtl/ustring.hxx> +#include <sal/log.hxx> +#include <unotools/configitem.hxx> +#include <unotools/configmgr.hxx> +#include <comphelper/processfactory.hxx> + +namespace { + +class RegisterConfigItemHelper { +public: + RegisterConfigItemHelper( + utl::ConfigManager & manager, utl::ConfigItem & item): + manager_(manager), item_(&item) + { + manager.registerConfigItem(item_); + } + + ~RegisterConfigItemHelper() { + if (item_ != nullptr) { + manager_.removeConfigItem(*item_); + } + } + + void keep() { item_ = nullptr; } + +private: + utl::ConfigManager & manager_; + utl::ConfigItem * item_; + + RegisterConfigItemHelper(const RegisterConfigItemHelper&) = delete; + RegisterConfigItemHelper& operator=(const RegisterConfigItemHelper&) = delete; +}; + +css::uno::Reference< css::lang::XMultiServiceFactory > +getConfigurationProvider() { + return css::configuration::theDefaultProvider::get( comphelper::getProcessComponentContext() ); +} + +} + +OUString utl::ConfigManager::getAboutBoxProductVersion() { + return officecfg::Setup::Product::ooSetupVersionAboutBox::get(); +} + +OUString utl::ConfigManager::getAboutBoxProductVersionSuffix() { + return officecfg::Setup::Product::ooSetupVersionAboutBoxSuffix::get(); +} + +OUString utl::ConfigManager::getDefaultCurrency() { + return officecfg::Setup::L10N::ooSetupCurrency::get(); +} + +OUString utl::ConfigManager::getUILocale() { + return officecfg::Setup::L10N::ooLocale::get(); +} + +OUString utl::ConfigManager::getWorkLocale() { + return officecfg::Setup::L10N::ooSetupSystemLocale::get(); +} + +OUString utl::ConfigManager::getProductExtension() { + return officecfg::Setup::Product::ooSetupExtension::get(); +} + +OUString utl::ConfigManager::getProductName() { + return officecfg::Setup::Product::ooName::get(); +} + +OUString utl::ConfigManager::getProductVersion() { + return officecfg::Setup::Product::ooSetupVersion::get(); +} + +OUString utl::ConfigManager::getVendor() { + return officecfg::Setup::Product::ooVendor::get(); +} + +void utl::ConfigManager::storeConfigItems() { + getConfigManager().doStoreConfigItems(); +} + +utl::ConfigManager & utl::ConfigManager::getConfigManager() { + static utl::ConfigManager theConfigManager; + return theConfigManager; +} + +css::uno::Reference< css::container::XHierarchicalNameAccess > +utl::ConfigManager::acquireTree(utl::ConfigItem const & item) { + css::uno::Sequence< css::uno::Any > args{ css::uno::Any(css::beans::NamedValue( + "nodepath", + css::uno::Any("/org.openoffice." + item.GetSubTreeName()))) }; + if (item.GetMode() & ConfigItemMode::AllLocales) { + args.realloc(2); + args.getArray()[1] <<= css::beans::NamedValue("locale", css::uno::Any(OUString("*"))); + } + return css::uno::Reference< css::container::XHierarchicalNameAccess >( + getConfigurationProvider()->createInstanceWithArguments( + "com.sun.star.configuration.ConfigurationUpdateAccess", + args), + css::uno::UNO_QUERY_THROW); +} + +css::uno::Reference< css::container::XHierarchicalNameAccess > +utl::ConfigManager::acquireTree(std::u16string_view rSubTreeName) { + css::uno::Sequence< css::uno::Any > args{ css::uno::Any(css::beans::NamedValue( + "nodepath", + css::uno::Any(OUString::Concat(u"/org.openoffice.") + rSubTreeName))) }; + return css::uno::Reference< css::container::XHierarchicalNameAccess >( + getConfigurationProvider()->createInstanceWithArguments( + "com.sun.star.configuration.ConfigurationUpdateAccess", + args), + css::uno::UNO_QUERY_THROW); +} + +utl::ConfigManager::ConfigManager() {} + +utl::ConfigManager::~ConfigManager() { + SAL_WARN_IF(!items_.empty(), "unotools.config", "ConfigManager not empty"); +} + +css::uno::Reference< css::container::XHierarchicalNameAccess > +utl::ConfigManager::addConfigItem(utl::ConfigItem & item) { + RegisterConfigItemHelper reg(*this, item); + css::uno::Reference< css::container::XHierarchicalNameAccess > tree( + acquireTree(item)); + reg.keep(); + return tree; +} + +void utl::ConfigManager::removeConfigItem(utl::ConfigItem & item) { + std::erase(items_, &item); +} + +void utl::ConfigManager::registerConfigItem(utl::ConfigItem * item) { + assert(item != nullptr); + items_.push_back(item); +} + +void utl::ConfigManager::doStoreConfigItems() { + for (auto const& item : items_) + { + if (item->IsModified()) { + item->Commit(); + item->ClearModified(); + } + } +} + +static bool bIsFuzzing = false; + +#if !defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) +bool utl::ConfigManager::IsFuzzing() +{ + return bIsFuzzing; +} +#endif + +void utl::ConfigManager::EnableFuzzing() +{ + bIsFuzzing = true; + LanguageTag::disable_lt_tag_parse(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/config/confignode.cxx b/unotools/source/config/confignode.cxx new file mode 100644 index 0000000000..93b8953207 --- /dev/null +++ b/unotools/source/config/confignode.cxx @@ -0,0 +1,562 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <unotools/confignode.hxx> +#include <unotools/configpaths.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <osl/diagnose.h> +#include <sal/log.hxx> +#include <com/sun/star/configuration/theDefaultProvider.hpp> +#include <com/sun/star/lang/XSingleServiceFactory.hpp> +#include <com/sun/star/lang/XComponent.hpp> +#include <com/sun/star/util/XChangesBatch.hpp> +#include <com/sun/star/util/XStringEscape.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/container/XNamed.hpp> +#include <com/sun/star/container/XNameContainer.hpp> +#include <com/sun/star/container/XHierarchicalNameAccess.hpp> +#include <comphelper/namedvaluecollection.hxx> + +namespace utl +{ + + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::lang; + using namespace ::com::sun::star::util; + using namespace ::com::sun::star::beans; + using namespace ::com::sun::star::container; + using namespace ::com::sun::star::configuration; + + //= OConfigurationNode + + OConfigurationNode::OConfigurationNode(const Reference< XInterface >& _rxNode ) + :m_bEscapeNames(false) + { + OSL_ENSURE(_rxNode.is(), "OConfigurationNode::OConfigurationNode: invalid node interface!"); + if (_rxNode.is()) + { + // collect all interfaces necessary + m_xHierarchyAccess.set(_rxNode, UNO_QUERY); + m_xDirectAccess.set(_rxNode, UNO_QUERY); + + // reset _all_ interfaces if _one_ of them is not supported + if (!m_xHierarchyAccess.is() || !m_xDirectAccess.is()) + { + m_xHierarchyAccess = nullptr; + m_xDirectAccess = nullptr; + } + + // now for the non-critical interfaces + m_xReplaceAccess.set(_rxNode, UNO_QUERY); + m_xContainerAccess.set(_rxNode, UNO_QUERY); + } + + Reference< XComponent > xConfigNodeComp(m_xDirectAccess, UNO_QUERY); + if (xConfigNodeComp.is()) + startComponentListening(xConfigNodeComp); + + if (isValid()) + m_bEscapeNames = isSetNode() && Reference< XStringEscape >::query(m_xDirectAccess).is(); + } + + OConfigurationNode::OConfigurationNode(const OConfigurationNode& _rSource) + : OEventListenerAdapter() + , m_xHierarchyAccess(_rSource.m_xHierarchyAccess) + , m_xDirectAccess(_rSource.m_xDirectAccess) + , m_xReplaceAccess(_rSource.m_xReplaceAccess) + , m_xContainerAccess(_rSource.m_xContainerAccess) + , m_bEscapeNames(_rSource.m_bEscapeNames) + { + Reference< XComponent > xConfigNodeComp(m_xDirectAccess, UNO_QUERY); + if (xConfigNodeComp.is()) + startComponentListening(xConfigNodeComp); + } + + OConfigurationNode::OConfigurationNode(OConfigurationNode&& _rSource) + : OEventListenerAdapter() + , m_xHierarchyAccess(std::move(_rSource.m_xHierarchyAccess)) + , m_xDirectAccess(std::move(_rSource.m_xDirectAccess)) + , m_xReplaceAccess(std::move(_rSource.m_xReplaceAccess)) + , m_xContainerAccess(std::move(_rSource.m_xContainerAccess)) + , m_bEscapeNames(std::move(_rSource.m_bEscapeNames)) + { + Reference< XComponent > xConfigNodeComp(m_xDirectAccess, UNO_QUERY); + if (xConfigNodeComp.is()) + startComponentListening(xConfigNodeComp); + } + + OConfigurationNode& OConfigurationNode::operator=(const OConfigurationNode& _rSource) + { + stopAllComponentListening(); + + m_xHierarchyAccess = _rSource.m_xHierarchyAccess; + m_xDirectAccess = _rSource.m_xDirectAccess; + m_xContainerAccess = _rSource.m_xContainerAccess; + m_xReplaceAccess = _rSource.m_xReplaceAccess; + m_bEscapeNames = _rSource.m_bEscapeNames; + + Reference< XComponent > xConfigNodeComp(m_xDirectAccess, UNO_QUERY); + if (xConfigNodeComp.is()) + startComponentListening(xConfigNodeComp); + + return *this; + } + + OConfigurationNode& OConfigurationNode::operator=(OConfigurationNode&& _rSource) + { + stopAllComponentListening(); + + m_xHierarchyAccess = std::move(_rSource.m_xHierarchyAccess); + m_xDirectAccess = std::move(_rSource.m_xDirectAccess); + m_xContainerAccess = std::move(_rSource.m_xContainerAccess); + m_xReplaceAccess = std::move(_rSource.m_xReplaceAccess); + m_bEscapeNames = std::move(_rSource.m_bEscapeNames); + + Reference< XComponent > xConfigNodeComp(m_xDirectAccess, UNO_QUERY); + if (xConfigNodeComp.is()) + startComponentListening(xConfigNodeComp); + + return *this; + } + + void OConfigurationNode::_disposing( const EventObject& _rSource ) + { + Reference< XComponent > xDisposingSource(_rSource.Source, UNO_QUERY); + Reference< XComponent > xConfigNodeComp(m_xDirectAccess, UNO_QUERY); + if (xDisposingSource.get() == xConfigNodeComp.get()) + clear(); + } + + OUString OConfigurationNode::getLocalName() const + { + OUString sLocalName; + try + { + Reference< XNamed > xNamed( m_xDirectAccess, UNO_QUERY_THROW ); + sLocalName = xNamed->getName(); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("unotools"); + } + return sLocalName; + } + + OUString OConfigurationNode::normalizeName(const OUString& _rName, NAMEORIGIN _eOrigin) const + { + OUString sName(_rName); + if (m_bEscapeNames) + { + Reference< XStringEscape > xEscaper(m_xDirectAccess, UNO_QUERY); + if (xEscaper.is() && !sName.isEmpty()) + { + try + { + if (NO_CALLER == _eOrigin) + sName = xEscaper->escapeString(sName); + else + sName = xEscaper->unescapeString(sName); + } + catch(Exception&) + { + DBG_UNHANDLED_EXCEPTION("unotools"); + } + } + } + return sName; + } + + Sequence< OUString > OConfigurationNode::getNodeNames() const noexcept + { + OSL_ENSURE(m_xDirectAccess.is(), "OConfigurationNode::getNodeNames: object is invalid!"); + Sequence< OUString > aReturn; + if (m_xDirectAccess.is()) + { + try + { + aReturn = m_xDirectAccess->getElementNames(); + // normalize the names + std::transform(std::cbegin(aReturn), std::cend(aReturn), aReturn.getArray(), + [this](const OUString& rName) -> OUString { return normalizeName(rName, NO_CONFIGURATION); }); + } + catch(Exception&) + { + TOOLS_WARN_EXCEPTION( "unotools", "OConfigurationNode::getNodeNames"); + } + } + + return aReturn; + } + + bool OConfigurationNode::removeNode(const OUString& _rName) const noexcept + { + OSL_ENSURE(m_xContainerAccess.is(), "OConfigurationNode::removeNode: object is invalid!"); + if (m_xContainerAccess.is()) + { + try + { + OUString sName = normalizeName(_rName, NO_CALLER); + m_xContainerAccess->removeByName(sName); + return true; + } + catch (NoSuchElementException&) + { + SAL_WARN( "unotools", "OConfigurationNode::removeNode: there is no element named: " << _rName ); + } + catch(Exception&) + { + TOOLS_WARN_EXCEPTION( "unotools", "OConfigurationNode::removeNode"); + } + } + return false; + } + + OConfigurationNode OConfigurationNode::insertNode(const OUString& _rName,const Reference< XInterface >& _xNode) const noexcept + { + if(_xNode.is()) + { + try + { + OUString sName = normalizeName(_rName, NO_CALLER); + m_xContainerAccess->insertByName(sName, Any(_xNode)); + // if we're here, all was ok ... + return OConfigurationNode( _xNode ); + } + catch(const Exception&) + { + DBG_UNHANDLED_EXCEPTION("unotools"); + } + + // dispose the child if it has already been created, but could not be inserted + Reference< XComponent > xChildComp(_xNode, UNO_QUERY); + if (xChildComp.is()) + try { xChildComp->dispose(); } catch(Exception&) { } + } + + return OConfigurationNode(); + } + + OConfigurationNode OConfigurationNode::createNode(const OUString& _rName) const noexcept + { + Reference< XSingleServiceFactory > xChildFactory(m_xContainerAccess, UNO_QUERY); + OSL_ENSURE(xChildFactory.is(), "OConfigurationNode::createNode: object is invalid or read-only!"); + + if (xChildFactory.is()) // implies m_xContainerAccess.is() + { + Reference< XInterface > xNewChild; + try + { + xNewChild = xChildFactory->createInstance(); + } + catch(const Exception&) + { + DBG_UNHANDLED_EXCEPTION("unotools"); + } + return insertNode(_rName,xNewChild); + } + + return OConfigurationNode(); + } + + OConfigurationNode OConfigurationNode::openNode(const OUString& _rPath) const noexcept + { + OSL_ENSURE(m_xDirectAccess.is(), "OConfigurationNode::openNode: object is invalid!"); + OSL_ENSURE(m_xHierarchyAccess.is(), "OConfigurationNode::openNode: object is invalid!"); + try + { + OUString sNormalized = normalizeName(_rPath, NO_CALLER); + + Reference< XInterface > xNode; + if (m_xDirectAccess.is() && m_xDirectAccess->hasByName(sNormalized)) + { + xNode.set( + m_xDirectAccess->getByName(sNormalized), css::uno::UNO_QUERY); + if (!xNode.is()) + OSL_FAIL("OConfigurationNode::openNode: could not open the node!"); + } + else if (m_xHierarchyAccess.is()) + { + xNode.set( + m_xHierarchyAccess->getByHierarchicalName(_rPath), + css::uno::UNO_QUERY); + if (!xNode.is()) + OSL_FAIL("OConfigurationNode::openNode: could not open the node!"); + } + if (xNode.is()) + return OConfigurationNode( xNode ); + } + catch(const NoSuchElementException&) + { + SAL_WARN( "unotools", "OConfigurationNode::openNode: there is no element named " << _rPath ); + } + catch(Exception&) + { + TOOLS_WARN_EXCEPTION( "unotools", "OConfigurationNode::openNode: caught an exception while retrieving the node!"); + } + return OConfigurationNode(); + } + + bool OConfigurationNode::isSetNode() const + { + bool bIsSet = false; + Reference< XServiceInfo > xSI(m_xHierarchyAccess, UNO_QUERY); + if (xSI.is()) + { + try { bIsSet = xSI->supportsService("com.sun.star.configuration.SetAccess"); } + catch(Exception&) { } + } + return bIsSet; + } + + bool OConfigurationNode::hasByHierarchicalName( const OUString& _rName ) const noexcept + { + OSL_ENSURE( m_xHierarchyAccess.is(), "OConfigurationNode::hasByHierarchicalName: no hierarchy access!" ); + try + { + if ( m_xHierarchyAccess.is() ) + { + OUString sName = normalizeName( _rName, NO_CALLER ); + return m_xHierarchyAccess->hasByHierarchicalName( sName ); + } + } + catch(Exception&) + { + } + return false; + } + + bool OConfigurationNode::hasByName(const OUString& _rName) const noexcept + { + OSL_ENSURE(m_xDirectAccess.is(), "OConfigurationNode::hasByName: object is invalid!"); + try + { + OUString sName = normalizeName(_rName, NO_CALLER); + if (m_xDirectAccess.is()) + return m_xDirectAccess->hasByName(sName); + } + catch(Exception&) + { + } + return false; + } + + bool OConfigurationNode::setNodeValue(const OUString& _rPath, const Any& _rValue) const noexcept + { + bool bResult = false; + + OSL_ENSURE(m_xReplaceAccess.is(), "OConfigurationNode::setNodeValue: object is invalid!"); + if (m_xReplaceAccess.is()) + { + try + { + // check if _rPath is a level-1 path + OUString sNormalizedName = normalizeName(_rPath, NO_CALLER); + if (m_xReplaceAccess->hasByName(sNormalizedName)) + { + m_xReplaceAccess->replaceByName(sNormalizedName, _rValue); + bResult = true; + } + + // check if the name refers to an indirect descendant + else if (m_xHierarchyAccess.is() && m_xHierarchyAccess->hasByHierarchicalName(_rPath)) + { + OSL_ASSERT(!_rPath.isEmpty()); + + OUString sParentPath, sLocalName; + + if ( splitLastFromConfigurationPath(_rPath, sParentPath, sLocalName) ) + { + OConfigurationNode aParentAccess = openNode(sParentPath); + if (aParentAccess.isValid()) + bResult = aParentAccess.setNodeValue(sLocalName, _rValue); + } + else + { + m_xReplaceAccess->replaceByName(sLocalName, _rValue); + bResult = true; + } + } + + } + catch(Exception&) + { + TOOLS_WARN_EXCEPTION( "unotools", "OConfigurationNode::setNodeValue: could not replace the value"); + } + + } + return bResult; + } + + Any OConfigurationNode::getNodeValue(const OUString& _rPath) const noexcept + { + OSL_ENSURE(m_xDirectAccess.is(), "OConfigurationNode::hasByName: object is invalid!"); + OSL_ENSURE(m_xHierarchyAccess.is(), "OConfigurationNode::hasByName: object is invalid!"); + Any aReturn; + try + { + OUString sNormalizedPath = normalizeName(_rPath, NO_CALLER); + if (m_xDirectAccess.is() && m_xDirectAccess->hasByName(sNormalizedPath) ) + { + aReturn = m_xDirectAccess->getByName(sNormalizedPath); + } + else if (m_xHierarchyAccess.is()) + { + aReturn = m_xHierarchyAccess->getByHierarchicalName(_rPath); + } + } + catch(const NoSuchElementException&) + { + DBG_UNHANDLED_EXCEPTION("unotools"); + } + return aReturn; + } + + void OConfigurationNode::clear() noexcept + { + m_xHierarchyAccess.clear(); + m_xDirectAccess.clear(); + m_xReplaceAccess.clear(); + m_xContainerAccess.clear(); + } + + //= helper + + namespace + { + + Reference< XMultiServiceFactory > lcl_getConfigProvider( const Reference<XComponentContext> & i_rContext ) + { + try + { + Reference< XMultiServiceFactory > xProvider = theDefaultProvider::get( i_rContext ); + return xProvider; + } + catch ( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("unotools"); + } + return nullptr; + } + + Reference< XInterface > lcl_createConfigurationRoot( const Reference< XMultiServiceFactory >& i_rxConfigProvider, + const OUString& i_rNodePath, const bool i_bUpdatable, const sal_Int32 i_nDepth ) + { + ENSURE_OR_RETURN( i_rxConfigProvider.is(), "invalid provider", nullptr ); + try + { + ::comphelper::NamedValueCollection aArgs; + aArgs.put( "nodepath", i_rNodePath ); + aArgs.put( "depth", i_nDepth ); + + OUString sAccessService( i_bUpdatable ? + OUString( "com.sun.star.configuration.ConfigurationUpdateAccess" ) : + OUString( "com.sun.star.configuration.ConfigurationAccess" )); + + Reference< XInterface > xRoot( + i_rxConfigProvider->createInstanceWithArguments( sAccessService, aArgs.getWrappedPropertyValues() ), + UNO_SET_THROW + ); + return xRoot; + } + catch ( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("unotools"); + } + return nullptr; + } + } + + OConfigurationTreeRoot::OConfigurationTreeRoot( const Reference< XInterface >& _rxRootNode ) + :OConfigurationNode( _rxRootNode ) + ,m_xCommitter( _rxRootNode, UNO_QUERY ) + { + } + + OConfigurationTreeRoot::OConfigurationTreeRoot( const Reference<XComponentContext> & i_rContext, const OUString& i_rNodePath, const bool i_bUpdatable ) + :OConfigurationNode( lcl_createConfigurationRoot( lcl_getConfigProvider( i_rContext ), + i_rNodePath, i_bUpdatable, -1 ) ) + ,m_xCommitter() + { + if ( i_bUpdatable ) + { + m_xCommitter.set( getUNONode(), UNO_QUERY ); + OSL_ENSURE( m_xCommitter.is(), "OConfigurationTreeRoot::OConfigurationTreeRoot: could not create an updatable node!" ); + } + } + + void OConfigurationTreeRoot::clear() noexcept + { + OConfigurationNode::clear(); + m_xCommitter.clear(); + } + + bool OConfigurationTreeRoot::commit() const noexcept + { + OSL_ENSURE(isValid(), "OConfigurationTreeRoot::commit: object is invalid!"); + if (!isValid()) + return false; + OSL_ENSURE(m_xCommitter.is(), "OConfigurationTreeRoot::commit: I'm a readonly node!"); + if (!m_xCommitter.is()) + return false; + + try + { + m_xCommitter->commitChanges(); + return true; + } + catch(const Exception&) + { + DBG_UNHANDLED_EXCEPTION("unotools"); + } + return false; + } + + OConfigurationTreeRoot OConfigurationTreeRoot::createWithProvider(const Reference< XMultiServiceFactory >& _rxConfProvider, const OUString& _rPath, sal_Int32 _nDepth, CREATION_MODE _eMode) + { + Reference< XInterface > xRoot( lcl_createConfigurationRoot( + _rxConfProvider, _rPath, _eMode != CM_READONLY, _nDepth ) ); + if ( xRoot.is() ) + return OConfigurationTreeRoot( xRoot ); + return OConfigurationTreeRoot(); + } + + OConfigurationTreeRoot OConfigurationTreeRoot::createWithComponentContext( const Reference< XComponentContext >& _rxContext, const OUString& _rPath, sal_Int32 _nDepth, CREATION_MODE _eMode ) + { + return createWithProvider( lcl_getConfigProvider( _rxContext ), _rPath, _nDepth, _eMode ); + } + + OConfigurationTreeRoot OConfigurationTreeRoot::tryCreateWithComponentContext( const Reference< XComponentContext >& rxContext, + const OUString& _rPath, sal_Int32 _nDepth , CREATION_MODE _eMode ) + { + OSL_ENSURE( rxContext.is(), "OConfigurationTreeRoot::tryCreateWithComponentContext: invalid XComponentContext!" ); + try + { + Reference< XMultiServiceFactory > xConfigFactory = theDefaultProvider::get( rxContext ); + return createWithProvider( xConfigFactory, _rPath, _nDepth, _eMode ); + } + catch(const Exception&) + { + // silence this, 'cause the contract of this method states "no assertions" + } + return OConfigurationTreeRoot(); + } + +} // namespace utl + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/config/configpaths.cxx b/unotools/source/config/configpaths.cxx new file mode 100644 index 0000000000..4c626d96b4 --- /dev/null +++ b/unotools/source/config/configpaths.cxx @@ -0,0 +1,307 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <cassert> +#include <string_view> + +#include <unotools/configpaths.hxx> +#include <rtl/ustring.hxx> +#include <rtl/ustrbuf.hxx> +#include <osl/diagnose.h> +#include <o3tl/string_view.hxx> + +namespace utl +{ + +static +void lcl_resolveCharEntities(OUString & aLocalString) +{ + sal_Int32 nEscapePos=aLocalString.indexOf('&'); + if (nEscapePos < 0) return; + + OUStringBuffer aResult; + sal_Int32 nStart = 0; + + do + { + sal_Unicode ch = 0; + if (aLocalString.match("&",nEscapePos)) + ch = '&'; + + else if (aLocalString.match("'",nEscapePos)) + ch = '\''; + + else if (aLocalString.match(""",nEscapePos)) + ch = '"'; + + OSL_ENSURE(ch,"Configuration path contains '&' that is not part of a valid character escape"); + if (ch) + { + aResult.append(aLocalString.subView(nStart,nEscapePos-nStart) + OUStringChar(ch)); + + sal_Int32 nEscapeEnd=aLocalString.indexOf(';',nEscapePos); + nStart = nEscapeEnd+1; + nEscapePos=aLocalString.indexOf('&',nStart); + } + else + { + nEscapePos=aLocalString.indexOf('&',nEscapePos+1); + } + } + while ( nEscapePos > 0); + + aResult.append(aLocalString.subView(nStart)); + + aLocalString = aResult.makeStringAndClear(); +} + +bool splitLastFromConfigurationPath(std::u16string_view _sInPath, + OUString& _rsOutPath, + OUString& _rsLocalName) +{ + size_t nStart,nEnd; + + size_t nPos = _sInPath.size(); + + // for backwards compatibility, strip trailing slash + if (nPos > 1 && _sInPath[ nPos - 1 ] == '/') + { + --nPos; + } + + // check for set element ['xxx'] or ["yyy"] + bool decode; + if (nPos > 0 && _sInPath[ nPos - 1 ] == ']') + { + decode = true; + if (nPos < 3) { // expect at least chQuote + chQuote + ']' at _sInPath[nPos-3..nPos-1] + goto invalid; + } + nPos -= 2; + sal_Unicode chQuote = _sInPath[nPos]; + + if (chQuote == '\'' || chQuote == '\"') + { + nEnd = nPos; + nPos = _sInPath.rfind(chQuote,nEnd - 1); + if (nPos == std::u16string_view::npos) { + goto invalid; + } + nStart = nPos + 1; + } + else + { + goto invalid; + } + + OSL_ENSURE(nPos > 0 && _sInPath[nPos - 1] == '[', "Invalid config path: unmatched quotes or brackets"); + if (nPos > 1 && _sInPath[nPos - 1] == '[') + // expect at least '/' + '[' at _sInPath[nPos-2..nPos-1] + { + nPos = _sInPath.rfind('/',nPos - 2); + if (nPos == std::u16string_view::npos) { + goto invalid; + } + } + else + { + goto invalid; + } + + } + else + { + decode = false; + nEnd = nPos; + if (nEnd == 0) { + goto invalid; + } + nPos = _sInPath.rfind('/',nEnd - 1); + if (nPos == std::u16string_view::npos) { + goto invalid; + } + nStart = nPos + 1; + } + assert( nPos != std::u16string_view::npos && + nPos < nStart && + nStart <= nEnd && + nEnd <= _sInPath.size() ); + + assert(_sInPath[nPos] == '/'); + OSL_ENSURE(nPos != 0 , "Invalid config child path: immediate child of root"); + + _rsLocalName = _sInPath.substr(nStart, nEnd-nStart); + _rsOutPath = (nPos > 0) ? OUString(_sInPath.substr(0,nPos)) : OUString(); + if (decode) { + lcl_resolveCharEntities(_rsLocalName); + } + + return true; + +invalid: + _rsOutPath.clear(); + _rsLocalName = _sInPath; + return false; +} + +OUString extractFirstFromConfigurationPath(OUString const& _sInPath, OUString* _sOutPath) +{ + sal_Int32 nSep = _sInPath.indexOf('/'); + sal_Int32 nBracket = _sInPath.indexOf('['); + + sal_Int32 nStart = nBracket + 1; + sal_Int32 nEnd = nSep; + + if (0 <= nBracket) // found a bracket-quoted relative path + { + if (nSep < 0 || nBracket < nSep) // and the separator comes after it + { + sal_Unicode chQuote = _sInPath[nStart]; + if (chQuote == '\'' || chQuote == '\"') + { + ++nStart; + nEnd = _sInPath.indexOf(chQuote, nStart+1); + nBracket = nEnd+1; + } + else + { + nEnd = _sInPath.indexOf(']',nStart); + nBracket = nEnd; + } + OSL_ENSURE(nEnd > nStart && _sInPath[nBracket] == ']', "Invalid config path: improper mismatch of quote or bracket"); + OSL_ENSURE((nBracket+1 == _sInPath.getLength() && nSep == -1) || (_sInPath[nBracket+1] == '/' && nSep == nBracket+1), "Invalid config path: brackets not followed by slash"); + } + else // ... but our initial element name is in simple form + nStart = 0; + } + + OUString sResult = (nEnd >= 0) ? _sInPath.copy(nStart, nEnd-nStart) : _sInPath; + lcl_resolveCharEntities(sResult); + + if (_sOutPath != nullptr) + { + *_sOutPath = (nSep >= 0) ? _sInPath.copy(nSep + 1) : OUString(); + } + + return sResult; +} + +// find the position after the prefix in the nested path +static sal_Int32 lcl_findPrefixEnd(std::u16string_view _sNestedPath, std::u16string_view _sPrefixPath) +{ + // TODO: currently handles only exact prefix matches + size_t nPrefixLength = _sPrefixPath.size(); + + OSL_ENSURE(nPrefixLength == 0 || _sPrefixPath[nPrefixLength-1] != '/', + "Cannot handle slash-terminated prefix paths"); + + bool bIsPrefix; + if (_sNestedPath.size() > nPrefixLength) + { + bIsPrefix = _sNestedPath[nPrefixLength] == '/' && + o3tl::starts_with(_sNestedPath, _sPrefixPath); + ++nPrefixLength; + } + else if (_sNestedPath.size() == nPrefixLength) + { + bIsPrefix = _sNestedPath == _sPrefixPath; + } + else + { + bIsPrefix = false; + } + + return bIsPrefix ? nPrefixLength : 0; +} + +bool isPrefixOfConfigurationPath(std::u16string_view _sNestedPath, + std::u16string_view _sPrefixPath) +{ + return _sPrefixPath.empty() || lcl_findPrefixEnd(_sNestedPath,_sPrefixPath) != 0; +} + +OUString dropPrefixFromConfigurationPath(OUString const& _sNestedPath, + std::u16string_view _sPrefixPath) +{ + if ( sal_Int32 nPrefixEnd = lcl_findPrefixEnd(_sNestedPath,_sPrefixPath) ) + { + return _sNestedPath.copy(nPrefixEnd); + } + else + { + OSL_ENSURE(_sPrefixPath.empty(), "Path does not start with expected prefix"); + + return _sNestedPath; + } +} + +static +OUString lcl_wrapName(std::u16string_view _sContent, const OUString& _sType) +{ + const sal_Unicode * const pBeginContent = _sContent.data(); + const sal_Unicode * const pEndContent = pBeginContent + _sContent.size(); + + OSL_PRECOND(!_sType.isEmpty(), "Unexpected config type name: empty"); + OSL_PRECOND(pBeginContent <= pEndContent, "Invalid config name: empty"); + + if (pBeginContent == pEndContent) + return _sType; + + OUStringBuffer aNormalized(_sType.getLength() + _sContent.size() + 4); // reserve approximate size initially + + // prefix: type, opening bracket and quote + aNormalized.append( _sType + "['" ); + + // content: copy over each char and handle escaping + for(const sal_Unicode* pCur = pBeginContent; pCur != pEndContent; ++pCur) + { + // append (escape if needed) + switch(*pCur) + { + case u'&' : aNormalized.append( "&" ); break; + case u'\'': aNormalized.append( "'" ); break; + case u'\"': aNormalized.append( """ ); break; + + default: aNormalized.append( *pCur ); + } + } + + // suffix: closing quote and bracket + aNormalized.append( "']" ); + + return aNormalized.makeStringAndClear(); +} + +OUString wrapConfigurationElementName(std::u16string_view _sElementName) +{ + return lcl_wrapName(_sElementName, "*" ); +} + +OUString wrapConfigurationElementName(std::u16string_view _sElementName, + OUString const& _sTypeName) +{ + // todo: check that _sTypeName is valid + return lcl_wrapName(_sElementName, _sTypeName); +} + +} // namespace utl + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/config/configvaluecontainer.cxx b/unotools/source/config/configvaluecontainer.cxx new file mode 100644 index 0000000000..6852674a95 --- /dev/null +++ b/unotools/source/config/configvaluecontainer.cxx @@ -0,0 +1,304 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <sal/log.hxx> +#include <unotools/configvaluecontainer.hxx> +#include <unotools/confignode.hxx> +#include <uno/data.h> +#include <algorithm> +#include <utility> +#include <vector> + +namespace utl +{ + + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::lang; + + //= NodeValueAccessor + + namespace { + + enum class LocationType + { + SimplyObjectInstance, + Unbound + }; + + } + + struct NodeValueAccessor + { + private: + OUString sRelativePath; // the relative path of the node + LocationType eLocationType; // the type of location where the value is stored + void* pLocation; // the pointer to the location + Type aDataType; // the type object pointed to by pLocation + + public: + explicit NodeValueAccessor( OUString _aNodePath ); + + void bind( void* _pLocation, const Type& _rType ); + + bool isBound( ) const { return ( LocationType::Unbound != eLocationType ) && ( nullptr != pLocation ); } + const OUString& getPath( ) const { return sRelativePath; } + LocationType getLocType( ) const { return eLocationType; } + void* getLocation( ) const { return pLocation; } + const Type& getDataType( ) const { return aDataType; } + + bool operator == ( const NodeValueAccessor& rhs ) const; + }; + + NodeValueAccessor::NodeValueAccessor( OUString _aNodePath ) + :sRelativePath(std::move( _aNodePath )) + ,eLocationType( LocationType::Unbound ) + ,pLocation( nullptr ) + { + } + + bool NodeValueAccessor::operator == ( const NodeValueAccessor& rhs ) const + { + return ( sRelativePath == rhs.sRelativePath ) + && ( eLocationType == rhs.eLocationType ) + && ( pLocation == rhs.pLocation ); + } + + void NodeValueAccessor::bind( void* _pLocation, const Type& _rType ) + { + SAL_WARN_IF(isBound(), "unotools.config", "NodeValueAccessor::bind: already bound!"); + + eLocationType = LocationType::SimplyObjectInstance; + pLocation = _pLocation; + aDataType = _rType; + } + + static + void lcl_copyData( const NodeValueAccessor& _rAccessor, const Any& _rData, std::mutex& _rMutex ) + { + std::unique_lock aGuard( _rMutex ); + + SAL_WARN_IF(!_rAccessor.isBound(), "unotools.config", "::utl::lcl_copyData: invalid accessor!"); + switch ( _rAccessor.getLocType() ) + { + case LocationType::SimplyObjectInstance: + { + if ( _rData.hasValue() ) + { + // assign the value + bool bSuccess = uno_type_assignData( + _rAccessor.getLocation(), _rAccessor.getDataType().getTypeLibType(), + const_cast< void* >( _rData.getValue() ), _rData.getValueType().getTypeLibType(), + cpp_queryInterface, cpp_acquire, cpp_release + ); + SAL_WARN_IF(!bSuccess, "unotools.config", + "::utl::lcl_copyData( Accessor, Any ): could not assign the data (node path: \"" << _rAccessor.getPath() << "\""); + } + else { + SAL_INFO("unotools.config", "::utl::lcl_copyData: NULL value lost!"); + } + } + break; + default: + break; + } + } + + static + void lcl_copyData( Any& _rData, const NodeValueAccessor& _rAccessor, std::mutex& _rMutex ) + { + std::unique_lock aGuard( _rMutex ); + + SAL_WARN_IF(!_rAccessor.isBound(), "unotools.config", "::utl::lcl_copyData: invalid accessor!" ); + switch ( _rAccessor.getLocType() ) + { + case LocationType::SimplyObjectInstance: + // a simple setValue... + _rData.setValue( _rAccessor.getLocation(), _rAccessor.getDataType() ); + break; + + default: + break; + } + } + + //= functors on NodeValueAccessor instances + + namespace { + + /// base class for functors synchronizing between exchange locations and config sub nodes + struct SubNodeAccess + { + protected: + const OConfigurationNode& m_rRootNode; + std::mutex& m_rMutex; + + public: + SubNodeAccess( const OConfigurationNode& _rRootNode, std::mutex& _rMutex ) + :m_rRootNode( _rRootNode ) + ,m_rMutex( _rMutex ) + { + } + }; + + struct UpdateFromConfig : public SubNodeAccess + { + public: + UpdateFromConfig( const OConfigurationNode& _rRootNode, std::mutex& _rMutex ) : SubNodeAccess( _rRootNode, _rMutex ) { } + + void operator() ( NodeValueAccessor const & _rAccessor ) + { + ::utl::lcl_copyData( _rAccessor, m_rRootNode.getNodeValue( _rAccessor.getPath( ) ), m_rMutex ); + } + }; + + struct UpdateToConfig : public SubNodeAccess + { + public: + UpdateToConfig( const OConfigurationNode& _rRootNode, std::mutex& _rMutex ) : SubNodeAccess( _rRootNode, _rMutex ) { } + + void operator() ( NodeValueAccessor const & _rAccessor ) + { + Any aNewValue; + lcl_copyData( aNewValue, _rAccessor, m_rMutex ); + m_rRootNode.setNodeValue( _rAccessor.getPath( ), aNewValue ); + } + }; + + } + + //= OConfigurationValueContainerImpl + + struct OConfigurationValueContainerImpl + { + Reference< XComponentContext > xORB; // the service factory + std::mutex& rMutex; // the mutex for accessing the data containers + OConfigurationTreeRoot aConfigRoot; // the configuration node we're accessing + + std::vector<NodeValueAccessor> aAccessors; // the accessors to the node values + + OConfigurationValueContainerImpl( const Reference< XComponentContext >& _rxORB, std::mutex& _rMutex ) + :xORB( _rxORB ) + ,rMutex( _rMutex ) + { + } + }; + + //= OConfigurationValueContainer + + OConfigurationValueContainer::OConfigurationValueContainer( + const Reference< XComponentContext >& _rxORB, std::mutex& _rAccessSafety, + const char* _pConfigLocation, const sal_Int32 _nLevels ) + :m_pImpl( new OConfigurationValueContainerImpl( _rxORB, _rAccessSafety ) ) + { + implConstruct( OUString::createFromAscii( _pConfigLocation ), _nLevels ); + } + + OConfigurationValueContainer::~OConfigurationValueContainer() + { + } + + void OConfigurationValueContainer::implConstruct( const OUString& _rConfigLocation, + const sal_Int32 _nLevels ) + { + SAL_WARN_IF(m_pImpl->aConfigRoot.isValid(), "unotools.config", "OConfigurationValueContainer::implConstruct: already initialized!"); + + // create the configuration node we're about to work with + m_pImpl->aConfigRoot = OConfigurationTreeRoot::createWithComponentContext( + m_pImpl->xORB, + _rConfigLocation, + _nLevels + ); + SAL_WARN_IF(!m_pImpl->aConfigRoot.isValid(), "unotools.config", + "Could not access the configuration node located at " << _rConfigLocation); + } + + void OConfigurationValueContainer::registerExchangeLocation( const char* _pRelativePath, + void* _pContainer, const Type& _rValueType ) + { + // checks... + SAL_WARN_IF(!_pContainer, "unotools.config", + "OConfigurationValueContainer::registerExchangeLocation: invalid container location!"); + SAL_WARN_IF(!( (TypeClass_CHAR == _rValueType.getTypeClass( ) ) + || ( TypeClass_BOOLEAN == _rValueType.getTypeClass( ) ) + || ( TypeClass_BYTE == _rValueType.getTypeClass( ) ) + || ( TypeClass_SHORT == _rValueType.getTypeClass( ) ) + || ( TypeClass_LONG == _rValueType.getTypeClass( ) ) + || ( TypeClass_DOUBLE == _rValueType.getTypeClass( ) ) + || ( TypeClass_STRING == _rValueType.getTypeClass( ) ) + || ( TypeClass_SEQUENCE == _rValueType.getTypeClass( ) )), + "unotools.config", + "OConfigurationValueContainer::registerExchangeLocation: invalid type!" ); + + // build an accessor for this container + NodeValueAccessor aNewAccessor( OUString::createFromAscii( _pRelativePath ) ); + aNewAccessor.bind( _pContainer, _rValueType ); + + // insert it into our structure + implRegisterExchangeLocation( aNewAccessor ); + } + + void OConfigurationValueContainer::read( ) + { + std::for_each( + m_pImpl->aAccessors.begin(), + m_pImpl->aAccessors.end(), + UpdateFromConfig( m_pImpl->aConfigRoot, m_pImpl->rMutex ) + ); + } + + void OConfigurationValueContainer::commit() + { + // write the current values in the exchange locations + std::for_each( + m_pImpl->aAccessors.begin(), + m_pImpl->aAccessors.end(), + UpdateToConfig( m_pImpl->aConfigRoot, m_pImpl->rMutex ) + ); + + // commit the changes done + m_pImpl->aConfigRoot.commit( ); + } + + void OConfigurationValueContainer::implRegisterExchangeLocation( const NodeValueAccessor& _rAccessor ) + { + // some checks + SAL_WARN_IF(m_pImpl->aConfigRoot.isValid() && !m_pImpl->aConfigRoot.hasByHierarchicalName(_rAccessor.getPath()), + "unotools.config", + "OConfigurationValueContainer::implRegisterExchangeLocation: invalid relative path!" ); + + // another check (should be the first container for this node) + SAL_WARN_IF(!(m_pImpl->aAccessors.end() == ::std::find( + m_pImpl->aAccessors.begin(), + m_pImpl->aAccessors.end(), + _rAccessor)), + "unotools.config", + "OConfigurationValueContainer::implRegisterExchangeLocation: already registered a container for this subnode!" ); + + // remember the accessor + m_pImpl->aAccessors.push_back( _rAccessor ); + + // and initially fill the value + lcl_copyData( _rAccessor, m_pImpl->aConfigRoot.getNodeValue( _rAccessor.getPath() ), m_pImpl->rMutex ); + } + +} // namespace utl + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/config/defaultoptions.cxx b/unotools/source/config/defaultoptions.cxx new file mode 100644 index 0000000000..127cc68580 --- /dev/null +++ b/unotools/source/config/defaultoptions.cxx @@ -0,0 +1,119 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <osl/file.hxx> +#include <unotools/defaultoptions.hxx> +#include <unotools/pathoptions.hxx> +#include <com/sun/star/uno/Sequence.hxx> +#include <rtl/ustrbuf.hxx> +#include <officecfg/Office/Common.hxx> + +namespace SvtDefaultOptions +{ + +OUString GetDefaultPath( SvtPathOptions::Paths nId ) +{ + SvtPathOptions aPathOpt; + auto seqToPath = [&aPathOpt] (const css::uno::Sequence<OUString> & rSeq) + { + // single paths + sal_Int32 nCount = rSeq.getLength(); + OUStringBuffer aFullPathBuf(nCount * 40); + for ( sal_Int32 nPosition = 0; nPosition < nCount; ++nPosition ) + { + aFullPathBuf.append(aPathOpt.SubstituteVariable( rSeq[ nPosition ] )); + if ( nPosition < nCount-1 ) + aFullPathBuf.append(";"); + } + return aFullPathBuf.makeStringAndClear(); + }; + + OUString aRet; + switch (nId) + { + case SvtPathOptions::Paths::AddIn: + aRet = aPathOpt.SubstituteVariable(officecfg::Office::Common::Path::Default::Addin::get()); break; + case SvtPathOptions::Paths::AutoCorrect: + aRet = seqToPath(officecfg::Office::Common::Path::Default::AutoCorrect::get()); break; + case SvtPathOptions::Paths::AutoText: + aRet = seqToPath(officecfg::Office::Common::Path::Default::AutoText::get()); break; + case SvtPathOptions::Paths::Backup: + aRet = aPathOpt.SubstituteVariable(officecfg::Office::Common::Path::Default::Backup::get()); break; + case SvtPathOptions::Paths::Basic: + aRet = seqToPath(officecfg::Office::Common::Path::Default::Basic::get()); break; + case SvtPathOptions::Paths::Bitmap: + aRet = aPathOpt.SubstituteVariable(officecfg::Office::Common::Path::Default::Bitmap::get()); break; + case SvtPathOptions::Paths::Config: + aRet = aPathOpt.SubstituteVariable(officecfg::Office::Common::Path::Default::Config::get()); break; + case SvtPathOptions::Paths::Dictionary: + aRet = aPathOpt.SubstituteVariable(officecfg::Office::Common::Path::Default::Dictionary::get()); break; + case SvtPathOptions::Paths::Favorites: + aRet = aPathOpt.SubstituteVariable(officecfg::Office::Common::Path::Default::Favorite::get()); break; + case SvtPathOptions::Paths::Filter: + aRet = aPathOpt.SubstituteVariable(officecfg::Office::Common::Path::Default::Filter::get()); break; + case SvtPathOptions::Paths::Gallery: + aRet = seqToPath(officecfg::Office::Common::Path::Default::Gallery::get()); break; + case SvtPathOptions::Paths::Graphic: + aRet = aPathOpt.SubstituteVariable(officecfg::Office::Common::Path::Default::Graphic::get()); break; + case SvtPathOptions::Paths::Help: + aRet = aPathOpt.SubstituteVariable(officecfg::Office::Common::Path::Default::Help::get()); break; + case SvtPathOptions::Paths::Linguistic: + aRet = aPathOpt.SubstituteVariable(officecfg::Office::Common::Path::Default::Linguistic::get()); break; + case SvtPathOptions::Paths::Module: + aRet = aPathOpt.SubstituteVariable(officecfg::Office::Common::Path::Default::Module::get()); break; + case SvtPathOptions::Paths::Palette: + aRet = aPathOpt.SubstituteVariable(officecfg::Office::Common::Path::Default::Palette::get()); break; + case SvtPathOptions::Paths::Plugin: + aRet = seqToPath(officecfg::Office::Common::Path::Default::Plugin::get()); break; + case SvtPathOptions::Paths::Temp: + aRet = aPathOpt.SubstituteVariable(officecfg::Office::Common::Path::Default::Temp::get()); break; + case SvtPathOptions::Paths::Template: + aRet = seqToPath(officecfg::Office::Common::Path::Default::Template::get()); break; + case SvtPathOptions::Paths::UserConfig: + aRet = aPathOpt.SubstituteVariable(officecfg::Office::Common::Path::Default::UserConfig::get()); break; + case SvtPathOptions::Paths::Work: + aRet = aPathOpt.SubstituteVariable(officecfg::Office::Common::Path::Default::Work::get()); break; + case SvtPathOptions::Paths::Classification: + aRet = aPathOpt.SubstituteVariable(officecfg::Office::Common::Path::Default::Classification::get()); break; + default: + assert(false); + } + + + if ( nId == SvtPathOptions::Paths::AddIn || + nId == SvtPathOptions::Paths::Filter || + nId == SvtPathOptions::Paths::Help || + nId == SvtPathOptions::Paths::Module || + nId == SvtPathOptions::Paths::Plugin ) + { + OUString aTmp; + osl::FileBase::getFileURLFromSystemPath( aRet, aTmp ); + aRet = aTmp; + } + + return aRet; +} + +} // namespace + + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/config/docinfohelper.cxx b/unotools/source/config/docinfohelper.cxx new file mode 100644 index 0000000000..6674649eae --- /dev/null +++ b/unotools/source/config/docinfohelper.cxx @@ -0,0 +1,105 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <rtl/ustrbuf.hxx> +#include <unotools/configmgr.hxx> +#include <unotools/bootstrap.hxx> +#include <unotools/docinfohelper.hxx> +#include <rtl/bootstrap.hxx> +#include <officecfg/Office/Common.hxx> + +using namespace ::com::sun::star; + +namespace utl +{ + +OUString DocInfoHelper::GetGeneratorString() +{ + static const OUString sGenerator = []() + { + OUString aResultOverride = officecfg::Office::Common::Save::Document::GeneratorOverride::get(); + if( !aResultOverride.isEmpty()) + return aResultOverride; + + OUStringBuffer aResult(128); + + // First product: branded name + version + // version is <product_versions>_<product_extension>$<platform> + + // plain product name + OUString aValue( utl::ConfigManager::getProductName() ); + if ( !aValue.isEmpty() ) + { + aResult.append( aValue.replace( ' ', '_' ) + "/" ); + + aValue = utl::ConfigManager::getProductVersion(); + if ( !aValue.isEmpty() ) + { + aResult.append( aValue.replace( ' ', '_' ) ); + + aValue = utl::ConfigManager::getProductExtension(); + if ( !aValue.isEmpty() ) + { + aResult.append( aValue.replace( ' ', '_' ) ); + } + } + + OUString os( "$_OS" ); + OUString arch( "$_ARCH" ); + ::rtl::Bootstrap::expandMacros(os); + ::rtl::Bootstrap::expandMacros(arch); + aResult.append( "$" + os + "_" + arch + " " ); + } + + // second product: LibreOffice_project/<build_information> + // build_information has '(' and '[' encoded as '$', ')' and ']' ignored + // and ':' replaced by '-' + { + aResult.append( "LibreOffice_project/" ); + OUString aBuildId( Bootstrap::getBuildIdData( OUString() ) ); + for( sal_Int32 i=0; i < aBuildId.getLength(); i++ ) + { + sal_Unicode c = aBuildId[i]; + switch( c ) + { + case '(': + case '[': + aResult.append( '$' ); + break; + case ')': + case ']': + break; + case ':': + aResult.append( '-' ); + break; + default: + aResult.append( c ); + break; + } + } + } + + return aResult.makeStringAndClear(); + }(); + return sGenerator; +} + +} // end of namespace utl + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/config/dynamicmenuoptions.cxx b/unotools/source/config/dynamicmenuoptions.cxx new file mode 100644 index 0000000000..05b9c17073 --- /dev/null +++ b/unotools/source/config/dynamicmenuoptions.cxx @@ -0,0 +1,339 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <o3tl/string_view.hxx> +#include <unotools/dynamicmenuoptions.hxx> +#include <tools/debug.hxx> +#include <unotools/configmgr.hxx> +#include <unotools/configitem.hxx> +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/Sequence.hxx> + +#include <vector> +#include <algorithm> +#include <string_view> + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::beans; + +constexpr OUStringLiteral DYNAMICMENU_PROPERTYNAME_URL = u"URL"; +constexpr OUStringLiteral DYNAMICMENU_PROPERTYNAME_TITLE = u"Title"; +constexpr OUStringLiteral DYNAMICMENU_PROPERTYNAME_IMAGEIDENTIFIER = u"ImageIdentifier"; +constexpr OUStringLiteral DYNAMICMENU_PROPERTYNAME_TARGETNAME = u"TargetName"; + +constexpr OUString PATHDELIMITER = u"/"_ustr; + +constexpr OUString SETNODE_NEWMENU = u"New"_ustr; +constexpr OUString SETNODE_WIZARDMENU = u"Wizard"_ustr; + +#define PROPERTYNAME_URL DYNAMICMENU_PROPERTYNAME_URL +#define PROPERTYNAME_TITLE DYNAMICMENU_PROPERTYNAME_TITLE +#define PROPERTYNAME_IMAGEIDENTIFIER DYNAMICMENU_PROPERTYNAME_IMAGEIDENTIFIER +#define PROPERTYNAME_TARGETNAME DYNAMICMENU_PROPERTYNAME_TARGETNAME + +#define PROPERTYCOUNT 4 + +constexpr std::u16string_view PATHPREFIX_SETUP = u"m"; + +namespace +{ +/*-**************************************************************************************************************** + @descr support simple menu structures and operations on it +****************************************************************************************************************-*/ +struct SvtDynMenu +{ + // append setup written menu entry + // Don't touch name of entry. It was defined by setup and must be the same every time! + // Look for double menu entries here too... may be some separator items are superfluous... + void AppendSetupEntry( const SvtDynMenuEntry& rEntry ) + { + if( lSetupEntries.empty() || lSetupEntries.rbegin()->sURL != rEntry.sURL ) + lSetupEntries.push_back( rEntry ); + } + + // convert internal list to external format + // for using it on right menus really + // Notice: We build a property list with 4 entries and set it on result list then. + // Separator entries will be packed in another way then normal entries! We define + // special string "sSeparator" to perform too ... + std::vector< SvtDynMenuEntry > GetList() const + { + sal_Int32 nSetupCount = static_cast<sal_Int32>(lSetupEntries.size()); + sal_Int32 nUserCount = static_cast<sal_Int32>(lUserEntries.size()); + sal_Int32 nStep = 0; + std::vector< SvtDynMenuEntry > lResult ( nSetupCount+nUserCount ); + OUString sSeparator ( "private:separator" ); + + for( const auto& pList : {&lSetupEntries, &lUserEntries} ) + { + for( const auto& rItem : *pList ) + { + SvtDynMenuEntry entry; + if( rItem.sURL == sSeparator ) + { + entry.sURL = sSeparator; + } + else + { + entry = rItem; + } + lResult[nStep] = entry; + ++nStep; + } + } + return lResult; + } + +private: + std::vector< SvtDynMenuEntry > lSetupEntries; + std::vector< SvtDynMenuEntry > lUserEntries; +}; + +} + +namespace SvtDynamicMenuOptions +{ + +static Sequence< OUString > lcl_GetPropertyNames( + css::uno::Reference<css::container::XHierarchicalNameAccess> const & xHierarchyAccess, + sal_uInt32& nNewCount, sal_uInt32& nWizardCount ); + +std::vector< SvtDynMenuEntry > GetMenu( EDynamicMenuType eMenu ) +{ + SvtDynMenu aNewMenu; + SvtDynMenu aWizardMenu; + + Reference<css::container::XHierarchicalNameAccess> xHierarchyAccess = utl::ConfigManager::acquireTree(u"Office.Common/Menus/"); + + // Get names and values of all accessible menu entries and fill internal structures. + // See impl_GetPropertyNames() for further information. + sal_uInt32 nNewCount = 0; + sal_uInt32 nWizardCount = 0; + Sequence< OUString > lNames = lcl_GetPropertyNames ( xHierarchyAccess, nNewCount , + nWizardCount ); + Sequence< Any > lValues = utl::ConfigItem::GetProperties( xHierarchyAccess, lNames, /*bAllLocales*/false ); + + // Safe impossible cases. + // We need values from ALL configuration keys. + // Follow assignment use order of values in relation to our list of key names! + DBG_ASSERT( !(lNames.getLength()!=lValues.getLength()), "SvtDynamicMenuOptions_Impl::SvtDynamicMenuOptions_Impl()\nI miss some values of configuration keys!\n" ); + + // Copy values from list in right order to our internal member. + // Attention: List for names and values have an internal construction pattern! + + // first "New" menu ... + // Name Value + // /New/1/URL "private:factory/swriter" + // /New/1/Title "New Writer Document" + // /New/1/ImageIdentifier "icon_writer" + // /New/1/TargetName "_blank" + + // /New/2/URL "private:factory/scalc" + // /New/2/Title "New Calc Document" + // /New/2/ImageIdentifier "icon_calc" + // /New/2/TargetName "_blank" + + // second "Wizard" menu ... + // /Wizard/1/URL "file://b" + // /Wizard/1/Title "PaintSomething" + // /Wizard/1/ImageIdentifier "icon_?" + // /Wizard/1/TargetName "_self" + + // ... and so on ... + + sal_uInt32 nItem = 0; + sal_uInt32 nPosition = 0; + + // Get names/values for new menu. + // 4 subkeys for every item! + for( nItem=0; nItem<nNewCount; ++nItem ) + { + SvtDynMenuEntry aItem; + lValues[nPosition] >>= aItem.sURL; + ++nPosition; + lValues[nPosition] >>= aItem.sTitle; + ++nPosition; + lValues[nPosition] >>= aItem.sImageIdentifier; + ++nPosition; + lValues[nPosition] >>= aItem.sTargetName; + ++nPosition; + aNewMenu.AppendSetupEntry( aItem ); + } + + // Attention: Don't reset nPosition here! + + // Get names/values for wizard menu. + // 4 subkeys for every item! + for( nItem=0; nItem<nWizardCount; ++nItem ) + { + SvtDynMenuEntry aItem; + lValues[nPosition] >>= aItem.sURL; + ++nPosition; + lValues[nPosition] >>= aItem.sTitle; + ++nPosition; + lValues[nPosition] >>= aItem.sImageIdentifier; + ++nPosition; + lValues[nPosition] >>= aItem.sTargetName; + ++nPosition; + aWizardMenu.AppendSetupEntry( aItem ); + } + + std::vector< SvtDynMenuEntry > lReturn; + switch( eMenu ) + { + case EDynamicMenuType::NewMenu : + lReturn = aNewMenu.GetList(); + break; + + case EDynamicMenuType::WizardMenu : + lReturn = aWizardMenu.GetList(); + break; + } + return lReturn; +} + +static void lcl_SortAndExpandPropertyNames( const Sequence< OUString >& lSource, + Sequence< OUString >& lDestination, std::u16string_view sSetNode ); + +/*-**************************************************************************************************** + @short return list of key names of our configuration management which represent our module tree + @descr This method returns the current list of key names! We need it to get needed values from our + configuration management and support dynamical menu item lists! + @param "nNewCount" , returns count of menu entries for "new" + @param "nWizardCount" , returns count of menu entries for "wizard" + @return A list of configuration key names is returned. +*//*-*****************************************************************************************************/ +static Sequence< OUString > lcl_GetPropertyNames( + css::uno::Reference<css::container::XHierarchicalNameAccess> const & xHierarchyAccess, + sal_uInt32& nNewCount, sal_uInt32& nWizardCount ) +{ + // First get ALL names of current existing list items in configuration! + Sequence< OUString > lNewItems = utl::ConfigItem::GetNodeNames( xHierarchyAccess, SETNODE_NEWMENU, utl::ConfigNameFormat::LocalPath ); + Sequence< OUString > lWizardItems = utl::ConfigItem::GetNodeNames( xHierarchyAccess, SETNODE_WIZARDMENU, utl::ConfigNameFormat::LocalPath ); + + // Get information about list counts ... + nNewCount = lNewItems.getLength(); + nWizardCount = lWizardItems.getLength(); + + // Sort and expand all three list to result list ... + Sequence< OUString > lProperties; + lcl_SortAndExpandPropertyNames( lNewItems , lProperties, SETNODE_NEWMENU ); + lcl_SortAndExpandPropertyNames( lWizardItems , lProperties, SETNODE_WIZARDMENU ); + + // Return result. + return lProperties; +} + +/*-**************************************************************************************************** + @short sort given source list and expand it for all well known properties to destination + @descr We must support sets of entries with count inside the name .. but some of them could be missing! + e.g. s1-s2-s3-s0-u1-s6-u5-u7 + Then we must sort it by name and expand it to the follow one: + sSetNode/s0/URL + sSetNode/s0/Title + sSetNode/s0/... + sSetNode/s1/URL + sSetNode/s1/Title + sSetNode/s1/... + ... + sSetNode/s6/URL + sSetNode/s6/Title + sSetNode/s6/... + sSetNode/u1/URL + sSetNode/u1/Title + sSetNode/u1/... + ... + sSetNode/u7/URL + sSetNode/u7/Title + sSetNode/u7/... + Rules: We start with all setup written entries names "sx" and x=[0..n]. + Then we handle all "ux" items. Inside these blocks we sort it ascending by number. + + @attention We add these expanded list to the end of given "lDestination" list! + So we must start on "lDestination.getLength()". + Reallocation of memory of destination list is done by us! + + @seealso method impl_GetPropertyNames() + + @param "lSource" , original list (e.g. [m1-m2-m3-m6-m0] ) + @param "lDestination" , destination of operation + @param "sSetNode" , name of configuration set to build complete path + @return A list of configuration key names is returned. +*//*-*****************************************************************************************************/ + +static void lcl_SortAndExpandPropertyNames( const Sequence< OUString >& lSource , + Sequence< OUString >& lDestination , + std::u16string_view sSetNode ) +{ + struct CountWithPrefixSort + { + bool operator() ( std::u16string_view s1, std::u16string_view s2 ) const + { + // Get order numbers from entry name without prefix. + // e.g. "m10" => 10 + // "m5" => 5 + sal_Int32 n1 = o3tl::toInt32(s1.substr( 1 )); + sal_Int32 n2 = o3tl::toInt32(s2.substr( 1 )); + // MUST be in [0,1] ... because it's a difference between + // insert-positions of given entries in sorted list! + return( n1<n2 ); + } + }; + struct SelectByPrefix + { + bool operator() ( std::u16string_view s ) const + { + // Prefer setup written entries by check first letter of given string. It must be a "s". + return o3tl::starts_with( s, PATHPREFIX_SETUP ); + } + }; + + std::vector< OUString > lTemp; + sal_Int32 nSourceCount = lSource.getLength(); + sal_Int32 nDestinationStep = lDestination.getLength(); // start on end of current list ...! + + lDestination.realloc( (nSourceCount*PROPERTYCOUNT)+nDestinationStep ); // get enough memory for copy operations after nDestination ... + auto plDestination = lDestination.getArray(); + + // Copy all items to temp. vector to use fast sort operations :-) + lTemp.insert( lTemp.end(), lSource.begin(), lSource.end() ); + + // Sort all entries by number ... + std::stable_sort( lTemp.begin(), lTemp.end(), CountWithPrefixSort() ); + // and split into setup & user written entries! + std::stable_partition( lTemp.begin(), lTemp.end(), SelectByPrefix() ); + + // Copy sorted entries to destination and expand every item with + // 4 supported sub properties. + for( const auto& rItem : lTemp ) + { + OUString sFixPath(OUString::Concat(sSetNode) + PATHDELIMITER + rItem + PATHDELIMITER); + plDestination[nDestinationStep++] = sFixPath + PROPERTYNAME_URL; + plDestination[nDestinationStep++] = sFixPath + PROPERTYNAME_TITLE; + plDestination[nDestinationStep++] = sFixPath + PROPERTYNAME_IMAGEIDENTIFIER; + plDestination[nDestinationStep++] = sFixPath + PROPERTYNAME_TARGETNAME; + } +} + + +} // namespace SvtDynamicMenuOptions + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/config/eventcfg.cxx b/unotools/source/config/eventcfg.cxx new file mode 100644 index 0000000000..62f854c003 --- /dev/null +++ b/unotools/source/config/eventcfg.cxx @@ -0,0 +1,380 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <comphelper/propertyvalue.hxx> +#include <unotools/eventcfg.hxx> +#include <unotools/configmgr.hxx> +#include <unotools/configitem.hxx> +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <o3tl/enumarray.hxx> +#include <o3tl/enumrange.hxx> +#include <rtl/ref.hxx> +#include <sal/log.hxx> + +#include "itemholder1.hxx" + +#include <algorithm> +#include <unordered_map> + +using namespace ::utl; +using namespace ::osl; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star; + +#define PATHDELIMITER "/" +#define SETNODE_BINDINGS "Bindings" +#define PROPERTYNAME_BINDINGURL "BindingURL" + +static o3tl::enumarray<GlobalEventId, const char*> pEventAsciiNames = +{ +"OnStartApp", +"OnCloseApp", +"OnCreate", +"OnNew", +"OnLoadFinished", +"OnLoad", +"OnPrepareUnload", +"OnUnload", +"OnSave", +"OnSaveDone", +"OnSaveFailed", +"OnSaveAs", +"OnSaveAsDone", +"OnSaveAsFailed", +"OnCopyTo", +"OnCopyToDone", +"OnCopyToFailed", +"OnFocus", +"OnUnfocus", +"OnPrint", +"OnViewCreated", +"OnPrepareViewClosing", +"OnViewClosed", +"OnModifyChanged", +"OnTitleChanged", +"OnVisAreaChanged", +"OnModeChanged", +"OnStorageChanged" +}; + +typedef std::unordered_map< OUString, OUString > EventBindingHash; +typedef o3tl::enumarray< GlobalEventId, OUString > SupportedEventsVector; + +static std::mutex& GetOwnStaticMutex() +{ + static std::mutex INSTANCE; + return INSTANCE; +} + +class GlobalEventConfig_Impl : public utl::ConfigItem +{ +private: + EventBindingHash m_eventBindingHash; + SupportedEventsVector m_supportedEvents; + + void initBindingInfo(); + + virtual void ImplCommit() override; + +public: + GlobalEventConfig_Impl( ); + virtual ~GlobalEventConfig_Impl( ) override; + + void Notify( const css::uno::Sequence<OUString>& aPropertyNames) override; + + /// @throws css::lang::IllegalArgumentException + /// @throws css::container::NoSuchElementException + /// @throws css::lang::WrappedTargetException + /// @throws css::uno::RuntimeException + void replaceByName( const OUString& aName, const css::uno::Any& aElement ); + /// @throws css::container::NoSuchElementException + /// @throws css::lang::WrappedTargetException + /// @throws css::uno::RuntimeException + css::uno::Sequence < css::beans::PropertyValue > getByName( const OUString& aName ); + /// @throws css::uno::RuntimeException + css::uno::Sequence< OUString > getElementNames( ); + /// @throws css::uno::RuntimeException + bool hasByName( const OUString& aName ); + /// @throws css::uno::RuntimeException + static css::uno::Type const & getElementType( ); + /// @throws css::uno::RuntimeException + bool hasElements() const; + OUString const & GetEventName( GlobalEventId nID ) const; +}; + + +GlobalEventConfig_Impl::GlobalEventConfig_Impl() + : ConfigItem( "Office.Events/ApplicationEvents", ConfigItemMode::NONE ) +{ + // the supported event names + for (const GlobalEventId id : o3tl::enumrange<GlobalEventId>()) + m_supportedEvents[id] = OUString::createFromAscii( pEventAsciiNames[id] ); + + initBindingInfo(); + +/*TODO: Not used in the moment! see Notify() ... + // Enable notification mechanism of our baseclass. + // We need it to get information about changes outside these class on our used configuration keys! */ + Sequence<OUString> aNotifySeq { "Events" }; + EnableNotification( aNotifySeq, true ); +} + +// destructor + +GlobalEventConfig_Impl::~GlobalEventConfig_Impl() +{ + assert(!IsModified()); // should have been committed +} + +OUString const & GlobalEventConfig_Impl::GetEventName( GlobalEventId nIndex ) const +{ + return m_supportedEvents[nIndex]; +} + +// public method + +void GlobalEventConfig_Impl::Notify( const Sequence< OUString >& ) +{ + std::unique_lock aGuard( GetOwnStaticMutex() ); + + initBindingInfo(); +} + +// public method + +void GlobalEventConfig_Impl::ImplCommit() +{ + //DF need to check it this is correct?? + SAL_INFO("unotools", "In GlobalEventConfig_Impl::ImplCommit"); + // clear the existing nodes + ClearNodeSet( SETNODE_BINDINGS ); + OUString sNode; + //step through the list of events + for(const auto& rEntry : m_eventBindingHash) + { + //no point in writing out empty bindings! + if(rEntry.second.isEmpty() ) + continue; + sNode = SETNODE_BINDINGS PATHDELIMITER "BindingType['" + + rEntry.first + + "']" PATHDELIMITER PROPERTYNAME_BINDINGURL; + SAL_INFO("unotools", "writing binding for: " << sNode); + //write the data to the registry + SetSetProperties(SETNODE_BINDINGS,{ comphelper::makePropertyValue(sNode, rEntry.second) }); + } +} + +// private method + +void GlobalEventConfig_Impl::initBindingInfo() +{ + // Get ALL names of current existing list items in configuration! + const Sequence< OUString > lEventNames = GetNodeNames( SETNODE_BINDINGS, utl::ConfigNameFormat::LocalPath ); + + OUString aSetNode = SETNODE_BINDINGS PATHDELIMITER; + OUString aCommandKey = PATHDELIMITER PROPERTYNAME_BINDINGURL; + + // Expand all keys + Sequence< OUString > lMacros(1); + auto plMacros = lMacros.getArray(); + for (const auto& rEventName : lEventNames ) + { + plMacros[0] = aSetNode + rEventName + aCommandKey; + SAL_INFO("unotools", "reading binding for: " << lMacros[0]); + Sequence< Any > lValues = GetProperties( lMacros ); + if( lValues.hasElements() ) + { + OUString sMacroURL; + lValues[0] >>= sMacroURL; + sal_Int32 startIndex = rEventName.indexOf('\''); + sal_Int32 endIndex = rEventName.lastIndexOf('\''); + if( startIndex >=0 && endIndex > 0 ) + { + startIndex++; + OUString eventName = rEventName.copy(startIndex,endIndex-startIndex); + m_eventBindingHash[ eventName ] = sMacroURL; + } + } + } +} + +void GlobalEventConfig_Impl::replaceByName( const OUString& aName, const Any& aElement ) +{ + Sequence< beans::PropertyValue > props; + //DF should we prepopulate the hash with a list of valid event Names? + if( !( aElement >>= props ) ) + { + throw lang::IllegalArgumentException( OUString(), + Reference< XInterface > (), 2); + } + OUString macroURL; + for( const auto& rProp : std::as_const(props) ) + { + if ( rProp.Name == "Script" ) + rProp.Value >>= macroURL; + } + m_eventBindingHash[ aName ] = macroURL; + SetModified(); +} + +css::uno::Sequence < css::beans::PropertyValue > GlobalEventConfig_Impl::getByName( const OUString& aName ) +{ + static constexpr OUStringLiteral sEventType = u"EventType"; + static constexpr OUString sScript = u"Script"_ustr; + Sequence< beans::PropertyValue > props(2); + auto pProps = props.getArray(); + pProps[0].Name = sEventType; + pProps[0].Value <<= sScript; + pProps[1].Name = sScript; + EventBindingHash::const_iterator it = m_eventBindingHash.find( aName ); + if( it != m_eventBindingHash.end() ) + { + pProps[1].Value <<= it->second; + } + else + { + // not yet accessed - is it a supported name? + SupportedEventsVector::iterator pos = ::std::find( + m_supportedEvents.begin(), m_supportedEvents.end(), aName ); + if ( pos == m_supportedEvents.end() ) + throw container::NoSuchElementException( aName ); + + pProps[1].Value <<= OUString(); + } + return props; +} + +Sequence< OUString > GlobalEventConfig_Impl::getElementNames( ) +{ + return uno::Sequence< OUString >(m_supportedEvents.data(), SupportedEventsVector::size()); +} + +bool GlobalEventConfig_Impl::hasByName( const OUString& aName ) +{ + if ( m_eventBindingHash.find( aName ) != m_eventBindingHash.end() ) + return true; + + // never accessed before - is it supported in general? + SupportedEventsVector::iterator pos = ::std::find( + m_supportedEvents.begin(), m_supportedEvents.end(), aName ); + return pos != m_supportedEvents.end(); +} + +Type const & GlobalEventConfig_Impl::getElementType( ) +{ + //DF definitely not sure about this?? + return cppu::UnoType<Sequence<beans::PropertyValue>>::get(); +} + +bool GlobalEventConfig_Impl::hasElements() const +{ + return !m_eventBindingHash.empty(); +} + +// and now the wrapper + +//initialize static member +GlobalEventConfig_Impl* GlobalEventConfig::m_pImpl = nullptr; +sal_Int32 GlobalEventConfig::m_nRefCount = 0; + +GlobalEventConfig::GlobalEventConfig() +{ + // Global access, must be guarded (multithreading!). + std::unique_lock aGuard( GetOwnStaticMutex() ); + // Increase our refcount ... + ++m_nRefCount; + // ... and initialize our data container only if it not already exist! + if( m_pImpl == nullptr ) + { + m_pImpl = new GlobalEventConfig_Impl; + aGuard.unlock(); + ItemHolder1::holdConfigItem(EItem::EventConfig); + } +} + +GlobalEventConfig::~GlobalEventConfig() +{ + // Global access, must be guarded (multithreading!) + std::unique_lock aGuard( GetOwnStaticMutex() ); + // Decrease our refcount. + --m_nRefCount; + // If last instance was deleted ... + // we must destroy our static data container! + if( m_nRefCount <= 0 ) + { + delete m_pImpl; + m_pImpl = nullptr; + } +} + +Reference< container::XNameReplace > SAL_CALL GlobalEventConfig::getEvents() +{ + std::unique_lock aGuard( GetOwnStaticMutex() ); + Reference< container::XNameReplace > ret(this); + return ret; +} + +void SAL_CALL GlobalEventConfig::replaceByName( const OUString& aName, const Any& aElement ) +{ + std::unique_lock aGuard( GetOwnStaticMutex() ); + m_pImpl->replaceByName( aName, aElement ); +} +Any SAL_CALL GlobalEventConfig::getByName( const OUString& aName ) +{ + return Any(getByName2(aName)); +} +css::uno::Sequence < css::beans::PropertyValue > GlobalEventConfig::getByName2( const OUString& aName ) +{ + std::unique_lock aGuard( GetOwnStaticMutex() ); + return m_pImpl->getByName( aName ); +} +Sequence< OUString > SAL_CALL GlobalEventConfig::getElementNames( ) +{ + std::unique_lock aGuard( GetOwnStaticMutex() ); + return m_pImpl->getElementNames( ); +} +sal_Bool SAL_CALL GlobalEventConfig::hasByName( const OUString& aName ) +{ + std::unique_lock aGuard( GetOwnStaticMutex() ); + return m_pImpl->hasByName( aName ); +} +Type SAL_CALL GlobalEventConfig::getElementType( ) +{ + std::unique_lock aGuard( GetOwnStaticMutex() ); + return GlobalEventConfig_Impl::getElementType( ); +} +sal_Bool SAL_CALL GlobalEventConfig::hasElements( ) +{ + std::unique_lock aGuard( GetOwnStaticMutex() ); + return m_pImpl->hasElements( ); +} + +OUString GlobalEventConfig::GetEventName( GlobalEventId nIndex ) +{ + if (utl::ConfigManager::IsFuzzing()) + return OUString(); + static rtl::Reference<GlobalEventConfig> createImpl(new GlobalEventConfig); + return GlobalEventConfig::m_pImpl->GetEventName( nIndex ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/config/fltrcfg.cxx b/unotools/source/config/fltrcfg.cxx new file mode 100644 index 0000000000..21a540e404 --- /dev/null +++ b/unotools/source/config/fltrcfg.cxx @@ -0,0 +1,670 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <o3tl/any.hxx> +#include <o3tl/typed_flags_set.hxx> +#include <unotools/fltrcfg.hxx> +#include <tools/debug.hxx> +#include <osl/diagnose.h> + +#include <com/sun/star/uno/Sequence.hxx> + +using namespace utl; +using namespace com::sun::star::uno; + +namespace { + +enum class ConfigFlags { + NONE = 0x0000000, + WordCode = 0x0000001, + WordStorage = 0x0000002, + ExcelCode = 0x0000004, + ExcelStorage = 0x0000008, + PowerPointCode = 0x0000010, + PowerPointStorage = 0x0000020, + MathLoad = 0x0000100, + MathSave = 0x0000200, + WriterLoad = 0x0000400, + WriterSave = 0x0000800, + CalcLoad = 0x0001000, + CalcSave = 0x0002000, + ImpressLoad = 0x0004000, + ImpressSave = 0x0008000, + ExcelExecTbl = 0x0010000, + EnablePowerPointPreview = 0x0020000, + EnableExcelPreview = 0x0040000, + EnableWordPreview = 0x0080000, + UseEnhancedFields = 0x0100000, + WordWbctbl = 0x0200000, + SmartArtShapeLoad = 0x0400000, + CharBackgroundToHighlighting = 0x8000000, + CreateMSOLockFiles = 0x2000000, + VisioLoad = 0x4000000, +}; + +} + +namespace o3tl { + template<> struct typed_flags<ConfigFlags> : is_typed_flags<ConfigFlags, 0xe7fff3f> {}; +} + +namespace { + +class SvtAppFilterOptions_Impl : public utl::ConfigItem +{ +private: + bool bLoadVBA; + bool bSaveVBA; + +protected: + virtual void ImplCommit() override; + +public: + explicit SvtAppFilterOptions_Impl(const OUString& rRoot) : + utl::ConfigItem(rRoot), + bLoadVBA(false), + bSaveVBA(false) {} + virtual ~SvtAppFilterOptions_Impl() override; + virtual void Notify( const css::uno::Sequence<OUString>& aPropertyNames) override; + void Load(); + + bool IsLoad() const {return bLoadVBA;} + void SetLoad(bool bSet) + { + if(bSet != bLoadVBA) + SetModified(); + bLoadVBA = bSet; + } + bool IsSave() const {return bSaveVBA;} + void SetSave(bool bSet) + { + if(bSet != bSaveVBA) + SetModified(); + bSaveVBA = bSet; + } +}; + +} + +SvtAppFilterOptions_Impl::~SvtAppFilterOptions_Impl() +{ + assert(!IsModified()); // should have been committed +} + +void SvtAppFilterOptions_Impl::ImplCommit() +{ + PutProperties( + {"Load", "Save"}, {css::uno::Any(bLoadVBA), css::uno::Any(bSaveVBA)}); +} + +void SvtAppFilterOptions_Impl::Notify( const Sequence< OUString >& ) +{ + // no listeners supported yet +} + +void SvtAppFilterOptions_Impl::Load() +{ + Sequence<Any> aValues = GetProperties({ "Load", "Save" }); + const Any* pValues = aValues.getConstArray(); + + if(pValues[0].hasValue()) + bLoadVBA = *o3tl::doAccess<bool>(pValues[0]); + if(pValues[1].hasValue()) + bSaveVBA = *o3tl::doAccess<bool>(pValues[1]); +} + +namespace { + +class SvtWriterFilterOptions_Impl : public SvtAppFilterOptions_Impl +{ +private: + bool bLoadExecutable; + + virtual void ImplCommit() override; + +public: + explicit SvtWriterFilterOptions_Impl(const OUString& rRoot) : + SvtAppFilterOptions_Impl(rRoot), + bLoadExecutable(false) + {} + void Load(); + + bool IsLoadExecutable() const {return bLoadExecutable;} + void SetLoadExecutable(bool bSet) + { + if(bSet != bLoadExecutable) + SetModified(); + bLoadExecutable = bSet; + } +}; + +} + +void SvtWriterFilterOptions_Impl::ImplCommit() +{ + SvtAppFilterOptions_Impl::ImplCommit(); + + PutProperties({ "Executable" }, { Any(bLoadExecutable) }); +} + +void SvtWriterFilterOptions_Impl::Load() +{ + SvtAppFilterOptions_Impl::Load(); + + Sequence<OUString> aNames { "Executable" }; + + Sequence<Any> aValues = GetProperties(aNames); + const Any* pValues = aValues.getConstArray(); + if(pValues[0].hasValue()) + bLoadExecutable = *o3tl::doAccess<bool>(pValues[0]); +} + +namespace { + +class SvtCalcFilterOptions_Impl : public SvtAppFilterOptions_Impl +{ +private: + bool bLoadExecutable; + + virtual void ImplCommit() override; + +public: + explicit SvtCalcFilterOptions_Impl(const OUString& rRoot) : + SvtAppFilterOptions_Impl(rRoot), + bLoadExecutable(false) + {} + void Load(); + + bool IsLoadExecutable() const {return bLoadExecutable;} + void SetLoadExecutable(bool bSet) + { + if(bSet != bLoadExecutable) + SetModified(); + bLoadExecutable = bSet; + } +}; + +} + +void SvtCalcFilterOptions_Impl::ImplCommit() +{ + SvtAppFilterOptions_Impl::ImplCommit(); + + PutProperties({ "Executable" }, { Any(bLoadExecutable) }); +} + +void SvtCalcFilterOptions_Impl::Load() +{ + SvtAppFilterOptions_Impl::Load(); + + Sequence<OUString> aNames { "Executable" }; + + Sequence<Any> aValues = GetProperties(aNames); + const Any* pValues = aValues.getConstArray(); + if(pValues[0].hasValue()) + bLoadExecutable = *o3tl::doAccess<bool>(pValues[0]); +} + +struct SvtFilterOptions_Impl +{ + ConfigFlags nFlags; + SvtWriterFilterOptions_Impl aWriterCfg; + SvtCalcFilterOptions_Impl aCalcCfg; + SvtAppFilterOptions_Impl aImpressCfg; + + SvtFilterOptions_Impl() : + aWriterCfg("Office.Writer/Filter/Import/VBA"), + aCalcCfg("Office.Calc/Filter/Import/VBA"), + aImpressCfg("Office.Impress/Filter/Import/VBA") + { + nFlags = ConfigFlags::WordCode | + ConfigFlags::WordStorage | + ConfigFlags::ExcelCode | + ConfigFlags::ExcelStorage | + ConfigFlags::PowerPointCode | + ConfigFlags::PowerPointStorage | + ConfigFlags::MathLoad | + ConfigFlags::MathSave | + ConfigFlags::WriterLoad | + ConfigFlags::WriterSave | + ConfigFlags::CalcLoad | + ConfigFlags::CalcSave | + ConfigFlags::ImpressLoad | + ConfigFlags::ImpressSave | + ConfigFlags::UseEnhancedFields | + ConfigFlags::SmartArtShapeLoad | + ConfigFlags::CharBackgroundToHighlighting| + ConfigFlags::CreateMSOLockFiles; + Load(); + } + + void SetFlag( ConfigFlags nFlag, bool bSet ); + bool IsFlag( ConfigFlags nFlag ) const; + void Load() + { + aWriterCfg.Load(); + aCalcCfg.Load(); + aImpressCfg.Load(); + } +}; + +void SvtFilterOptions_Impl::SetFlag( ConfigFlags nFlag, bool bSet ) +{ + switch(nFlag) + { + case ConfigFlags::WordCode: aWriterCfg.SetLoad(bSet);break; + case ConfigFlags::WordStorage: aWriterCfg.SetSave(bSet);break; + case ConfigFlags::WordWbctbl: aWriterCfg.SetLoadExecutable(bSet);break; + case ConfigFlags::ExcelCode: aCalcCfg.SetLoad(bSet);break; + case ConfigFlags::ExcelStorage: aCalcCfg.SetSave(bSet);break; + case ConfigFlags::ExcelExecTbl: aCalcCfg.SetLoadExecutable(bSet);break; + case ConfigFlags::PowerPointCode: aImpressCfg.SetLoad(bSet);break; + case ConfigFlags::PowerPointStorage: aImpressCfg.SetSave(bSet);break; + default: + if( bSet ) + nFlags |= nFlag; + else + nFlags &= ~nFlag; + } +} + +bool SvtFilterOptions_Impl::IsFlag( ConfigFlags nFlag ) const +{ + bool bRet; + switch(nFlag) + { + case ConfigFlags::WordCode : bRet = aWriterCfg.IsLoad();break; + case ConfigFlags::WordStorage : bRet = aWriterCfg.IsSave();break; + case ConfigFlags::WordWbctbl : bRet = aWriterCfg.IsLoadExecutable();break; + case ConfigFlags::ExcelCode : bRet = aCalcCfg.IsLoad();break; + case ConfigFlags::ExcelStorage : bRet = aCalcCfg.IsSave();break; + case ConfigFlags::ExcelExecTbl : bRet = aCalcCfg.IsLoadExecutable();break; + case ConfigFlags::PowerPointCode : bRet = aImpressCfg.IsLoad();break; + case ConfigFlags::PowerPointStorage : bRet = aImpressCfg.IsSave();break; + default: + bRet = bool(nFlags & nFlag ); + } + return bRet; +} + +namespace { + +const Sequence<OUString>& GetPropertyNames() +{ + static Sequence<OUString> const aNames + { + "Import/MathTypeToMath", // 0 + "Import/WinWordToWriter", // 1 + "Import/PowerPointToImpress", // 2 + "Import/ExcelToCalc", // 3 + "Export/MathToMathType", // 4 + "Export/WriterToWinWord", // 5 + "Export/ImpressToPowerPoint", // 6 + "Export/CalcToExcel", // 7 + "Export/EnablePowerPointPreview", // 8 + "Export/EnableExcelPreview", // 9 + "Export/EnableWordPreview", // 10 + "Import/ImportWWFieldsAsEnhancedFields", // 11 + "Import/SmartArtToShapes", // 12 + "Export/CharBackgroundToHighlighting", // 13 + "Import/CreateMSOLockFiles", // 14 + "Import/VisioToDraw" // 15 + }; + return aNames; +} + +} + +SvtFilterOptions::SvtFilterOptions() : + ConfigItem( "Office.Common/Filter/Microsoft" ), + pImpl(new SvtFilterOptions_Impl) +{ + EnableNotification(GetPropertyNames()); + Load(); +} + +SvtFilterOptions::~SvtFilterOptions() +{ +} + +static ConfigFlags lcl_GetFlag(sal_Int32 nProp) +{ + ConfigFlags nFlag = ConfigFlags::NONE; + switch(nProp) + { + case 0: nFlag = ConfigFlags::MathLoad; break; + case 1: nFlag = ConfigFlags::WriterLoad; break; + case 2: nFlag = ConfigFlags::ImpressLoad; break; + case 3: nFlag = ConfigFlags::CalcLoad; break; + case 4: nFlag = ConfigFlags::MathSave; break; + case 5: nFlag = ConfigFlags::WriterSave; break; + case 6: nFlag = ConfigFlags::ImpressSave; break; + case 7: nFlag = ConfigFlags::CalcSave; break; + case 8: nFlag = ConfigFlags::EnablePowerPointPreview; break; + case 9: nFlag = ConfigFlags::EnableExcelPreview; break; + case 10: nFlag = ConfigFlags::EnableWordPreview; break; + case 11: nFlag = ConfigFlags::UseEnhancedFields; break; + case 12: nFlag = ConfigFlags::SmartArtShapeLoad; break; + case 13: nFlag = ConfigFlags::CharBackgroundToHighlighting; break; + case 14: nFlag = ConfigFlags::CreateMSOLockFiles; break; + case 15: + nFlag = ConfigFlags::VisioLoad; + break; + + default: OSL_FAIL("illegal value"); + } + return nFlag; +} + +void SvtFilterOptions::Notify( const Sequence<OUString>& ) +{ + Load(); +} + +void SvtFilterOptions::ImplCommit() +{ + const Sequence<OUString>& aNames = GetPropertyNames(); + Sequence<Any> aValues(aNames.getLength()); + Any* pValues = aValues.getArray(); + + for(int nProp = 0; nProp < aNames.getLength(); nProp++) + { + ConfigFlags nFlag = lcl_GetFlag(nProp); + pValues[nProp] <<= pImpl->IsFlag(nFlag); + + } + PutProperties(aNames, aValues); +} + +void SvtFilterOptions::Load() +{ + pImpl->Load(); + const Sequence<OUString>& rNames = GetPropertyNames(); + Sequence<Any> aValues = GetProperties(rNames); + const Any* pValues = aValues.getConstArray(); + DBG_ASSERT(aValues.getLength() == rNames.getLength(), "GetProperties failed"); + if(aValues.getLength() == rNames.getLength()) + { + for(int nProp = 0; nProp < rNames.getLength(); nProp++) + { + if(pValues[nProp].hasValue()) + { + bool bVal = *o3tl::doAccess<bool>(pValues[nProp]); + ConfigFlags nFlag = lcl_GetFlag(nProp); + pImpl->SetFlag( nFlag, bVal); + } + } + } +} + +void SvtFilterOptions::SetLoadWordBasicCode( bool bFlag ) +{ + pImpl->SetFlag( ConfigFlags::WordCode, bFlag ); + SetModified(); +} + +bool SvtFilterOptions::IsLoadWordBasicCode() const +{ + return pImpl->IsFlag( ConfigFlags::WordCode ); +} + +void SvtFilterOptions::SetLoadWordBasicExecutable( bool bFlag ) +{ + pImpl->SetFlag( ConfigFlags::WordWbctbl, bFlag ); + SetModified(); +} + +bool SvtFilterOptions::IsLoadWordBasicExecutable() const +{ + return pImpl->IsFlag( ConfigFlags::WordWbctbl ); +} + +void SvtFilterOptions::SetLoadWordBasicStorage( bool bFlag ) +{ + pImpl->SetFlag( ConfigFlags::WordStorage, bFlag ); + SetModified(); +} + +bool SvtFilterOptions::IsLoadWordBasicStorage() const +{ + return pImpl->IsFlag( ConfigFlags::WordStorage ); +} + +void SvtFilterOptions::SetLoadExcelBasicCode( bool bFlag ) +{ + pImpl->SetFlag( ConfigFlags::ExcelCode, bFlag ); + SetModified(); +} + +bool SvtFilterOptions::IsLoadExcelBasicCode() const +{ + return pImpl->IsFlag( ConfigFlags::ExcelCode ); +} + +void SvtFilterOptions::SetLoadExcelBasicExecutable( bool bFlag ) +{ + pImpl->SetFlag( ConfigFlags::ExcelExecTbl, bFlag ); + SetModified(); +} + +bool SvtFilterOptions::IsLoadExcelBasicExecutable() const +{ + return pImpl->IsFlag( ConfigFlags::ExcelExecTbl ); +} + +void SvtFilterOptions::SetLoadExcelBasicStorage( bool bFlag ) +{ + pImpl->SetFlag( ConfigFlags::ExcelStorage, bFlag ); + SetModified(); +} + +bool SvtFilterOptions::IsLoadExcelBasicStorage() const +{ + return pImpl->IsFlag( ConfigFlags::ExcelStorage ); +} + +void SvtFilterOptions::SetLoadPPointBasicCode( bool bFlag ) +{ + pImpl->SetFlag( ConfigFlags::PowerPointCode, bFlag ); + SetModified(); +} + +bool SvtFilterOptions::IsLoadPPointBasicCode() const +{ + return pImpl->IsFlag( ConfigFlags::PowerPointCode ); +} + +void SvtFilterOptions::SetLoadPPointBasicStorage( bool bFlag ) +{ + pImpl->SetFlag( ConfigFlags::PowerPointStorage, bFlag ); + SetModified(); +} + +bool SvtFilterOptions::IsLoadPPointBasicStorage() const +{ + return pImpl->IsFlag( ConfigFlags::PowerPointStorage ); +} + +bool SvtFilterOptions::IsMathType2Math() const +{ + return pImpl->IsFlag( ConfigFlags::MathLoad ); +} + +void SvtFilterOptions::SetMathType2Math( bool bFlag ) +{ + pImpl->SetFlag( ConfigFlags::MathLoad, bFlag ); + SetModified(); +} + +bool SvtFilterOptions::IsMath2MathType() const +{ + return pImpl->IsFlag( ConfigFlags::MathSave ); +} + +void SvtFilterOptions::SetMath2MathType( bool bFlag ) +{ + pImpl->SetFlag( ConfigFlags::MathSave, bFlag ); + SetModified(); +} + +bool SvtFilterOptions::IsWinWord2Writer() const +{ + return pImpl->IsFlag( ConfigFlags::WriterLoad ); +} + +void SvtFilterOptions::SetWinWord2Writer( bool bFlag ) +{ + pImpl->SetFlag( ConfigFlags::WriterLoad, bFlag ); + SetModified(); +} + +bool SvtFilterOptions::IsWriter2WinWord() const +{ + return pImpl->IsFlag( ConfigFlags::WriterSave ); +} + +void SvtFilterOptions::SetWriter2WinWord( bool bFlag ) +{ + pImpl->SetFlag( ConfigFlags::WriterSave, bFlag ); + SetModified(); +} + +bool SvtFilterOptions::IsUseEnhancedFields() const +{ + return pImpl->IsFlag( ConfigFlags::UseEnhancedFields ); +} + +bool SvtFilterOptions::IsExcel2Calc() const +{ + return pImpl->IsFlag( ConfigFlags::CalcLoad ); +} + +void SvtFilterOptions::SetExcel2Calc( bool bFlag ) +{ + pImpl->SetFlag( ConfigFlags::CalcLoad, bFlag ); + SetModified(); +} + +bool SvtFilterOptions::IsCalc2Excel() const +{ + return pImpl->IsFlag( ConfigFlags::CalcSave ); +} + +void SvtFilterOptions::SetCalc2Excel( bool bFlag ) +{ + pImpl->SetFlag( ConfigFlags::CalcSave, bFlag ); + SetModified(); +} + +bool SvtFilterOptions::IsPowerPoint2Impress() const +{ + return pImpl->IsFlag( ConfigFlags::ImpressLoad ); +} + +void SvtFilterOptions::SetPowerPoint2Impress( bool bFlag ) +{ + pImpl->SetFlag( ConfigFlags::ImpressLoad, bFlag ); + SetModified(); +} + +bool SvtFilterOptions::IsImpress2PowerPoint() const +{ + return pImpl->IsFlag( ConfigFlags::ImpressSave ); +} + +void SvtFilterOptions::SetImpress2PowerPoint( bool bFlag ) +{ + pImpl->SetFlag( ConfigFlags::ImpressSave, bFlag ); + SetModified(); +} + +bool SvtFilterOptions::IsSmartArt2Shape() const +{ + return pImpl->IsFlag( ConfigFlags::SmartArtShapeLoad ); +} + +void SvtFilterOptions::SetSmartArt2Shape( bool bFlag ) +{ + pImpl->SetFlag( ConfigFlags::SmartArtShapeLoad, bFlag ); + SetModified(); +} + +bool SvtFilterOptions::IsVisio2Draw() const { return pImpl->IsFlag(ConfigFlags::VisioLoad); } + +void SvtFilterOptions::SetVisio2Draw(bool bFlag) +{ + pImpl->SetFlag(ConfigFlags::VisioLoad, bFlag); + SetModified(); +} + +SvtFilterOptions& SvtFilterOptions::Get() +{ + static SvtFilterOptions aOptions; + return aOptions; +} + +bool SvtFilterOptions::IsEnablePPTPreview() const +{ + return pImpl->IsFlag( ConfigFlags::EnablePowerPointPreview ); +} + +bool SvtFilterOptions::IsEnableCalcPreview() const +{ + return pImpl->IsFlag( ConfigFlags::EnableExcelPreview ); +} + +bool SvtFilterOptions::IsEnableWordPreview() const +{ + return pImpl->IsFlag( ConfigFlags::EnableWordPreview ); +} + +bool SvtFilterOptions::IsCharBackground2Highlighting() const +{ + return pImpl->IsFlag( ConfigFlags::CharBackgroundToHighlighting ); +} + +bool SvtFilterOptions::IsCharBackground2Shading() const +{ + return !pImpl->IsFlag( ConfigFlags::CharBackgroundToHighlighting ); +} + +void SvtFilterOptions::SetCharBackground2Highlighting() +{ + pImpl->SetFlag( ConfigFlags::CharBackgroundToHighlighting, true ); + SetModified(); +} + +void SvtFilterOptions::SetCharBackground2Shading() +{ + pImpl->SetFlag( ConfigFlags::CharBackgroundToHighlighting, false ); + SetModified(); +} + +bool SvtFilterOptions::IsMSOLockFileCreationIsEnabled() const +{ + return pImpl->IsFlag( ConfigFlags::CreateMSOLockFiles ); +} + +void SvtFilterOptions::EnableMSOLockFileCreation(bool bEnable) +{ + pImpl->SetFlag( ConfigFlags::CreateMSOLockFiles, bEnable ); + SetModified(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/config/fontcfg.cxx b/unotools/source/config/fontcfg.cxx new file mode 100644 index 0000000000..26ceda82d5 --- /dev/null +++ b/unotools/source/config/fontcfg.cxx @@ -0,0 +1,1076 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <i18nlangtag/mslangid.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <o3tl/any.hxx> +#include <unotools/configmgr.hxx> +#include <unotools/fontcfg.hxx> +#include <unotools/fontdefs.hxx> +#include <comphelper/processfactory.hxx> +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/configuration/theDefaultProvider.hpp> +#include <com/sun/star/container/XNameAccess.hpp> +#include <comphelper/propertysequence.hxx> +#include <unotools/syslocale.hxx> +#include <rtl/ustrbuf.hxx> +#include <osl/diagnose.h> +#include <sal/log.hxx> + +#include <string.h> +#include <algorithm> + +using namespace utl; +using namespace com::sun::star::uno; +using namespace com::sun::star::lang; +using namespace com::sun::star::beans; +using namespace com::sun::star::container; +using namespace com::sun::star::configuration; + +/* + * DefaultFontConfiguration + */ + +static const char* getKeyType( DefaultFontType nKeyType ) +{ + switch( nKeyType ) + { + case DefaultFontType::CJK_DISPLAY: return "CJK_DISPLAY"; + case DefaultFontType::CJK_HEADING: return "CJK_HEADING"; + case DefaultFontType::CJK_PRESENTATION: return "CJK_PRESENTATION"; + case DefaultFontType::CJK_SPREADSHEET: return "CJK_SPREADSHEET"; + case DefaultFontType::CJK_TEXT: return "CJK_TEXT"; + case DefaultFontType::CTL_DISPLAY: return "CTL_DISPLAY"; + case DefaultFontType::CTL_HEADING: return "CTL_HEADING"; + case DefaultFontType::CTL_PRESENTATION: return "CTL_PRESENTATION"; + case DefaultFontType::CTL_SPREADSHEET: return "CTL_SPREADSHEET"; + case DefaultFontType::CTL_TEXT: return "CTL_TEXT"; + case DefaultFontType::FIXED: return "FIXED"; + case DefaultFontType::LATIN_DISPLAY: return "LATIN_DISPLAY"; + case DefaultFontType::LATIN_FIXED: return "LATIN_FIXED"; + case DefaultFontType::LATIN_HEADING: return "LATIN_HEADING"; + case DefaultFontType::LATIN_PRESENTATION: return "LATIN_PRESENTATION"; + case DefaultFontType::LATIN_SPREADSHEET: return "LATIN_SPREADSHEET"; + case DefaultFontType::LATIN_TEXT: return "LATIN_TEXT"; + case DefaultFontType::SANS: return "SANS"; + case DefaultFontType::SANS_UNICODE: return "SANS_UNICODE"; + case DefaultFontType::SERIF: return "SERIF"; + case DefaultFontType::SYMBOL: return "SYMBOL"; + case DefaultFontType::UI_FIXED: return "UI_FIXED"; + case DefaultFontType::UI_SANS: return "UI_SANS"; + default: + OSL_FAIL( "unmatched type" ); + return ""; + } +} + +DefaultFontConfiguration& DefaultFontConfiguration::get() +{ + static DefaultFontConfiguration theDefaultFontConfiguration; + return theDefaultFontConfiguration; +} + +DefaultFontConfiguration::DefaultFontConfiguration() +{ + if (utl::ConfigManager::IsFuzzing()) + return; + // create configuration hierarchical access name + try + { + // get service provider + m_xConfigProvider = theDefaultProvider::get(comphelper::getProcessComponentContext()); + Sequence<Any> aArgs(comphelper::InitAnyPropertySequence( + { + {"nodepath", Any(OUString( "/org.openoffice.VCL/DefaultFonts" ))} + })); + m_xConfigAccess = + Reference< XNameAccess >( + m_xConfigProvider->createInstanceWithArguments( "com.sun.star.configuration.ConfigurationAccess", + aArgs ), + UNO_QUERY ); + if( m_xConfigAccess.is() ) + { + const Sequence< OUString > aLocales = m_xConfigAccess->getElementNames(); + // fill config hash with empty interfaces + for( const OUString& rLocaleString : aLocales ) + { + // Feed through LanguageTag for casing. + OUString aLoc( LanguageTag( rLocaleString, true).getBcp47( false)); + m_aConfig[ aLoc ] = LocaleAccess(); + m_aConfig[ aLoc ].aConfigLocaleString = rLocaleString; + } + } + } + catch (const Exception&) + { + // configuration is awry + m_xConfigProvider.clear(); + m_xConfigAccess.clear(); + } + SAL_INFO("unotools.config", "config provider: " << m_xConfigProvider.is() + << ", config access: " << m_xConfigAccess.is()); +} + +DefaultFontConfiguration::~DefaultFontConfiguration() +{ + // release all nodes + m_aConfig.clear(); + // release top node + m_xConfigAccess.clear(); + // release config provider + m_xConfigProvider.clear(); +} + +OUString DefaultFontConfiguration::tryLocale( const OUString& rBcp47, const OUString& rType ) const +{ + OUString aRet; + + std::unordered_map< OUString, LocaleAccess >::const_iterator it = m_aConfig.find( rBcp47 ); + if( it != m_aConfig.end() ) + { + if( !it->second.xAccess.is() ) + { + try + { + Reference< XNameAccess > xNode; + if ( m_xConfigAccess->hasByName( it->second.aConfigLocaleString ) ) + { + Any aAny = m_xConfigAccess->getByName( it->second.aConfigLocaleString ); + if( aAny >>= xNode ) + it->second.xAccess = xNode; + } + } + catch (const NoSuchElementException&) + { + } + catch (const WrappedTargetException&) + { + } + } + if( it->second.xAccess.is() ) + { + try + { + if ( it->second.xAccess->hasByName( rType ) ) + { + Any aAny = it->second.xAccess->getByName( rType ); + aAny >>= aRet; + } + } + catch (const NoSuchElementException&) + { + } + catch (const WrappedTargetException&) + { + } + } + } + + return aRet; +} + +OUString DefaultFontConfiguration::getDefaultFont( const LanguageTag& rLanguageTag, DefaultFontType nType ) const +{ + OUString aType = OUString::createFromAscii( getKeyType( nType ) ); + // Try the simple cases first without constructing fallbacks. + OUString aRet = tryLocale( rLanguageTag.getBcp47(), aType ); + if (aRet.isEmpty()) + { + if (rLanguageTag.isIsoLocale()) + { + if (!rLanguageTag.getCountry().isEmpty()) + { + aRet = tryLocale( rLanguageTag.getLanguage(), aType ); + } + } + else + { + ::std::vector< OUString > aFallbacks( rLanguageTag.getFallbackStrings( false)); + for (const auto& rFallback : aFallbacks) + { + aRet = tryLocale( rFallback, aType ); + if (!aRet.isEmpty()) + break; + } + } + } + if( aRet.isEmpty() ) + { + aRet = tryLocale( "en", aType ); + } + return aRet; +} + +OUString DefaultFontConfiguration::getUserInterfaceFont( const LanguageTag& rLanguageTag ) const +{ + LanguageTag aLanguageTag( rLanguageTag); + if( aLanguageTag.isSystemLocale() ) + aLanguageTag = SvtSysLocale().GetUILanguageTag(); + + OUString aUIFont = getDefaultFont( aLanguageTag, DefaultFontType::UI_SANS ); + + if( !aUIFont.isEmpty() ) + return aUIFont; + + // fallback mechanism (either no configuration or no entry in configuration + + static constexpr OUStringLiteral FALLBACKFONT_UI_SANS = u"Andale Sans UI;Albany;Albany AMT;Tahoma;Arial Unicode MS;Arial;Nimbus Sans L;Bitstream Vera Sans;gnu-unifont;Interface User;Geneva;WarpSans;Dialog;Swiss;Lucida;Helvetica;Charcoal;Chicago;MS Sans Serif;Helv;Times;Times New Roman;Interface System"; + static constexpr OUStringLiteral FALLBACKFONT_UI_SANS_LATIN2 = u"Andale Sans UI;Albany;Albany AMT;Tahoma;Arial Unicode MS;Arial;Nimbus Sans L;Luxi Sans;Bitstream Vera Sans;Interface User;Geneva;WarpSans;Dialog;Swiss;Lucida;Helvetica;Charcoal;Chicago;MS Sans Serif;Helv;Times;Times New Roman;Interface System"; + static constexpr OUStringLiteral FALLBACKFONT_UI_SANS_ARABIC = u"Tahoma;Traditional Arabic;Simplified Arabic;Lucidasans;Lucida Sans;Supplement;Andale Sans UI;clearlyU;Interface User;Arial Unicode MS;Lucida Sans Unicode;WarpSans;Geneva;MS Sans Serif;Helv;Dialog;Albany;Lucida;Helvetica;Charcoal;Chicago;Arial;Helmet;Interface System;Sans Serif"; + static constexpr OUStringLiteral FALLBACKFONT_UI_SANS_THAI = u"OONaksit;Tahoma;Lucidasans;Arial Unicode MS"; + static constexpr OUStringLiteral FALLBACKFONT_UI_SANS_KOREAN = u"Noto Sans KR;Noto Sans CJK KR;Noto Serif KR;Noto Serif CJK KR;Source Han Sans KR;NanumGothic;NanumBarunGothic;NanumBarunGothic YetHangul;KoPubWorld Dotum;Malgun Gothic;Apple SD Gothic Neo;Dotum;DotumChe;Gulim;GulimChe;Batang;BatangChe;Apple Gothic;UnDotum;Baekmuk Gulim;Arial Unicode MS;Lucida Sans Unicode;gnu-unifont;Andale Sans UI"; + static constexpr OUStringLiteral FALLBACKFONT_UI_SANS_JAPANESE = u"Noto Sans CJK JP;Noto Sans JP;Source Han Sans;Source Han Sans JP;Yu Gothic UI;Yu Gothic;YuGothic;Hiragino Sans;Hiragino Kaku Gothic ProN;Hiragino Kaku Gothic Pro;Hiragino Kaku Gothic StdN;Meiryo UI;Meiryo;IPAexGothic;IPAPGothic;IPAGothic;MS UI Gothic;MS PGothic;MS Gothic;Osaka;Unifont;gnu-unifont;Arial Unicode MS;Interface System"; + static constexpr OUStringLiteral FALLBACKFONT_UI_SANS_CHINSIM = u"Andale Sans UI;Arial Unicode MS;ZYSong18030;AR PL SungtiL GB;AR PL KaitiM GB;SimSun;Lucida Sans Unicode;Fangsong;Hei;Song;Kai;Ming;gnu-unifont;Interface User;"; + static constexpr OUStringLiteral FALLBACKFONT_UI_SANS_CHINTRD = u"Andale Sans UI;Arial Unicode MS;AR PL Mingti2L Big5;AR PL KaitiM Big5;Kai;PMingLiU;MingLiU;Ming;Lucida Sans Unicode;gnu-unifont;Interface User;"; + + const OUString aLanguage( aLanguageTag.getLanguage()); + + // optimize font list for some locales, as long as Andale Sans UI does not support them + if( aLanguage == "ar" || aLanguage == "he" || aLanguage == "iw" ) + { + return FALLBACKFONT_UI_SANS_ARABIC; + } + else if ( aLanguage == "th" ) + { + return FALLBACKFONT_UI_SANS_THAI; + } + else if ( aLanguage == "ko" ) + { + return FALLBACKFONT_UI_SANS_KOREAN; + } + else if ( aLanguage == "ja" ) + { + return FALLBACKFONT_UI_SANS_JAPANESE; + } + else if( aLanguage == "cs" || + aLanguage == "hu" || + aLanguage == "pl" || + aLanguage == "ro" || + aLanguage == "rm" || + aLanguage == "hr" || + aLanguage == "sk" || + aLanguage == "sl" || + aLanguage == "sb") + { + return FALLBACKFONT_UI_SANS_LATIN2; + } + else + { + const Locale& aLocale( aLanguageTag.getLocale()); + if (MsLangId::isTraditionalChinese(aLocale)) + return FALLBACKFONT_UI_SANS_CHINTRD; + else if (MsLangId::isSimplifiedChinese(aLocale)) + return FALLBACKFONT_UI_SANS_CHINSIM; + } + + return FALLBACKFONT_UI_SANS; +} + +/* + * FontSubstConfigItem::get + */ + +FontSubstConfiguration& FontSubstConfiguration::get() +{ + static FontSubstConfiguration theFontSubstConfiguration; + return theFontSubstConfiguration; +} + +/* + * FontSubstConfigItem::FontSubstConfigItem + */ + +FontSubstConfiguration::FontSubstConfiguration() : + maSubstHash( 300 ), + maLanguageTag("en") +{ + if (utl::ConfigManager::IsFuzzing()) + return; + try + { + // get service provider + Reference< XComponentContext > xContext( comphelper::getProcessComponentContext() ); + // create configuration hierarchical access name + m_xConfigProvider = theDefaultProvider::get( xContext ); + Sequence<Any> aArgs(comphelper::InitAnyPropertySequence( + { + {"nodepath", Any(OUString( "/org.openoffice.VCL/FontSubstitutions" ))} + })); + m_xConfigAccess = + Reference< XNameAccess >( + m_xConfigProvider->createInstanceWithArguments( "com.sun.star.configuration.ConfigurationAccess", + aArgs ), + UNO_QUERY ); + if( m_xConfigAccess.is() ) + { + const Sequence< OUString > aLocales = m_xConfigAccess->getElementNames(); + // fill config hash with empty interfaces + for( const OUString& rLocaleString : aLocales ) + { + // Feed through LanguageTag for casing. + OUString aLoc( LanguageTag( rLocaleString, true).getBcp47( false)); + m_aSubst[ aLoc ] = LocaleSubst(); + m_aSubst[ aLoc ].aConfigLocaleString = rLocaleString; + } + } + } + catch (const Exception&) + { + // configuration is awry + m_xConfigProvider.clear(); + m_xConfigAccess.clear(); + } + SAL_INFO("unotools.config", "config provider: " << m_xConfigProvider.is() + << ", config access: " << m_xConfigAccess.is()); + + if( maLanguageTag.isSystemLocale() ) + maLanguageTag = SvtSysLocale().GetUILanguageTag(); +} + +/* + * FontSubstConfigItem::~FontSubstConfigItem + */ + +FontSubstConfiguration::~FontSubstConfiguration() +{ + // release config access + m_xConfigAccess.clear(); + // release config provider + m_xConfigProvider.clear(); +} + +/* + * FontSubstConfigItem::getMapName + */ + +const char* const aImplKillLeadingList[] = +{ + "microsoft", + "monotype", + "linotype", + "baekmuk", + "adobe", + "nimbus", + "zycjk", + "itc", + "sun", + "amt", + "ms", + "mt", + "cg", + "hg", + "fz", + "ipa", + "sazanami", + "kochi", + nullptr +}; + +const char* const aImplKillTrailingList[] = +{ + "microsoft", + "monotype", + "linotype", + "adobe", + "nimbus", + "itc", + "sun", + "amt", + "ms", + "mt", + "clm", + // Scripts, for compatibility with older versions + "we", + "cyr", + "tur", + "wt", + "greek", + "wl", + // CJK extensions + "gb", + "big5", + "pro", + "z01", + "z02", + "z03", + "z13", + "b01", + "w3x12", + // Old Printer Fontnames + "5cpi", + "6cpi", + "7cpi", + "8cpi", + "9cpi", + "10cpi", + "11cpi", + "12cpi", + "13cpi", + "14cpi", + "15cpi", + "16cpi", + "18cpi", + "24cpi", + "scale", + "pc", + nullptr +}; + +const char* const aImplKillTrailingWithExceptionsList[] = +{ + "ce", "monospace", "oldface", nullptr, + "ps", "caps", nullptr, + nullptr +}; + +namespace { + +struct ImplFontAttrWeightSearchData +{ + const char* mpStr; + FontWeight meWeight; +}; + +} + +ImplFontAttrWeightSearchData const aImplWeightAttrSearchList[] = +{ +// the attribute names are ordered by "first match wins" +// e.g. "semilight" should wins over "semi" +{ "extrablack", WEIGHT_BLACK }, +{ "ultrablack", WEIGHT_BLACK }, +{ "ultrabold", WEIGHT_ULTRABOLD }, +{ "semibold", WEIGHT_SEMIBOLD }, +{ "semilight", WEIGHT_SEMILIGHT }, +{ "semi", WEIGHT_SEMIBOLD }, +{ "demi", WEIGHT_SEMIBOLD }, +{ "black", WEIGHT_BLACK }, +{ "bold", WEIGHT_BOLD }, +{ "heavy", WEIGHT_BLACK }, +{ "ultralight", WEIGHT_ULTRALIGHT }, +{ "light", WEIGHT_LIGHT }, +{ "medium", WEIGHT_MEDIUM }, +{ nullptr, WEIGHT_DONTKNOW }, +}; + +namespace { + +struct ImplFontAttrWidthSearchData +{ + const char* mpStr; + FontWidth meWidth; +}; + +} + +ImplFontAttrWidthSearchData const aImplWidthAttrSearchList[] = +{ +{ "narrow", WIDTH_CONDENSED }, +{ "semicondensed", WIDTH_SEMI_CONDENSED }, +{ "ultracondensed", WIDTH_ULTRA_CONDENSED }, +{ "semiexpanded", WIDTH_SEMI_EXPANDED }, +{ "ultraexpanded", WIDTH_ULTRA_EXPANDED }, +{ "expanded", WIDTH_EXPANDED }, +{ "wide", WIDTH_ULTRA_EXPANDED }, +{ "condensed", WIDTH_CONDENSED }, +{ "cond", WIDTH_CONDENSED }, +{ "cn", WIDTH_CONDENSED }, +{ nullptr, WIDTH_DONTKNOW }, +}; + +namespace { + +struct ImplFontAttrTypeSearchData +{ + const char* mpStr; + ImplFontAttrs mnType; +}; + +} + +ImplFontAttrTypeSearchData const aImplTypeAttrSearchList[] = +{ +{ "monotype", ImplFontAttrs::None }, +{ "linotype", ImplFontAttrs::None }, +{ "titling", ImplFontAttrs::Titling }, +{ "captitals", ImplFontAttrs::Capitals }, +{ "captital", ImplFontAttrs::Capitals }, +{ "caps", ImplFontAttrs::Capitals }, +{ "italic", ImplFontAttrs::Italic }, +{ "oblique", ImplFontAttrs::Italic }, +{ "rounded", ImplFontAttrs::Rounded }, +{ "outline", ImplFontAttrs::Outline }, +{ "shadow", ImplFontAttrs::Shadow }, +{ "handwriting", ImplFontAttrs::Handwriting | ImplFontAttrs::Script }, +{ "hand", ImplFontAttrs::Handwriting | ImplFontAttrs::Script }, +{ "signet", ImplFontAttrs::Handwriting | ImplFontAttrs::Script }, +{ "script", ImplFontAttrs::BrushScript | ImplFontAttrs::Script }, +{ "calligraphy", ImplFontAttrs::Chancery | ImplFontAttrs::Script }, +{ "chancery", ImplFontAttrs::Chancery | ImplFontAttrs::Script }, +{ "corsiva", ImplFontAttrs::Chancery | ImplFontAttrs::Script }, +{ "gothic", ImplFontAttrs::SansSerif | ImplFontAttrs::Gothic }, +{ "schoolbook", ImplFontAttrs::Serif | ImplFontAttrs::Schoolbook }, +{ "schlbk", ImplFontAttrs::Serif | ImplFontAttrs::Schoolbook }, +{ "typewriter", ImplFontAttrs::Typewriter | ImplFontAttrs::Fixed }, +{ "lineprinter", ImplFontAttrs::Typewriter | ImplFontAttrs::Fixed }, +{ "monospaced", ImplFontAttrs::Fixed }, +{ "monospace", ImplFontAttrs::Fixed }, +{ "mono", ImplFontAttrs::Fixed }, +{ "fixed", ImplFontAttrs::Fixed }, +{ "sansserif", ImplFontAttrs::SansSerif }, +{ "sans", ImplFontAttrs::SansSerif }, +{ "swiss", ImplFontAttrs::SansSerif }, +{ "serif", ImplFontAttrs::Serif }, +{ "bright", ImplFontAttrs::Serif }, +{ "symbols", ImplFontAttrs::Symbol }, +{ "symbol", ImplFontAttrs::Symbol }, +{ "dingbats", ImplFontAttrs::Symbol }, +{ "dings", ImplFontAttrs::Symbol }, +{ "ding", ImplFontAttrs::Symbol }, +{ "bats", ImplFontAttrs::Symbol }, +{ "math", ImplFontAttrs::Symbol }, +{ "oldstyle", ImplFontAttrs::OtherStyle }, +{ "oldface", ImplFontAttrs::OtherStyle }, +{ "old", ImplFontAttrs::OtherStyle }, +{ "new", ImplFontAttrs::None }, +{ "modern", ImplFontAttrs::None }, +{ "lucida", ImplFontAttrs::None }, +{ "regular", ImplFontAttrs::None }, +{ "extended", ImplFontAttrs::None }, +{ "extra", ImplFontAttrs::OtherStyle }, +{ "ext", ImplFontAttrs::None }, +{ "scalable", ImplFontAttrs::None }, +{ "scale", ImplFontAttrs::None }, +{ "nimbus", ImplFontAttrs::None }, +{ "adobe", ImplFontAttrs::None }, +{ "itc", ImplFontAttrs::None }, +{ "amt", ImplFontAttrs::None }, +{ "mt", ImplFontAttrs::None }, +{ "ms", ImplFontAttrs::None }, +{ "cpi", ImplFontAttrs::None }, +{ "no", ImplFontAttrs::None }, +{ nullptr, ImplFontAttrs::None }, +}; + +static bool ImplKillLeading( OUString& rName, const char* const* ppStr ) +{ + for(; *ppStr; ++ppStr ) + { + const char* pStr = *ppStr; + const sal_Unicode* pNameStr = rName.getStr(); + while ( (*pNameStr == static_cast<sal_Unicode>(static_cast<unsigned char>(*pStr))) && *pStr ) + { + pNameStr++; + pStr++; + } + if ( !*pStr ) + { + sal_Int32 nLen = static_cast<sal_Int32>(pNameStr - rName.getStr()); + rName = rName.copy(nLen); + return true; + } + } + + // special case for Baekmuk + // TODO: allow non-ASCII KillLeading list + const sal_Unicode* pNameStr = rName.getStr(); + if( (pNameStr[0]==0xBC31) && (pNameStr[1]==0xBC35) ) + { + sal_Int32 nLen = (pNameStr[2]==0x0020) ? 3 : 2; + rName = rName.copy(nLen); + return true; + } + + return false; +} + +static sal_Int32 ImplIsTrailing( std::u16string_view rName, const char* pStr ) +{ + size_t nStrLen = strlen( pStr ); + if( nStrLen >= rName.size() ) + return 0; + + const sal_Unicode* pEndName = rName.data() + rName.size(); + const sal_Unicode* pNameStr = pEndName - nStrLen; + do if( *(pNameStr++) != *(pStr++) ) + return 0; + while( *pStr ); + + return nStrLen; +} + +static bool ImplKillTrailing( OUString& rName, const char* const* ppStr ) +{ + for(; *ppStr; ++ppStr ) + { + sal_Int32 nTrailLen = ImplIsTrailing( rName, *ppStr ); + if( nTrailLen ) + { + rName = rName.copy(0, rName.getLength() - nTrailLen ); + return true; + } + } + + return false; +} + +static bool ImplKillTrailingWithExceptions( OUString& rName, const char* const* ppStr ) +{ + for(; *ppStr; ++ppStr ) + { + sal_Int32 nTrailLen = ImplIsTrailing( rName, *ppStr ); + if( nTrailLen ) + { + // check string match against string exceptions + while( *++ppStr ) + if( ImplIsTrailing( rName, *ppStr ) ) + return false; + + rName = rName.copy(0, rName.getLength() - nTrailLen ); + return true; + } + else + { + // skip exception strings + while( *++ppStr ) {} + } + } + + return false; +} + +static bool ImplFindAndErase( OUString& rName, const char* pStr ) +{ + sal_Int32 nLen = static_cast<sal_Int32>(strlen(pStr)); + sal_Int32 nPos = rName.indexOfAsciiL(pStr, nLen ); + if ( nPos < 0 ) + return false; + + OUStringBuffer sBuff(rName); + sBuff.remove(nPos, nLen); + rName = sBuff.makeStringAndClear(); + return true; +} + +void FontSubstConfiguration::getMapName( const OUString& rOrgName, OUString& rShortName, + OUString& rFamilyName, FontWeight& rWeight, + FontWidth& rWidth, ImplFontAttrs& rType ) +{ + rShortName = rOrgName; + + // TODO: get rid of the crazy O(N*strlen) searches below + // they should be possible in O(strlen) + + // Kill leading vendor names and other unimportant data + ImplKillLeading( rShortName, aImplKillLeadingList ); + + // Kill trailing vendor names and other unimportant data + ImplKillTrailing( rShortName, aImplKillTrailingList ); + ImplKillTrailingWithExceptions( rShortName, aImplKillTrailingWithExceptionsList ); + + rFamilyName = rShortName; + + // Kill attributes from the name and update the data + // Weight + const ImplFontAttrWeightSearchData* pWeightList = aImplWeightAttrSearchList; + while ( pWeightList->mpStr ) + { + if ( ImplFindAndErase( rFamilyName, pWeightList->mpStr ) ) + { + if ( (rWeight == WEIGHT_DONTKNOW) || (rWeight == WEIGHT_NORMAL) ) + rWeight = pWeightList->meWeight; + break; + } + pWeightList++; + } + + // Width + const ImplFontAttrWidthSearchData* pWidthList = aImplWidthAttrSearchList; + while ( pWidthList->mpStr ) + { + if ( ImplFindAndErase( rFamilyName, pWidthList->mpStr ) ) + { + if ( (rWidth == WIDTH_DONTKNOW) || (rWidth == WIDTH_NORMAL) ) + rWidth = pWidthList->meWidth; + break; + } + pWidthList++; + } + + // Type + rType = ImplFontAttrs::None; + const ImplFontAttrTypeSearchData* pTypeList = aImplTypeAttrSearchList; + while ( pTypeList->mpStr ) + { + if ( ImplFindAndErase( rFamilyName, pTypeList->mpStr ) ) + rType |= pTypeList->mnType; + pTypeList++; + } + + // Remove numbers + // TODO: also remove localized and fullwidth digits + sal_Int32 i = 0; + OUStringBuffer sBuff(rFamilyName); + while ( i < sBuff.getLength() ) + { + sal_Unicode c = sBuff[ i ]; + if ( (c >= 0x0030) && (c <= 0x0039) ) + sBuff.remove(i, 1); + else + i++; + } +} + +namespace { + +struct StrictStringSort +{ + bool operator()( const FontNameAttr& rLeft, const FontNameAttr& rRight ) + { return rLeft.Name.compareTo( rRight.Name ) < 0; } +}; + +} + +// The entries in this table must match the bits in the ImplFontAttrs enum. + +const char* const pAttribNames[] = +{ + "default", + "standard", + "normal", + "symbol", + "fixed", + "sansserif", + "serif", + "decorative", + "special", + "italic", + "title", + "capitals", + "cjk", + "cjk_jp", + "cjk_sc", + "cjk_tc", + "cjk_kr", + "ctl", + "nonelatin", + "full", + "outline", + "shadow", + "rounded", + "typewriter", + "script", + "handwriting", + "chancery", + "comic", + "brushscript", + "gothic", + "schoolbook", + "other" +}; + +namespace { + +struct enum_convert +{ + const char* pName; + int nEnum; +}; + +} + +const enum_convert pWeightNames[] = +{ + { "normal", WEIGHT_NORMAL }, + { "medium", WEIGHT_MEDIUM }, + { "bold", WEIGHT_BOLD }, + { "black", WEIGHT_BLACK }, + { "semibold", WEIGHT_SEMIBOLD }, + { "light", WEIGHT_LIGHT }, + { "semilight", WEIGHT_SEMILIGHT }, + { "ultrabold", WEIGHT_ULTRABOLD }, + { "semi", WEIGHT_SEMIBOLD }, + { "demi", WEIGHT_SEMIBOLD }, + { "heavy", WEIGHT_BLACK }, + { "unknown", WEIGHT_DONTKNOW }, + { "thin", WEIGHT_THIN }, + { "ultralight", WEIGHT_ULTRALIGHT } +}; + +const enum_convert pWidthNames[] = +{ + { "normal", WIDTH_NORMAL }, + { "condensed", WIDTH_CONDENSED }, + { "expanded", WIDTH_EXPANDED }, + { "unknown", WIDTH_DONTKNOW }, + { "ultracondensed", WIDTH_ULTRA_CONDENSED }, + { "extracondensed", WIDTH_EXTRA_CONDENSED }, + { "semicondensed", WIDTH_SEMI_CONDENSED }, + { "semiexpanded", WIDTH_SEMI_EXPANDED }, + { "extraexpanded", WIDTH_EXTRA_EXPANDED }, + { "ultraexpanded", WIDTH_ULTRA_EXPANDED } +}; + +void FontSubstConfiguration::fillSubstVector( const css::uno::Reference< XNameAccess >& rFont, + const OUString& rType, + std::vector< OUString >& rSubstVector ) const +{ + try + { + Any aAny = rFont->getByName( rType ); + if( auto pLine = o3tl::tryAccess<OUString>(aAny) ) + { + sal_Int32 nLength = pLine->getLength(); + if( nLength ) + { + const sal_Unicode* pStr = pLine->getStr(); + sal_Int32 nTokens = 0; + // count tokens + while( nLength-- ) + { + if( *pStr++ == ';' ) + nTokens++; + } + rSubstVector.clear(); + // optimize performance, heap fragmentation + rSubstVector.reserve( nTokens ); + sal_Int32 nIndex = 0; + while( nIndex != -1 ) + { + OUString aSubst( pLine->getToken( 0, ';', nIndex ) ); + if( !aSubst.isEmpty() ) + { + auto itPair = maSubstHash.insert( aSubst ); + if (!itPair.second) + aSubst = *itPair.first; + rSubstVector.push_back( aSubst ); + } + } + } + } + } + catch (const NoSuchElementException&) + { + } + catch (const WrappedTargetException&) + { + } +} + +FontWeight FontSubstConfiguration::getSubstWeight( const css::uno::Reference< XNameAccess >& rFont, + const OUString& rType ) const +{ + int weight = -1; + try + { + Any aAny = rFont->getByName( rType ); + if( auto pLine = o3tl::tryAccess<OUString>(aAny) ) + { + if( !pLine->isEmpty() ) + { + for( weight=std::size(pWeightNames)-1; weight >= 0; weight-- ) + if( pLine->equalsIgnoreAsciiCaseAscii( pWeightNames[weight].pName ) ) + break; + } + SAL_WARN_IF(weight < 0, "unotools.config", "Error: invalid weight " << *pLine); + } + } + catch (const NoSuchElementException&) + { + } + catch (const WrappedTargetException&) + { + } + return static_cast<FontWeight>( weight >= 0 ? pWeightNames[weight].nEnum : WEIGHT_DONTKNOW ); +} + +FontWidth FontSubstConfiguration::getSubstWidth( const css::uno::Reference< XNameAccess >& rFont, + const OUString& rType ) const +{ + int width = -1; + try + { + Any aAny = rFont->getByName( rType ); + if( auto pLine = o3tl::tryAccess<OUString>(aAny) ) + { + if( !pLine->isEmpty() ) + { + for( width=std::size(pWidthNames)-1; width >= 0; width-- ) + if( pLine->equalsIgnoreAsciiCaseAscii( pWidthNames[width].pName ) ) + break; + } + SAL_WARN_IF( width < 0, "unotools.config", "Error: invalid width " << *pLine); + } + } + catch (const NoSuchElementException&) + { + } + catch (const WrappedTargetException&) + { + } + return static_cast<FontWidth>( width >= 0 ? pWidthNames[width].nEnum : WIDTH_DONTKNOW ); +} + +ImplFontAttrs FontSubstConfiguration::getSubstType( const css::uno::Reference< XNameAccess >& rFont, + const OUString& rType ) const +{ + sal_uInt32 type = 0; + try + { + Any aAny = rFont->getByName( rType ); + auto pLine = o3tl::tryAccess<OUString>(aAny); + if( !pLine ) + return ImplFontAttrs::None; + if( pLine->isEmpty() ) + return ImplFontAttrs::None; + sal_Int32 nIndex = 0; + while( nIndex != -1 ) + { + OUString aToken( pLine->getToken( 0, ',', nIndex ) ); + for( int k = 0; k < 32; k++ ) + if( aToken.equalsIgnoreAsciiCaseAscii( pAttribNames[k] ) ) + { + type |= sal_uInt32(1) << k; + break; + } + } + assert(((type & ~o3tl::typed_flags<ImplFontAttrs>::mask) == 0) && "invalid font attributes"); + } + catch (const NoSuchElementException&) + { + } + catch (const WrappedTargetException&) + { + } + + return static_cast<ImplFontAttrs>(type); +} + +void FontSubstConfiguration::readLocaleSubst( const OUString& rBcp47 ) const +{ + std::unordered_map< OUString, LocaleSubst >::const_iterator it = m_aSubst.find( rBcp47 ); + if( it == m_aSubst.end() ) + return; + + if( it->second.bConfigRead ) + return; + + it->second.bConfigRead = true; + Reference< XNameAccess > xNode; + try + { + Any aAny = m_xConfigAccess->getByName( it->second.aConfigLocaleString ); + aAny >>= xNode; + } + catch (const NoSuchElementException&) + { + } + catch (const WrappedTargetException&) + { + } + if( !xNode.is() ) + return; + + const Sequence< OUString > aFonts = xNode->getElementNames(); + int nFonts = aFonts.getLength(); + // improve performance, heap fragmentation + it->second.aSubstAttributes.reserve( nFonts ); + + // strings for subst retrieval, construct only once + static constexpr OUStringLiteral aSubstFontsStr ( u"SubstFonts" ); + static constexpr OUStringLiteral aSubstFontsMSStr( u"SubstFontsMS" ); + static constexpr OUStringLiteral aSubstWeightStr ( u"FontWeight" ); + static constexpr OUStringLiteral aSubstWidthStr ( u"FontWidth" ); + static constexpr OUStringLiteral aSubstTypeStr ( u"FontType" ); + for( const OUString& rFontName : aFonts ) + { + Reference< XNameAccess > xFont; + try + { + Any aAny = xNode->getByName( rFontName ); + aAny >>= xFont; + } + catch (const NoSuchElementException&) + { + } + catch (const WrappedTargetException&) + { + } + if( ! xFont.is() ) + { + SAL_WARN("unotools.config", "did not get font attributes for " << rFontName); + continue; + } + + FontNameAttr aAttr; + // read subst attributes from config + aAttr.Name = rFontName; + fillSubstVector( xFont, aSubstFontsStr, aAttr.Substitutions ); + fillSubstVector( xFont, aSubstFontsMSStr, aAttr.MSSubstitutions ); + aAttr.Weight = getSubstWeight( xFont, aSubstWeightStr ); + aAttr.Width = getSubstWidth( xFont, aSubstWidthStr ); + aAttr.Type = getSubstType( xFont, aSubstTypeStr ); + + // finally insert this entry + it->second.aSubstAttributes.push_back( aAttr ); + } + std::sort( it->second.aSubstAttributes.begin(), it->second.aSubstAttributes.end(), StrictStringSort() ); +} + +const FontNameAttr* FontSubstConfiguration::getSubstInfo( const OUString& rFontName ) const +{ + if( rFontName.isEmpty() ) + return nullptr; + + // search if a (language dep.) replacement table for the given font exists + // fallback is english + OUString aSearchFont( rFontName.toAsciiLowerCase() ); + FontNameAttr aSearchAttr; + aSearchAttr.Name = aSearchFont; + + ::std::vector< OUString > aFallbacks( maLanguageTag.getFallbackStrings( true)); + if (maLanguageTag.getLanguage() != "en") + aFallbacks.emplace_back("en"); + + for (const auto& rFallback : aFallbacks) + { + std::unordered_map< OUString, LocaleSubst >::const_iterator lang = m_aSubst.find( rFallback ); + if( lang != m_aSubst.end() ) + { + if( ! lang->second.bConfigRead ) + readLocaleSubst( rFallback ); + // try to find an exact match + // because the list is sorted this will also find fontnames of the form searchfontname* + std::vector< FontNameAttr >::const_iterator it = ::std::lower_bound( lang->second.aSubstAttributes.begin(), lang->second.aSubstAttributes.end(), aSearchAttr, StrictStringSort() ); + if( it != lang->second.aSubstAttributes.end()) + { + const FontNameAttr& rFoundAttr = *it; + // a search for "abcblack" may match with an entry for "abc" + // the reverse is not a good idea (e.g. #i112731# alba->albani) + if( rFoundAttr.Name.getLength() <= aSearchFont.getLength() ) + if( aSearchFont.startsWith( rFoundAttr.Name)) + return &rFoundAttr; + } + } + } + return nullptr; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/config/historyoptions.cxx b/unotools/source/config/historyoptions.cxx new file mode 100644 index 0000000000..3d8d8f933b --- /dev/null +++ b/unotools/source/config/historyoptions.cxx @@ -0,0 +1,610 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <unotools/historyoptions.hxx> +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/Sequence.hxx> + +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/container/XNameContainer.hpp> +#include <com/sun/star/lang/XSingleServiceFactory.hpp> +#include <comphelper/configurationhelper.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <optional> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::beans; + +namespace { + constexpr OUString s_sItemList = u"ItemList"_ustr; + constexpr OUString s_sOrderList = u"OrderList"_ustr; + constexpr OUString s_sHistoryItemRef = u"HistoryItemRef"_ustr; + constexpr OUString s_sFilter = u"Filter"_ustr; + constexpr OUString s_sTitle = u"Title"_ustr; + constexpr OUString s_sThumbnail = u"Thumbnail"_ustr; + constexpr OUString s_sReadOnly = u"ReadOnly"_ustr; + constexpr OUString s_sPinned = u"Pinned"_ustr; +} + +static uno::Reference<container::XNameAccess> GetConfig(); +static uno::Reference<container::XNameAccess> GetCommonXCU(); +static uno::Reference<container::XNameAccess> GetListAccess( + uno::Reference<container::XNameAccess> const & xCfg, + EHistoryType eHistory); +static void TruncateList( + const uno::Reference<container::XNameAccess>& xCfg, + const uno::Reference<container::XNameAccess>& xList, + sal_uInt32 nSize); + +static void PrependItem(const uno::Reference<container::XNameAccess>& xCfg, + uno::Reference<container::XNameContainer>& xList, std::u16string_view sURL); +static void MoveItemToUnpinned(const uno::Reference<container::XNameAccess>& xCfg, + uno::Reference<container::XNameContainer>& xOrderList, + uno::Reference<container::XNameContainer>& xItemList, + std::u16string_view sURL); + +static sal_uInt32 GetCapacity(const uno::Reference<container::XNameAccess>& xCommonXCU, EHistoryType eHistory); + +namespace SvtHistoryOptions +{ + +void Clear(EHistoryType eHistory, const bool bClearPinnedItems) +{ + try + { + uno::Reference<container::XNameAccess> xCfg = GetConfig(); + uno::Reference<container::XNameAccess> xListAccess(GetListAccess(xCfg, eHistory)); + + // Retrieve order and item lists using name access to check properties of individual items + uno::Reference<container::XNameAccess> xItemList; + uno::Reference<container::XNameAccess> xOrderList; + xListAccess->getByName(s_sItemList) >>= xItemList; + xListAccess->getByName(s_sOrderList) >>= xOrderList; + + // Retrieve order and item lists using name container to delete individual items + uno::Reference<container::XNameContainer> xOrderNode; + uno::Reference<container::XNameContainer> xItemNode; + xListAccess->getByName(s_sItemList) >>= xItemNode; + xListAccess->getByName(s_sOrderList) >>= xOrderNode; + + const sal_Int32 nLength = xOrderList->getElementNames().getLength(); + for (sal_Int32 nItem = 0; nItem < nLength; ++nItem) + { + // Retrieve single item from the order list + OUString sUrl; + uno::Reference<beans::XPropertySet> xSet; + xOrderList->getByName(OUString::number(nItem)) >>= xSet; + xSet->getPropertyValue(s_sHistoryItemRef) >>= sUrl; + + // tdf#155698 - check if pinned items should be cleared + bool bIsItemPinned = false; + if (!bClearPinnedItems) + { + xItemList->getByName(sUrl) >>= xSet; + if (xSet->getPropertySetInfo()->hasPropertyByName(s_sPinned)) + xSet->getPropertyValue(s_sPinned) >>= bIsItemPinned; + } + + if (!bIsItemPinned) + { + xItemNode->removeByName(sUrl); + xOrderNode->removeByName(OUString::number(nItem)); + } + } + + ::comphelper::ConfigurationHelper::flush(xCfg); + } + catch(const uno::Exception&) + { + DBG_UNHANDLED_EXCEPTION("unotools.config"); + } +} + +std::vector< HistoryItem > GetList( EHistoryType eHistory ) +{ + std::vector< HistoryItem > aRet; + try + { + uno::Reference<container::XNameAccess> xCfg = GetConfig(); + uno::Reference<container::XNameAccess> xCommonXCU = GetCommonXCU(); + uno::Reference<container::XNameAccess> xListAccess(GetListAccess(xCfg, eHistory)); + + TruncateList(xCfg, xListAccess, GetCapacity(xCommonXCU, eHistory)); + + uno::Reference<container::XNameAccess> xItemList; + uno::Reference<container::XNameAccess> xOrderList; + xListAccess->getByName(s_sItemList) >>= xItemList; + xListAccess->getByName(s_sOrderList) >>= xOrderList; + + const sal_Int32 nLength = xOrderList->getElementNames().getLength(); + aRet.reserve(nLength); + + for (sal_Int32 nItem = 0; nItem < nLength; ++nItem) + { + try + { + OUString sUrl; + uno::Reference<beans::XPropertySet> xSet; + xOrderList->getByName(OUString::number(nItem)) >>= xSet; + xSet->getPropertyValue(s_sHistoryItemRef) >>= sUrl; + + xItemList->getByName(sUrl) >>= xSet; + HistoryItem aItem; + aItem.sURL = sUrl; + xSet->getPropertyValue(s_sFilter) >>= aItem.sFilter; + xSet->getPropertyValue(s_sTitle) >>= aItem.sTitle; + xSet->getPropertyValue(s_sThumbnail) >>= aItem.sThumbnail; + xSet->getPropertyValue(s_sReadOnly) >>= aItem.isReadOnly; + if (xSet->getPropertySetInfo()->hasPropertyByName(s_sPinned)) + xSet->getPropertyValue(s_sPinned) >>= aItem.isPinned; + + aRet.push_back(aItem); + } + catch(const uno::Exception&) + { + // <https://bugs.libreoffice.org/show_bug.cgi?id=46074> + // "FILEOPEN: No Recent Documents..." discusses a problem + // with corrupted /org.openoffice.Office/Histories/Histories + // configuration items; to work around that problem, simply + // ignore such corrupted individual items here, so that at + // least newly added items are successfully reported back + // from this function: + DBG_UNHANDLED_EXCEPTION("unotools.config"); + } + } + } + catch(const uno::Exception&) + { + DBG_UNHANDLED_EXCEPTION("unotools.config"); + } + return aRet; +} + +void AppendItem(EHistoryType eHistory, const OUString& sURL, const OUString& sFilter, + const OUString& sTitle, const std::optional<OUString>& sThumbnail, + ::std::optional<bool> const oIsReadOnly) +{ + try + { + uno::Reference<container::XNameAccess> xCfg = GetConfig(); + uno::Reference<container::XNameAccess> xCommonXCU = GetCommonXCU(); + uno::Reference<container::XNameAccess> xListAccess(GetListAccess(xCfg, eHistory)); + + TruncateList(xCfg, xListAccess, GetCapacity(xCommonXCU, eHistory)); + + sal_Int32 nMaxSize = GetCapacity(xCommonXCU, eHistory); + if (nMaxSize == 0) + return; + + uno::Reference<container::XNameContainer> xItemList; + uno::Reference<container::XNameContainer> xOrderList; + xListAccess->getByName(s_sItemList) >>= xItemList; + xListAccess->getByName(s_sOrderList) >>= xOrderList; + sal_Int32 nLength = xOrderList->getElementNames().getLength(); + + // The item to be appended already exists + if (xItemList->hasByName(sURL)) + { + uno::Reference<beans::XPropertySet> xSet; + xItemList->getByName(sURL) >>= xSet; + if (sThumbnail) + { + // update the thumbnail + xSet->setPropertyValue(s_sThumbnail, uno::Any(*sThumbnail)); + } + if (oIsReadOnly) + { + xSet->setPropertyValue(s_sReadOnly, uno::Any(*oIsReadOnly)); + } + + // tdf#38742 - check the current pinned state of the item and move it accordingly + bool bIsItemPinned = false; + if (xSet->getPropertySetInfo()->hasPropertyByName(s_sPinned)) + xSet->getPropertyValue(s_sPinned) >>= bIsItemPinned; + if (bIsItemPinned) + PrependItem(xCfg, xOrderList, sURL); + else + MoveItemToUnpinned(xCfg, xOrderList, xItemList, sURL); + } + else // The item to be appended does not exist yet + { + uno::Reference<beans::XPropertySet> xSet; + uno::Reference<lang::XSingleServiceFactory> xFac; + uno::Reference<uno::XInterface> xInst; + uno::Reference<beans::XPropertySet> xPrevSet; + uno::Reference<beans::XPropertySet> xNextSet; + + // Append new item to OrderList. + if ( nLength == nMaxSize ) + { + OUString sRemove; + xOrderList->getByName(OUString::number(nLength-1)) >>= xSet; + xSet->getPropertyValue(s_sHistoryItemRef) >>= sRemove; + try + { + xItemList->removeByName(sRemove); + } + catch (container::NoSuchElementException &) + { + // <https://bugs.libreoffice.org/show_bug.cgi?id=46074> + // "FILEOPEN: No Recent Documents..." discusses a problem + // with corrupted /org.openoffice.Office/Histories/Histories + // configuration items; to work around that problem, simply + // ignore such corrupted individual items here, so that at + // least newly added items are successfully added: + if (!sRemove.isEmpty()) + { + throw; + } + } + } + if (nLength != nMaxSize) + { + xFac.set(xOrderList, uno::UNO_QUERY); + xInst = xFac->createInstance(); + OUString sPush = OUString::number(nLength++); + xOrderList->insertByName(sPush, uno::Any(xInst)); + } + for (sal_Int32 j=nLength-1; j>0; --j) + { + xOrderList->getByName( OUString::number(j) ) >>= xPrevSet; + xOrderList->getByName( OUString::number(j-1) ) >>= xNextSet; + OUString sTemp; + xNextSet->getPropertyValue(s_sHistoryItemRef) >>= sTemp; + xPrevSet->setPropertyValue(s_sHistoryItemRef, uno::Any(sTemp)); + } + xOrderList->getByName( OUString::number(0) ) >>= xSet; + xSet->setPropertyValue(s_sHistoryItemRef, uno::Any(sURL)); + + // Append the item to ItemList. + xFac.set(xItemList, uno::UNO_QUERY); + xInst = xFac->createInstance(); + xItemList->insertByName(sURL, uno::Any(xInst)); + + xSet.set(xInst, uno::UNO_QUERY); + xSet->setPropertyValue(s_sFilter, uno::Any(sFilter)); + xSet->setPropertyValue(s_sTitle, uno::Any(sTitle)); + xSet->setPropertyValue(s_sThumbnail, uno::Any(sThumbnail.value_or(OUString()))); + if (oIsReadOnly) + { + xSet->setPropertyValue(s_sReadOnly, uno::Any(*oIsReadOnly)); + } + + // tdf#38742 - check the current pinned state of the item and move it accordingly + bool bIsItemPinned = false; + if (xSet->getPropertySetInfo()->hasPropertyByName(s_sPinned)) + xSet->getPropertyValue(s_sPinned) >>= bIsItemPinned; + if (bIsItemPinned) + PrependItem(xCfg, xOrderList, sURL); + else + MoveItemToUnpinned(xCfg, xOrderList, xItemList, sURL); + + ::comphelper::ConfigurationHelper::flush(xCfg); + } + } + catch(const uno::Exception&) + { + DBG_UNHANDLED_EXCEPTION("unotools.config"); + } +} + +void DeleteItem(EHistoryType eHistory, const OUString& sURL, const bool bClearPinned) +{ + try + { + uno::Reference<container::XNameAccess> xCfg = GetConfig(); + uno::Reference<container::XNameAccess> xListAccess(GetListAccess(xCfg, eHistory)); + + uno::Reference<container::XNameContainer> xItemList; + uno::Reference<container::XNameContainer> xOrderList; + xListAccess->getByName(s_sItemList) >>= xItemList; + xListAccess->getByName(s_sOrderList) >>= xOrderList; + sal_Int32 nLength = xOrderList->getElementNames().getLength(); + + // if it does not exist, nothing to do + if (!xItemList->hasByName(sURL)) + return; + + // it's the last one and pinned items can be cleared, just clear the lists + if (nLength == 1 && bClearPinned) + { + Clear(eHistory); + return; + } + + // find it in the OrderList + sal_Int32 nFromWhere = 0; + bool bIsItemPinned = false; + for (; nFromWhere < nLength - 1; ++nFromWhere) + { + uno::Reference<beans::XPropertySet> xSet; + OUString aItem; + xOrderList->getByName(OUString::number(nFromWhere)) >>= xSet; + xSet->getPropertyValue(s_sHistoryItemRef) >>= aItem; + // tdf#155698 - check if pinned item should be deleted + if (!bClearPinned && xSet->getPropertySetInfo()->hasPropertyByName(s_sPinned)) + xSet->getPropertyValue(s_sPinned) >>= bIsItemPinned; + + if (aItem == sURL) + break; + } + + // tdf#155698 - check if pinned item should be deleted + if (!bIsItemPinned) + { + // and shift the rest of the items in OrderList accordingly + for (sal_Int32 i = nFromWhere; i < nLength - 1; ++i) + { + uno::Reference<beans::XPropertySet> xPrevSet; + uno::Reference<beans::XPropertySet> xNextSet; + xOrderList->getByName(OUString::number(i)) >>= xPrevSet; + xOrderList->getByName(OUString::number(i + 1)) >>= xNextSet; + + OUString sTemp; + xNextSet->getPropertyValue(s_sHistoryItemRef) >>= sTemp; + xPrevSet->setPropertyValue(s_sHistoryItemRef, uno::Any(sTemp)); + } + xOrderList->removeByName(OUString::number(nLength - 1)); + + // and finally remove it from the ItemList + xItemList->removeByName(sURL); + + ::comphelper::ConfigurationHelper::flush(xCfg); + } + } + catch (const uno::Exception&) + { + DBG_UNHANDLED_EXCEPTION("unotools.config"); + } +} + +void TogglePinItem(EHistoryType eHistory, const OUString& sURL) +{ + try + { + uno::Reference<container::XNameAccess> xCfg = GetConfig(); + uno::Reference<container::XNameAccess> xListAccess(GetListAccess(xCfg, eHistory)); + + uno::Reference<container::XNameContainer> xItemList; + xListAccess->getByName(s_sItemList) >>= xItemList; + + // Check if item exists + if (xItemList->hasByName(sURL)) + { + // Toggle pinned option + uno::Reference<beans::XPropertySet> xSet; + xItemList->getByName(sURL) >>= xSet; + bool bIsItemPinned = false; + if (xSet->getPropertySetInfo()->hasPropertyByName(s_sPinned)) + xSet->getPropertyValue(s_sPinned) >>= bIsItemPinned; + xSet->setPropertyValue(s_sPinned, uno::Any(!bIsItemPinned)); + + uno::Reference<container::XNameContainer> xOrderList; + xListAccess->getByName(s_sOrderList) >>= xOrderList; + + // Shift item to the beginning of the document list if is not pinned now + if (bIsItemPinned) + MoveItemToUnpinned(xCfg, xOrderList, xItemList, sURL); + else + PrependItem(xCfg, xOrderList, sURL); + } + } + catch (const uno::Exception&) + { + DBG_UNHANDLED_EXCEPTION("unotools.config"); + } +} + +} // namespace + + +static uno::Reference<container::XNameAccess> GetConfig() +{ + return uno::Reference<container::XNameAccess>( + ::comphelper::ConfigurationHelper::openConfig( + ::comphelper::getProcessComponentContext(), + "org.openoffice.Office.Histories/Histories", + ::comphelper::EConfigurationModes::Standard), + uno::UNO_QUERY_THROW); +} + +static uno::Reference<container::XNameAccess> GetCommonXCU() +{ + return uno::Reference<container::XNameAccess>( + ::comphelper::ConfigurationHelper::openConfig( + ::comphelper::getProcessComponentContext(), + "org.openoffice.Office.Common/History", + ::comphelper::EConfigurationModes::Standard), + uno::UNO_QUERY_THROW); +} + +static uno::Reference<container::XNameAccess> GetListAccess( + const uno::Reference<container::XNameAccess>& xCfg, + EHistoryType eHistory) +{ + uno::Reference<container::XNameAccess> xListAccess; + switch (eHistory) + { + case EHistoryType::PickList: + xCfg->getByName("PickList") >>= xListAccess; + break; + + case EHistoryType::HelpBookmarks: + xCfg->getByName("HelpBookmarks") >>= xListAccess; + break; + } + return xListAccess; +} + +static void TruncateList( + const uno::Reference<container::XNameAccess>& xCfg, + const uno::Reference<container::XNameAccess>& xList, + sal_uInt32 nSize) +{ + uno::Reference<container::XNameContainer> xItemList; + uno::Reference<container::XNameContainer> xOrderList; + xList->getByName(s_sOrderList) >>= xOrderList; + xList->getByName(s_sItemList) >>= xItemList; + + const sal_uInt32 nLength = xOrderList->getElementNames().getLength(); + if (nSize >= nLength) + return; + + for (sal_uInt32 i=nLength-1; i>=nSize; --i) + { + uno::Reference<beans::XPropertySet> xSet; + OUString sTmp; + const OUString sRemove = OUString::number(i); + xOrderList->getByName(sRemove) >>= xSet; + xSet->getPropertyValue(s_sHistoryItemRef) >>= sTmp; + xItemList->removeByName(sTmp); + xOrderList->removeByName(sRemove); + } + + ::comphelper::ConfigurationHelper::flush(xCfg); +} + +static void PrependItem(const uno::Reference<container::XNameAccess>& xCfg, + uno::Reference<container::XNameContainer>& xList, std::u16string_view sURL) +{ + uno::Reference<beans::XPropertySet> xSet; + const sal_Int32 nLength = xList->getElementNames().getLength(); + for (sal_Int32 i = 0; i < nLength; i++) + { + OUString aItem; + xList->getByName(OUString::number(i)) >>= xSet; + xSet->getPropertyValue(s_sHistoryItemRef) >>= aItem; + + if (aItem == sURL) + { + for (sal_Int32 j = i - 1; j >= 0; --j) + { + uno::Reference<beans::XPropertySet> xPrevSet; + uno::Reference<beans::XPropertySet> xNextSet; + xList->getByName(OUString::number(j + 1)) >>= xPrevSet; + xList->getByName(OUString::number(j)) >>= xNextSet; + + OUString sTemp; + xNextSet->getPropertyValue(s_sHistoryItemRef) >>= sTemp; + xPrevSet->setPropertyValue(s_sHistoryItemRef, uno::Any(sTemp)); + } + xList->getByName(OUString::number(0)) >>= xSet; + xSet->setPropertyValue(s_sHistoryItemRef, uno::Any(aItem)); + ::comphelper::ConfigurationHelper::flush(xCfg); + return; + } + } +} + +static void MoveItemToUnpinned(const uno::Reference<container::XNameAccess>& xCfg, + uno::Reference<container::XNameContainer>& xOrderList, + uno::Reference<container::XNameContainer>& xItemList, + std::u16string_view sURL) +{ + uno::Reference<beans::XPropertySet> xSet; + const sal_Int32 nLength = xOrderList->getElementNames().getLength(); + // Search for item in the ordered list list + for (sal_Int32 i = 0; i < nLength; i++) + { + OUString aItem; + xOrderList->getByName(OUString::number(i)) >>= xSet; + xSet->getPropertyValue(s_sHistoryItemRef) >>= aItem; + + if (aItem == sURL) + { + // Move item to the unpinned document section to the right if it was previously pinned + for (sal_Int32 j = i + 1; j < nLength; j++) + { + uno::Reference<beans::XPropertySet> xNextSet; + xOrderList->getByName(OUString::number(j)) >>= xNextSet; + + OUString aNextItem; + xNextSet->getPropertyValue(s_sHistoryItemRef) >>= aNextItem; + + uno::Reference<beans::XPropertySet> xNextItemSet; + xItemList->getByName(aNextItem) >>= xNextItemSet; + bool bIsItemPinned = false; + if (xNextItemSet->getPropertySetInfo()->hasPropertyByName(s_sPinned)) + xNextItemSet->getPropertyValue(s_sPinned) >>= bIsItemPinned; + if (bIsItemPinned) + { + xOrderList->getByName(OUString::number(j - 1)) >>= xSet; + xSet->getPropertyValue(s_sHistoryItemRef) >>= aItem; + xSet->setPropertyValue(s_sHistoryItemRef, uno::Any(aNextItem)); + xNextSet->setPropertyValue(s_sHistoryItemRef, uno::Any(aItem)); + } + else + break; + } + + // Move item to the unpinned document section to the left if it was previously unpinned + for (sal_Int32 j = i - 1; j >= 0; --j) + { + uno::Reference<beans::XPropertySet> xPrevSet; + xOrderList->getByName(OUString::number(j)) >>= xPrevSet; + + OUString aPrevItem; + xPrevSet->getPropertyValue(s_sHistoryItemRef) >>= aPrevItem; + + uno::Reference<beans::XPropertySet> xPrevItemSet; + xItemList->getByName(aPrevItem) >>= xPrevItemSet; + bool bIsItemPinned = false; + if (xPrevItemSet->getPropertySetInfo()->hasPropertyByName(s_sPinned)) + xPrevItemSet->getPropertyValue(s_sPinned) >>= bIsItemPinned; + if (!bIsItemPinned) + { + xOrderList->getByName(OUString::number(j + 1)) >>= xSet; + xSet->getPropertyValue(s_sHistoryItemRef) >>= aItem; + xSet->setPropertyValue(s_sHistoryItemRef, uno::Any(aPrevItem)); + xPrevSet->setPropertyValue(s_sHistoryItemRef, uno::Any(aItem)); + } + else + break; + } + + ::comphelper::ConfigurationHelper::flush(xCfg); + return; + } + } +} + +static sal_uInt32 GetCapacity(const uno::Reference<container::XNameAccess>& xCommonXCU, EHistoryType eHistory) +{ + uno::Reference<beans::XPropertySet> xListAccess(xCommonXCU, uno::UNO_QUERY_THROW); + + sal_uInt32 nSize = 0; + + switch (eHistory) + { + case EHistoryType::PickList: + xListAccess->getPropertyValue("PickListSize") >>= nSize; + break; + + case EHistoryType::HelpBookmarks: + xListAccess->getPropertyValue("HelpBookmarkSize") >>= nSize; + break; + } + + return nSize; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/config/itemholder1.cxx b/unotools/source/config/itemholder1.cxx new file mode 100644 index 0000000000..a8fa0dbd5c --- /dev/null +++ b/unotools/source/config/itemholder1.cxx @@ -0,0 +1,150 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include "itemholder1.hxx" + +#include <comphelper/processfactory.hxx> +#include <com/sun/star/lang/XComponent.hpp> +#include <com/sun/star/configuration/theDefaultProvider.hpp> + +#include <unotools/useroptions.hxx> +#include <unotools/cmdoptions.hxx> +#include <unotools/compatibility.hxx> +#include <unotools/lingucfg.hxx> +#include <unotools/moduleoptions.hxx> +#include <unotools/pathoptions.hxx> +#include <unotools/options.hxx> +#include <unotools/syslocaleoptions.hxx> +#include <rtl/ref.hxx> +#include <osl/diagnose.h> +#include <comphelper/diagnose_ex.hxx> + +ItemHolder1::ItemHolder1() +{ + try + { + css::uno::Reference< css::uno::XComponentContext > xContext = ::comphelper::getProcessComponentContext(); + css::uno::Reference< css::lang::XComponent > xCfg( + css::configuration::theDefaultProvider::get( xContext ), + css::uno::UNO_QUERY_THROW ); + xCfg->addEventListener(static_cast< css::lang::XEventListener* >(this)); + } +#ifdef DBG_UTIL + catch(const css::uno::Exception&) + { + static bool bMessage = true; + if(bMessage) + { + bMessage = false; + TOOLS_WARN_EXCEPTION( "unotools", "CreateInstance with arguments"); + } + } +#else + catch(css::uno::Exception&){} +#endif +} + +ItemHolder1::~ItemHolder1() +{ + impl_releaseAllItems(); +} + +void ItemHolder1::holdConfigItem(EItem eItem) +{ + static rtl::Reference<ItemHolder1> pHolder = new ItemHolder1(); + pHolder->impl_addItem(eItem); +} + +void SAL_CALL ItemHolder1::disposing(const css::lang::EventObject&) +{ + css::uno::Reference< css::uno::XInterface > xSelfHold(static_cast< css::lang::XEventListener* >(this), css::uno::UNO_QUERY); + impl_releaseAllItems(); +} + +void ItemHolder1::impl_addItem(EItem eItem) +{ + std::scoped_lock aLock(m_aLock); + + for ( auto const & rInfo : m_lItems ) + { + if (rInfo.eItem == eItem) + return; + } + + TItemInfo aNewItem; + aNewItem.eItem = eItem; + impl_newItem(aNewItem); + if (aNewItem.pItem) + m_lItems.emplace_back(std::move(aNewItem)); +} + +void ItemHolder1::impl_releaseAllItems() +{ + std::vector< TItemInfo > items; + { + std::scoped_lock aLock(m_aLock); + items.swap(m_lItems); + } + + // items will be freed when the block exits +} + +void ItemHolder1::impl_newItem(TItemInfo& rItem) +{ + switch(rItem.eItem) + { + case EItem::CmdOptions : + rItem.pItem.reset( new SvtCommandOptions() ); + break; + + case EItem::Compatibility : + rItem.pItem.reset( new SvtCompatibilityOptions() ); + break; + + case EItem::EventConfig : + //rItem.pItem.reset( new GlobalEventConfig() ); + break; + + case EItem::LinguConfig : + rItem.pItem.reset( new SvtLinguConfig() ); + break; + + case EItem::ModuleOptions : + rItem.pItem.reset( new SvtModuleOptions() ); + break; + + case EItem::PathOptions : + rItem.pItem.reset( new SvtPathOptions() ); + break; + + case EItem::UserOptions : + rItem.pItem.reset( new SvtUserOptions() ); + break; + + case EItem::SysLocaleOptions : + rItem.pItem.reset( new SvtSysLocaleOptions() ); + break; + + default: + OSL_FAIL( "unknown item type" ); + break; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/config/itemholder1.hxx b/unotools/source/config/itemholder1.hxx new file mode 100644 index 0000000000..b8a6504fd3 --- /dev/null +++ b/unotools/source/config/itemholder1.hxx @@ -0,0 +1,56 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <unotools/itemholderbase.hxx> +#include <cppuhelper/implbase.hxx> +#include <com/sun/star/lang/XEventListener.hpp> +#include <mutex> + +class ItemHolder1 : public ::cppu::WeakImplHelper< css::lang::XEventListener > +{ + + // member + private: + + std::mutex m_aLock; + std::vector<TItemInfo> m_lItems; + + // c++ interface + public: + + ItemHolder1(); + virtual ~ItemHolder1() override; + static void holdConfigItem(EItem eItem); + + // uno interface + public: + + virtual void SAL_CALL disposing(const css::lang::EventObject& aEvent) override; + + // helper + private: + + void impl_addItem(EItem eItem); + void impl_releaseAllItems(); + static void impl_newItem(TItemInfo& rItem); +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/config/lingucfg.cxx b/unotools/source/config/lingucfg.cxx new file mode 100644 index 0000000000..730cab027f --- /dev/null +++ b/unotools/source/config/lingucfg.cxx @@ -0,0 +1,1207 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <com/sun/star/lang/Locale.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/configuration/theDefaultProvider.hpp> +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/util/XChangesBatch.hpp> +#include <sal/log.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <i18nlangtag/mslangid.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <tools/debug.hxx> +#include <unotools/configitem.hxx> +#include <unotools/lingucfg.hxx> +#include <unotools/linguprops.hxx> +#include <comphelper/getexpandeduri.hxx> +#include <comphelper/processfactory.hxx> +#include <o3tl/string_view.hxx> +#include <mutex> + +#include "itemholder1.hxx" + +using namespace com::sun::star; + +constexpr OUStringLiteral FILE_PROTOCOL = u"file:///"; + +namespace +{ + std::mutex& theSvtLinguConfigItemMutex() + { + static std::mutex SINGLETON; + return SINGLETON; + } +} + +static bool lcl_SetLocale( LanguageType &rLanguage, const uno::Any &rVal ) +{ + bool bSucc = false; + + lang::Locale aNew; + if (rVal >>= aNew) // conversion successful? + { + LanguageType nNew = LanguageTag::convertToLanguageType( aNew, false); + if (nNew != rLanguage) + { + rLanguage = nNew; + bSucc = true; + } + } + return bSucc; +} + +static OUString lcl_LanguageToCfgLocaleStr( LanguageType nLanguage ) +{ + OUString aRes; + if (LANGUAGE_SYSTEM != nLanguage) + aRes = LanguageTag::convertToBcp47( nLanguage ); + return aRes; +} + +static LanguageType lcl_CfgAnyToLanguage( const uno::Any &rVal ) +{ + OUString aTmp; + rVal >>= aTmp; + return (aTmp.isEmpty()) ? LANGUAGE_SYSTEM : LanguageTag::convertToLanguageTypeWithFallback( aTmp ); +} + +SvtLinguOptions::SvtLinguOptions() + : bROActiveDics(false) + , bROActiveConvDics(false) + , nHyphMinLeading(2) + , nHyphMinTrailing(2) + , nHyphMinWordLength(0) + , bROHyphMinLeading(false) + , bROHyphMinTrailing(false) + , bROHyphMinWordLength(false) + , nDefaultLanguage(LANGUAGE_NONE) + , nDefaultLanguage_CJK(LANGUAGE_NONE) + , nDefaultLanguage_CTL(LANGUAGE_NONE) + , bRODefaultLanguage(false) + , bRODefaultLanguage_CJK(false) + , bRODefaultLanguage_CTL(false) + , bIsSpellSpecial(true) + , bIsSpellAuto(false) + , bIsSpellReverse(false) + , bROIsSpellSpecial(false) + , bROIsSpellAuto(false) + , bROIsSpellReverse(false) + , bIsHyphSpecial(true) + , bIsHyphAuto(false) + , bROIsHyphSpecial(false) + , bROIsHyphAuto(false) + , bIsUseDictionaryList(true) + , bIsIgnoreControlCharacters(true) + , bROIsUseDictionaryList(false) + , bROIsIgnoreControlCharacters(false) + , bIsSpellWithDigits(false) + , bIsSpellUpperCase(false) + , bIsSpellClosedCompound(true) + , bIsSpellHyphenatedCompound(true) + , bROIsSpellWithDigits(false) + , bROIsSpellUpperCase(false) + , bROIsSpellClosedCompound(false) + , bROIsSpellHyphenatedCompound(false) + , bIsIgnorePostPositionalWord(true) + , bIsAutoCloseDialog(false) + , bIsShowEntriesRecentlyUsedFirst(false) + , bIsAutoReplaceUniqueEntries(false) + , bIsDirectionToSimplified(true) + , bIsUseCharacterVariants(false) + , bIsTranslateCommonTerms(false) + , bIsReverseMapping(false) + , bROIsIgnorePostPositionalWord(false) + , bROIsAutoCloseDialog(false) + , bROIsShowEntriesRecentlyUsedFirst(false) + , bROIsAutoReplaceUniqueEntries(false) + , bROIsDirectionToSimplified(false) + , bROIsUseCharacterVariants(false) + , bROIsTranslateCommonTerms(false) + , bROIsReverseMapping(false) + , nDataFilesChangedCheckValue(0) + , bRODataFilesChangedCheckValue(false) + , bIsGrammarAuto(false) + , bIsGrammarInteractive(false) + , bROIsGrammarAuto(false) + , bROIsGrammarInteractive(false) +{ +} + +class SvtLinguConfigItem : public utl::ConfigItem +{ + SvtLinguOptions aOpt; + + static bool GetHdlByName( sal_Int32 &rnHdl, std::u16string_view rPropertyName, bool bFullPropName = false ); + static uno::Sequence< OUString > GetPropertyNames(); + void LoadOptions( const uno::Sequence< OUString > &rProperyNames ); + bool SaveOptions( const uno::Sequence< OUString > &rProperyNames ); + + SvtLinguConfigItem(const SvtLinguConfigItem&) = delete; + SvtLinguConfigItem& operator=(const SvtLinguConfigItem&) = delete; + virtual void ImplCommit() override; + +public: + SvtLinguConfigItem(); + + // utl::ConfigItem + virtual void Notify( const css::uno::Sequence< OUString > &rPropertyNames ) override; + + // make some protected functions of utl::ConfigItem public + using utl::ConfigItem::GetNodeNames; + using utl::ConfigItem::GetProperties; + //using utl::ConfigItem::PutProperties; + //using utl::ConfigItem::SetSetProperties; + using utl::ConfigItem::ReplaceSetProperties; + //using utl::ConfigItem::GetReadOnlyStates; + + css::uno::Any + GetProperty( std::u16string_view rPropertyName ) const; + css::uno::Any + GetProperty( sal_Int32 nPropertyHandle ) const; + + bool SetProperty( std::u16string_view rPropertyName, + const css::uno::Any &rValue ); + bool SetProperty( sal_Int32 nPropertyHandle, + const css::uno::Any &rValue ); + + void GetOptions( SvtLinguOptions& ) const; + + bool IsReadOnly( std::u16string_view rPropertyName ) const; + bool IsReadOnly( sal_Int32 nPropertyHandle ) const; +}; + +SvtLinguConfigItem::SvtLinguConfigItem() : + utl::ConfigItem( "Office.Linguistic" ) +{ + const uno::Sequence< OUString > &rPropertyNames = GetPropertyNames(); + LoadOptions( rPropertyNames ); + ClearModified(); + + // request notify events when properties change + EnableNotification( rPropertyNames ); +} + +void SvtLinguConfigItem::Notify( const uno::Sequence< OUString > &rPropertyNames ) +{ + { + std::unique_lock aGuard(theSvtLinguConfigItemMutex()); + LoadOptions( rPropertyNames ); + } + NotifyListeners(ConfigurationHints::NONE); +} + +void SvtLinguConfigItem::ImplCommit() +{ + SaveOptions( GetPropertyNames() ); +} + +namespace { + +struct NamesToHdl +{ + const char *pFullPropName; // full qualified name as used in configuration + OUString aPropName; // property name only (atom) of above + sal_Int32 nHdl; // numeric handle representing the property +}; + +} + +NamesToHdl const aNamesToHdl[] = +{ +{/* 0 */ "General/DefaultLocale", UPN_DEFAULT_LOCALE, UPH_DEFAULT_LOCALE}, +{/* 1 */ "General/DictionaryList/ActiveDictionaries", UPN_ACTIVE_DICTIONARIES, UPH_ACTIVE_DICTIONARIES}, +{/* 2 */ "General/DictionaryList/IsUseDictionaryList", UPN_IS_USE_DICTIONARY_LIST, UPH_IS_USE_DICTIONARY_LIST}, +{/* 3 */ "General/IsIgnoreControlCharacters", UPN_IS_IGNORE_CONTROL_CHARACTERS, UPH_IS_IGNORE_CONTROL_CHARACTERS}, +{/* 5 */ "General/DefaultLocale_CJK", UPN_DEFAULT_LOCALE_CJK, UPH_DEFAULT_LOCALE_CJK}, +{/* 6 */ "General/DefaultLocale_CTL", UPN_DEFAULT_LOCALE_CTL, UPH_DEFAULT_LOCALE_CTL}, + +{/* 7 */ "SpellChecking/IsSpellUpperCase", UPN_IS_SPELL_UPPER_CASE, UPH_IS_SPELL_UPPER_CASE}, +{/* 8 */ "SpellChecking/IsSpellWithDigits", UPN_IS_SPELL_WITH_DIGITS, UPH_IS_SPELL_WITH_DIGITS}, +{/* 9 */ "SpellChecking/IsSpellAuto", UPN_IS_SPELL_AUTO, UPH_IS_SPELL_AUTO}, +{/* 10 */ "SpellChecking/IsSpellSpecial", UPN_IS_SPELL_SPECIAL, UPH_IS_SPELL_SPECIAL}, +{/* 11 */ "SpellChecking/IsSpellClosedCompound", UPN_IS_SPELL_CLOSED_COMPOUND, UPH_IS_SPELL_CLOSED_COMPOUND}, +{/* 12 */ "SpellChecking/IsSpellHyphenatedCompound", UPN_IS_SPELL_HYPHENATED_COMPOUND, UPH_IS_SPELL_HYPHENATED_COMPOUND}, +{/* 13 */ "SpellChecking/IsReverseDirection", UPN_IS_WRAP_REVERSE, UPH_IS_WRAP_REVERSE}, + +{/* 14 */ "Hyphenation/MinLeading", UPN_HYPH_MIN_LEADING, UPH_HYPH_MIN_LEADING}, +{/* 15 */ "Hyphenation/MinTrailing", UPN_HYPH_MIN_TRAILING, UPH_HYPH_MIN_TRAILING}, +{/* 16 */ "Hyphenation/MinWordLength", UPN_HYPH_MIN_WORD_LENGTH, UPH_HYPH_MIN_WORD_LENGTH}, +{/* 17*/ "Hyphenation/IsHyphSpecial", UPN_IS_HYPH_SPECIAL, UPH_IS_HYPH_SPECIAL}, +{/* 18 */ "Hyphenation/IsHyphAuto", UPN_IS_HYPH_AUTO, UPH_IS_HYPH_AUTO}, + +{/* 19 */ "TextConversion/ActiveConversionDictionaries", UPN_ACTIVE_CONVERSION_DICTIONARIES, UPH_ACTIVE_CONVERSION_DICTIONARIES}, +{/* 20 */ "TextConversion/IsIgnorePostPositionalWord", UPN_IS_IGNORE_POST_POSITIONAL_WORD, UPH_IS_IGNORE_POST_POSITIONAL_WORD}, +{/* 21 */ "TextConversion/IsAutoCloseDialog", UPN_IS_AUTO_CLOSE_DIALOG, UPH_IS_AUTO_CLOSE_DIALOG}, +{/* 22 */ "TextConversion/IsShowEntriesRecentlyUsedFirst", UPN_IS_SHOW_ENTRIES_RECENTLY_USED_FIRST, UPH_IS_SHOW_ENTRIES_RECENTLY_USED_FIRST}, +{/* 23 */ "TextConversion/IsAutoReplaceUniqueEntries", UPN_IS_AUTO_REPLACE_UNIQUE_ENTRIES, UPH_IS_AUTO_REPLACE_UNIQUE_ENTRIES}, +{/* 24 */ "TextConversion/IsDirectionToSimplified", UPN_IS_DIRECTION_TO_SIMPLIFIED, UPH_IS_DIRECTION_TO_SIMPLIFIED}, +{/* 25 */ "TextConversion/IsUseCharacterVariants", UPN_IS_USE_CHARACTER_VARIANTS, UPH_IS_USE_CHARACTER_VARIANTS}, +{/* 26 */ "TextConversion/IsTranslateCommonTerms", UPN_IS_TRANSLATE_COMMON_TERMS, UPH_IS_TRANSLATE_COMMON_TERMS}, +{/* 27 */ "TextConversion/IsReverseMapping", UPN_IS_REVERSE_MAPPING, UPH_IS_REVERSE_MAPPING}, + +{/* 28 */ "ServiceManager/DataFilesChangedCheckValue", UPN_DATA_FILES_CHANGED_CHECK_VALUE, UPH_DATA_FILES_CHANGED_CHECK_VALUE}, + +{/* 29 */ "GrammarChecking/IsAutoCheck", UPN_IS_GRAMMAR_AUTO, UPH_IS_GRAMMAR_AUTO}, +{/* 30 */ "GrammarChecking/IsInteractiveCheck", UPN_IS_GRAMMAR_INTERACTIVE, UPH_IS_GRAMMAR_INTERACTIVE}, + + /* similar to entry 0 (thus no own configuration entry) but with different property name and type */ +{ nullptr, UPN_DEFAULT_LANGUAGE, UPH_DEFAULT_LANGUAGE}, + +{ nullptr, "", -1} +}; + +uno::Sequence< OUString > SvtLinguConfigItem::GetPropertyNames() +{ + uno::Sequence< OUString > aNames; + aNames.realloc(std::size(aNamesToHdl)); + OUString *pNames = aNames.getArray(); + sal_Int32 nIdx = 0; + for (auto const & nameToHdl: aNamesToHdl) + { + const char *pFullPropName = nameToHdl.pFullPropName; + if (pFullPropName) + pNames[ nIdx++ ] = OUString::createFromAscii( pFullPropName ); + } + aNames.realloc( nIdx ); + + return aNames; +} + +bool SvtLinguConfigItem::GetHdlByName( + sal_Int32 &rnHdl, + std::u16string_view rPropertyName, + bool bFullPropName ) +{ + NamesToHdl const *pEntry = &aNamesToHdl[0]; + + if (bFullPropName) + { + while (pEntry && pEntry->pFullPropName != nullptr) + { + if (o3tl::equalsAscii(rPropertyName, pEntry->pFullPropName )) + { + rnHdl = pEntry->nHdl; + break; + } + ++pEntry; + } + return pEntry && pEntry->pFullPropName != nullptr; + } + else + { + while (pEntry && pEntry->pFullPropName != nullptr) + { + if (rPropertyName == pEntry->aPropName ) + { + rnHdl = pEntry->nHdl; + break; + } + ++pEntry; + } + return pEntry && pEntry->pFullPropName != nullptr; + } +} + +uno::Any SvtLinguConfigItem::GetProperty( std::u16string_view rPropertyName ) const +{ + sal_Int32 nHdl; + return GetHdlByName( nHdl, rPropertyName ) ? GetProperty( nHdl ) : uno::Any(); +} + +uno::Any SvtLinguConfigItem::GetProperty( sal_Int32 nPropertyHandle ) const +{ + std::unique_lock aGuard(theSvtLinguConfigItemMutex()); + + uno::Any aRes; + + const sal_Int16 *pnVal = nullptr; + const LanguageType *plVal = nullptr; + const bool *pbVal = nullptr; + const sal_Int32 *pnInt32Val = nullptr; + + const SvtLinguOptions &rOpt = const_cast< SvtLinguConfigItem * >(this)->aOpt; + switch (nPropertyHandle) + { + case UPH_IS_USE_DICTIONARY_LIST : pbVal = &rOpt.bIsUseDictionaryList; break; + case UPH_IS_IGNORE_CONTROL_CHARACTERS : pbVal = &rOpt.bIsIgnoreControlCharacters; break; + case UPH_IS_HYPH_AUTO : pbVal = &rOpt.bIsHyphAuto; break; + case UPH_IS_HYPH_SPECIAL : pbVal = &rOpt.bIsHyphSpecial; break; + case UPH_IS_SPELL_AUTO : pbVal = &rOpt.bIsSpellAuto; break; + case UPH_IS_SPELL_SPECIAL : pbVal = &rOpt.bIsSpellSpecial; break; + case UPH_IS_WRAP_REVERSE : pbVal = &rOpt.bIsSpellReverse; break; + case UPH_DEFAULT_LANGUAGE : plVal = &rOpt.nDefaultLanguage; break; + case UPH_IS_SPELL_CLOSED_COMPOUND: pbVal = &rOpt.bIsSpellClosedCompound; break; + case UPH_IS_SPELL_HYPHENATED_COMPOUND: pbVal = &rOpt.bIsSpellHyphenatedCompound; break; + case UPH_IS_SPELL_WITH_DIGITS : pbVal = &rOpt.bIsSpellWithDigits; break; + case UPH_IS_SPELL_UPPER_CASE : pbVal = &rOpt.bIsSpellUpperCase; break; + case UPH_HYPH_MIN_LEADING : pnVal = &rOpt.nHyphMinLeading; break; + case UPH_HYPH_MIN_TRAILING : pnVal = &rOpt.nHyphMinTrailing; break; + case UPH_HYPH_MIN_WORD_LENGTH : pnVal = &rOpt.nHyphMinWordLength; break; + case UPH_ACTIVE_DICTIONARIES : + { + aRes <<= rOpt.aActiveDics; + break; + } + case UPH_ACTIVE_CONVERSION_DICTIONARIES : + { + aRes <<= rOpt.aActiveConvDics; + break; + } + case UPH_DEFAULT_LOCALE : + { + aRes <<= LanguageTag::convertToLocale( rOpt.nDefaultLanguage, false); + break; + } + case UPH_DEFAULT_LOCALE_CJK : + { + aRes <<= LanguageTag::convertToLocale( rOpt.nDefaultLanguage_CJK, false); + break; + } + case UPH_DEFAULT_LOCALE_CTL : + { + aRes <<= LanguageTag::convertToLocale( rOpt.nDefaultLanguage_CTL, false); + break; + } + case UPH_IS_IGNORE_POST_POSITIONAL_WORD : pbVal = &rOpt.bIsIgnorePostPositionalWord; break; + case UPH_IS_AUTO_CLOSE_DIALOG : pbVal = &rOpt.bIsAutoCloseDialog; break; + case UPH_IS_SHOW_ENTRIES_RECENTLY_USED_FIRST : pbVal = &rOpt.bIsShowEntriesRecentlyUsedFirst; break; + case UPH_IS_AUTO_REPLACE_UNIQUE_ENTRIES : pbVal = &rOpt.bIsAutoReplaceUniqueEntries; break; + + case UPH_IS_DIRECTION_TO_SIMPLIFIED: pbVal = &rOpt.bIsDirectionToSimplified; break; + case UPH_IS_USE_CHARACTER_VARIANTS : pbVal = &rOpt.bIsUseCharacterVariants; break; + case UPH_IS_TRANSLATE_COMMON_TERMS : pbVal = &rOpt.bIsTranslateCommonTerms; break; + case UPH_IS_REVERSE_MAPPING : pbVal = &rOpt.bIsReverseMapping; break; + + case UPH_DATA_FILES_CHANGED_CHECK_VALUE : pnInt32Val = &rOpt.nDataFilesChangedCheckValue; break; + case UPH_IS_GRAMMAR_AUTO: pbVal = &rOpt.bIsGrammarAuto; break; + case UPH_IS_GRAMMAR_INTERACTIVE: pbVal = &rOpt.bIsGrammarInteractive; break; + default : + SAL_WARN( "unotools.config", "unexpected property handle" ); + } + + if (pbVal) + aRes <<= *pbVal; + else if (pnVal) + aRes <<= *pnVal; + else if (plVal) + aRes <<= static_cast<sal_Int16>(static_cast<sal_uInt16>(*plVal)); + else if (pnInt32Val) + aRes <<= *pnInt32Val; + + return aRes; +} + +bool SvtLinguConfigItem::SetProperty( std::u16string_view rPropertyName, const uno::Any &rValue ) +{ + bool bSucc = false; + sal_Int32 nHdl; + if (GetHdlByName( nHdl, rPropertyName )) + bSucc = SetProperty( nHdl, rValue ); + return bSucc; +} + +bool SvtLinguConfigItem::SetProperty( sal_Int32 nPropertyHandle, const uno::Any &rValue ) +{ + std::unique_lock aGuard(theSvtLinguConfigItemMutex()); + + bool bSucc = false; + if (!rValue.hasValue()) + return bSucc; + + bool bMod = false; + + sal_Int16 *pnVal = nullptr; + LanguageType *plVal = nullptr; + bool *pbVal = nullptr; + sal_Int32 *pnInt32Val = nullptr; + + SvtLinguOptions &rOpt = aOpt; + switch (nPropertyHandle) + { + case UPH_IS_USE_DICTIONARY_LIST : pbVal = &rOpt.bIsUseDictionaryList; break; + case UPH_IS_IGNORE_CONTROL_CHARACTERS : pbVal = &rOpt.bIsIgnoreControlCharacters; break; + case UPH_IS_HYPH_AUTO : pbVal = &rOpt.bIsHyphAuto; break; + case UPH_IS_HYPH_SPECIAL : pbVal = &rOpt.bIsHyphSpecial; break; + case UPH_IS_SPELL_AUTO : pbVal = &rOpt.bIsSpellAuto; break; + case UPH_IS_SPELL_SPECIAL : pbVal = &rOpt.bIsSpellSpecial; break; + case UPH_IS_WRAP_REVERSE : pbVal = &rOpt.bIsSpellReverse; break; + case UPH_DEFAULT_LANGUAGE : plVal = &rOpt.nDefaultLanguage; break; + case UPH_IS_SPELL_CLOSED_COMPOUND: pbVal = &rOpt.bIsSpellClosedCompound; break; + case UPH_IS_SPELL_HYPHENATED_COMPOUND: pbVal = &rOpt.bIsSpellHyphenatedCompound; break; + case UPH_IS_SPELL_WITH_DIGITS : pbVal = &rOpt.bIsSpellWithDigits; break; + case UPH_IS_SPELL_UPPER_CASE : pbVal = &rOpt.bIsSpellUpperCase; break; + case UPH_HYPH_MIN_LEADING : pnVal = &rOpt.nHyphMinLeading; break; + case UPH_HYPH_MIN_TRAILING : pnVal = &rOpt.nHyphMinTrailing; break; + case UPH_HYPH_MIN_WORD_LENGTH : pnVal = &rOpt.nHyphMinWordLength; break; + case UPH_ACTIVE_DICTIONARIES : + { + rValue >>= rOpt.aActiveDics; + bMod = true; + break; + } + case UPH_ACTIVE_CONVERSION_DICTIONARIES : + { + rValue >>= rOpt.aActiveConvDics; + bMod = true; + break; + } + case UPH_DEFAULT_LOCALE : + { + bSucc = lcl_SetLocale( rOpt.nDefaultLanguage, rValue ); + bMod = bSucc; + break; + } + case UPH_DEFAULT_LOCALE_CJK : + { + bSucc = lcl_SetLocale( rOpt.nDefaultLanguage_CJK, rValue ); + bMod = bSucc; + break; + } + case UPH_DEFAULT_LOCALE_CTL : + { + bSucc = lcl_SetLocale( rOpt.nDefaultLanguage_CTL, rValue ); + bMod = bSucc; + break; + } + case UPH_IS_IGNORE_POST_POSITIONAL_WORD : pbVal = &rOpt.bIsIgnorePostPositionalWord; break; + case UPH_IS_AUTO_CLOSE_DIALOG : pbVal = &rOpt.bIsAutoCloseDialog; break; + case UPH_IS_SHOW_ENTRIES_RECENTLY_USED_FIRST : pbVal = &rOpt.bIsShowEntriesRecentlyUsedFirst; break; + case UPH_IS_AUTO_REPLACE_UNIQUE_ENTRIES : pbVal = &rOpt.bIsAutoReplaceUniqueEntries; break; + + case UPH_IS_DIRECTION_TO_SIMPLIFIED : pbVal = &rOpt.bIsDirectionToSimplified; break; + case UPH_IS_USE_CHARACTER_VARIANTS : pbVal = &rOpt.bIsUseCharacterVariants; break; + case UPH_IS_TRANSLATE_COMMON_TERMS : pbVal = &rOpt.bIsTranslateCommonTerms; break; + case UPH_IS_REVERSE_MAPPING : pbVal = &rOpt.bIsReverseMapping; break; + + case UPH_DATA_FILES_CHANGED_CHECK_VALUE : pnInt32Val = &rOpt.nDataFilesChangedCheckValue; break; + case UPH_IS_GRAMMAR_AUTO: pbVal = &rOpt.bIsGrammarAuto; break; + case UPH_IS_GRAMMAR_INTERACTIVE: pbVal = &rOpt.bIsGrammarInteractive; break; + default : + SAL_WARN( "unotools.config", "unexpected property handle" ); + } + + if (pbVal) + { + bool bNew = bool(); + if (rValue >>= bNew) + { + if (bNew != *pbVal) + { + *pbVal = bNew; + bMod = true; + } + bSucc = true; + } + } + else if (pnVal) + { + sal_Int16 nNew = sal_Int16(); + if (rValue >>= nNew) + { + if (nNew != *pnVal) + { + *pnVal = nNew; + bMod = true; + } + bSucc = true; + } + } + else if (plVal) + { + sal_Int16 nNew = sal_Int16(); + if (rValue >>= nNew) + { + if (nNew != static_cast<sal_uInt16>(*plVal)) + { + *plVal = LanguageType(static_cast<sal_uInt16>(nNew)); + bMod = true; + } + bSucc = true; + } + } + else if (pnInt32Val) + { + sal_Int32 nNew = sal_Int32(); + if (rValue >>= nNew) + { + if (nNew != *pnInt32Val) + { + *pnInt32Val = nNew; + bMod = true; + } + bSucc = true; + } + } + + if (bMod) + SetModified(); + + NotifyListeners(ConfigurationHints::NONE); + return bSucc; +} + +void SvtLinguConfigItem::GetOptions(SvtLinguOptions &rOptions) const +{ + std::unique_lock aGuard(theSvtLinguConfigItemMutex()); + rOptions = aOpt; +} + +void SvtLinguConfigItem::LoadOptions( const uno::Sequence< OUString > &rProperyNames ) +{ + bool bRes = false; + + const OUString *pProperyNames = rProperyNames.getConstArray(); + sal_Int32 nProps = rProperyNames.getLength(); + + const uno::Sequence< uno::Any > aValues = GetProperties( rProperyNames ); + const uno::Sequence< sal_Bool > aROStates = GetReadOnlyStates( rProperyNames ); + + if (nProps && aValues.getLength() == nProps && aROStates.getLength() == nProps) + { + SvtLinguOptions &rOpt = aOpt; + + const uno::Any *pValue = aValues.getConstArray(); + const sal_Bool *pROStates = aROStates.getConstArray(); + for (sal_Int32 i = 0; i < nProps; ++i) + { + const uno::Any &rVal = pValue[i]; + sal_Int32 nPropertyHandle(0); + GetHdlByName( nPropertyHandle, pProperyNames[i], true ); + switch ( nPropertyHandle ) + { + case UPH_DEFAULT_LOCALE : + { rOpt.bRODefaultLanguage = pROStates[i]; rOpt.nDefaultLanguage = lcl_CfgAnyToLanguage( rVal ); } break; + case UPH_ACTIVE_DICTIONARIES : + { rOpt.bROActiveDics = pROStates[i]; rVal >>= rOpt.aActiveDics; } break; + case UPH_IS_USE_DICTIONARY_LIST : + { rOpt.bROIsUseDictionaryList = pROStates[i]; rVal >>= rOpt.bIsUseDictionaryList; } break; + case UPH_IS_IGNORE_CONTROL_CHARACTERS : + { rOpt.bROIsIgnoreControlCharacters = pROStates[i]; rVal >>= rOpt.bIsIgnoreControlCharacters; } break; + case UPH_DEFAULT_LOCALE_CJK : + { rOpt.bRODefaultLanguage_CJK = pROStates[i]; rOpt.nDefaultLanguage_CJK = lcl_CfgAnyToLanguage( rVal ); } break; + case UPH_DEFAULT_LOCALE_CTL : + { rOpt.bRODefaultLanguage_CTL = pROStates[i]; rOpt.nDefaultLanguage_CTL = lcl_CfgAnyToLanguage( rVal ); } break; + + case UPH_IS_SPELL_UPPER_CASE : + { rOpt.bROIsSpellUpperCase = pROStates[i]; rVal >>= rOpt.bIsSpellUpperCase; } break; + case UPH_IS_SPELL_WITH_DIGITS : + { rOpt.bROIsSpellWithDigits = pROStates[i]; rVal >>= rOpt.bIsSpellWithDigits; } break; + case UPH_IS_SPELL_CLOSED_COMPOUND : + { rOpt.bROIsSpellClosedCompound = pROStates[i]; rVal >>= rOpt.bIsSpellClosedCompound; } break; + case UPH_IS_SPELL_HYPHENATED_COMPOUND : + { rOpt.bROIsSpellHyphenatedCompound = pROStates[i]; rVal >>= rOpt.bIsSpellHyphenatedCompound; } break; + + case UPH_IS_SPELL_AUTO : + { rOpt.bROIsSpellAuto = pROStates[i]; rVal >>= rOpt.bIsSpellAuto; } break; + case UPH_IS_SPELL_SPECIAL : + { rOpt.bROIsSpellSpecial = pROStates[i]; rVal >>= rOpt.bIsSpellSpecial; } break; + case UPH_IS_WRAP_REVERSE : + { rOpt.bROIsSpellReverse = pROStates[i]; rVal >>= rOpt.bIsSpellReverse; } break; + + case UPH_HYPH_MIN_LEADING : + { rOpt.bROHyphMinLeading = pROStates[i]; rVal >>= rOpt.nHyphMinLeading; } break; + case UPH_HYPH_MIN_TRAILING : + { rOpt.bROHyphMinTrailing = pROStates[i]; rVal >>= rOpt.nHyphMinTrailing; } break; + case UPH_HYPH_MIN_WORD_LENGTH : + { rOpt.bROHyphMinWordLength = pROStates[i]; rVal >>= rOpt.nHyphMinWordLength; } break; + case UPH_IS_HYPH_SPECIAL : + { rOpt.bROIsHyphSpecial = pROStates[i]; rVal >>= rOpt.bIsHyphSpecial; } break; + case UPH_IS_HYPH_AUTO : + { rOpt.bROIsHyphAuto = pROStates[i]; rVal >>= rOpt.bIsHyphAuto; } break; + + case UPH_ACTIVE_CONVERSION_DICTIONARIES : { rOpt.bROActiveConvDics = pROStates[i]; rVal >>= rOpt.aActiveConvDics; } break; + + case UPH_IS_IGNORE_POST_POSITIONAL_WORD : + { rOpt.bROIsIgnorePostPositionalWord = pROStates[i]; rVal >>= rOpt.bIsIgnorePostPositionalWord; } break; + case UPH_IS_AUTO_CLOSE_DIALOG : + { rOpt.bROIsAutoCloseDialog = pROStates[i]; rVal >>= rOpt.bIsAutoCloseDialog; } break; + case UPH_IS_SHOW_ENTRIES_RECENTLY_USED_FIRST : + { rOpt.bROIsShowEntriesRecentlyUsedFirst = pROStates[i]; rVal >>= rOpt.bIsShowEntriesRecentlyUsedFirst; } break; + case UPH_IS_AUTO_REPLACE_UNIQUE_ENTRIES : + { rOpt.bROIsAutoReplaceUniqueEntries = pROStates[i]; rVal >>= rOpt.bIsAutoReplaceUniqueEntries; } break; + + case UPH_IS_DIRECTION_TO_SIMPLIFIED : + { + rOpt.bROIsDirectionToSimplified = pROStates[i]; + if( ! (rVal >>= rOpt.bIsDirectionToSimplified) ) + { + //default is locale dependent: + if (MsLangId::isTraditionalChinese(rOpt.nDefaultLanguage_CJK)) + { + rOpt.bIsDirectionToSimplified = false; + } + else + { + rOpt.bIsDirectionToSimplified = true; + } + } + } break; + case UPH_IS_USE_CHARACTER_VARIANTS : + { rOpt.bROIsUseCharacterVariants = pROStates[i]; rVal >>= rOpt.bIsUseCharacterVariants; } break; + case UPH_IS_TRANSLATE_COMMON_TERMS : + { rOpt.bROIsTranslateCommonTerms = pROStates[i]; rVal >>= rOpt.bIsTranslateCommonTerms; } break; + case UPH_IS_REVERSE_MAPPING : + { rOpt.bROIsReverseMapping = pROStates[i]; rVal >>= rOpt.bIsReverseMapping; } break; + + case UPH_DATA_FILES_CHANGED_CHECK_VALUE : + { rOpt.bRODataFilesChangedCheckValue = pROStates[i]; rVal >>= rOpt.nDataFilesChangedCheckValue; } break; + + case UPH_IS_GRAMMAR_AUTO: + { rOpt.bROIsGrammarAuto = pROStates[i]; rVal >>= rOpt.bIsGrammarAuto; } + break; + case UPH_IS_GRAMMAR_INTERACTIVE: + { rOpt.bROIsGrammarInteractive = pROStates[i]; rVal >>= rOpt.bIsGrammarInteractive; } + break; + + default: + SAL_WARN( "unotools.config", "unexpected case" ); + } + } + + bRes = true; + } + DBG_ASSERT( bRes, "LoadOptions failed" ); +} + +bool SvtLinguConfigItem::SaveOptions( const uno::Sequence< OUString > &rProperyNames ) +{ + if (!IsModified()) + return true; + + std::unique_lock aGuard(theSvtLinguConfigItemMutex()); + + bool bRet = false; + + sal_Int32 nProps = rProperyNames.getLength(); + uno::Sequence< uno::Any > aValues( nProps ); + uno::Any *pValue = aValues.getArray(); + + if (nProps && aValues.getLength() == nProps) + { + const SvtLinguOptions &rOpt = aOpt; + + OUString aTmp( lcl_LanguageToCfgLocaleStr( rOpt.nDefaultLanguage ) ); + *pValue++ <<= aTmp; // 0 + *pValue++ <<= rOpt.aActiveDics; // 1 + *pValue++ <<= rOpt.bIsUseDictionaryList; // 2 + *pValue++ <<= rOpt.bIsIgnoreControlCharacters; // 3 + aTmp = lcl_LanguageToCfgLocaleStr( rOpt.nDefaultLanguage_CJK ); + *pValue++ <<= aTmp; // 5 + aTmp = lcl_LanguageToCfgLocaleStr( rOpt.nDefaultLanguage_CTL ); + *pValue++ <<= aTmp; // 6 + + *pValue++ <<= rOpt.bIsSpellUpperCase; // 7 + *pValue++ <<= rOpt.bIsSpellWithDigits; // 8 + *pValue++ <<= rOpt.bIsSpellAuto; // 9 + *pValue++ <<= rOpt.bIsSpellSpecial; // 10 + *pValue++ <<= rOpt.bIsSpellClosedCompound; // 11 + *pValue++ <<= rOpt.bIsSpellHyphenatedCompound; // 12 + *pValue++ <<= rOpt.bIsSpellReverse; // 13 + + *pValue++ <<= rOpt.nHyphMinLeading; // 14 + *pValue++ <<= rOpt.nHyphMinTrailing; // 15 + *pValue++ <<= rOpt.nHyphMinWordLength; // 16 + *pValue++ <<= rOpt.bIsHyphSpecial; // 17 + *pValue++ <<= rOpt.bIsHyphAuto; // 18 + + *pValue++ <<= rOpt.aActiveConvDics; // 19 + + *pValue++ <<= rOpt.bIsIgnorePostPositionalWord; // 20 + *pValue++ <<= rOpt.bIsAutoCloseDialog; // 21 + *pValue++ <<= rOpt.bIsShowEntriesRecentlyUsedFirst; // 22 + *pValue++ <<= rOpt.bIsAutoReplaceUniqueEntries; // 23 + + *pValue++ <<= rOpt.bIsDirectionToSimplified; // 24 + *pValue++ <<= rOpt.bIsUseCharacterVariants; // 25 + *pValue++ <<= rOpt.bIsTranslateCommonTerms; // 26 + *pValue++ <<= rOpt.bIsReverseMapping; // 27 + + *pValue++ <<= rOpt.nDataFilesChangedCheckValue; // 28 + *pValue++ <<= rOpt.bIsGrammarAuto; // 29 + *pValue++ <<= rOpt.bIsGrammarInteractive; // 30 + + bRet |= PutProperties( rProperyNames, aValues ); + } + + if (bRet) + ClearModified(); + + return bRet; +} + +bool SvtLinguConfigItem::IsReadOnly( std::u16string_view rPropertyName ) const +{ + bool bReadOnly = false; + sal_Int32 nHdl; + if (GetHdlByName( nHdl, rPropertyName )) + bReadOnly = IsReadOnly( nHdl ); + return bReadOnly; +} + +bool SvtLinguConfigItem::IsReadOnly( sal_Int32 nPropertyHandle ) const +{ + std::unique_lock aGuard(theSvtLinguConfigItemMutex()); + + bool bReadOnly = false; + + const SvtLinguOptions &rOpt = const_cast< SvtLinguConfigItem * >(this)->aOpt; + switch(nPropertyHandle) + { + case UPH_IS_USE_DICTIONARY_LIST : bReadOnly = rOpt.bROIsUseDictionaryList; break; + case UPH_IS_IGNORE_CONTROL_CHARACTERS : bReadOnly = rOpt.bROIsIgnoreControlCharacters; break; + case UPH_IS_HYPH_AUTO : bReadOnly = rOpt.bROIsHyphAuto; break; + case UPH_IS_HYPH_SPECIAL : bReadOnly = rOpt.bROIsHyphSpecial; break; + case UPH_IS_SPELL_AUTO : bReadOnly = rOpt.bROIsSpellAuto; break; + case UPH_IS_SPELL_SPECIAL : bReadOnly = rOpt.bROIsSpellSpecial; break; + case UPH_IS_WRAP_REVERSE : bReadOnly = rOpt.bROIsSpellReverse; break; + case UPH_DEFAULT_LANGUAGE : bReadOnly = rOpt.bRODefaultLanguage; break; + case UPH_IS_SPELL_CLOSED_COMPOUND : bReadOnly = rOpt.bROIsSpellClosedCompound; break; + case UPH_IS_SPELL_HYPHENATED_COMPOUND : bReadOnly = rOpt.bROIsSpellHyphenatedCompound; break; + case UPH_IS_SPELL_WITH_DIGITS : bReadOnly = rOpt.bROIsSpellWithDigits; break; + case UPH_IS_SPELL_UPPER_CASE : bReadOnly = rOpt.bROIsSpellUpperCase; break; + case UPH_HYPH_MIN_LEADING : bReadOnly = rOpt.bROHyphMinLeading; break; + case UPH_HYPH_MIN_TRAILING : bReadOnly = rOpt.bROHyphMinTrailing; break; + case UPH_HYPH_MIN_WORD_LENGTH : bReadOnly = rOpt.bROHyphMinWordLength; break; + case UPH_ACTIVE_DICTIONARIES : bReadOnly = rOpt.bROActiveDics; break; + case UPH_ACTIVE_CONVERSION_DICTIONARIES : bReadOnly = rOpt.bROActiveConvDics; break; + case UPH_DEFAULT_LOCALE : bReadOnly = rOpt.bRODefaultLanguage; break; + case UPH_DEFAULT_LOCALE_CJK : bReadOnly = rOpt.bRODefaultLanguage_CJK; break; + case UPH_DEFAULT_LOCALE_CTL : bReadOnly = rOpt.bRODefaultLanguage_CTL; break; + case UPH_IS_IGNORE_POST_POSITIONAL_WORD : bReadOnly = rOpt.bROIsIgnorePostPositionalWord; break; + case UPH_IS_AUTO_CLOSE_DIALOG : bReadOnly = rOpt.bROIsAutoCloseDialog; break; + case UPH_IS_SHOW_ENTRIES_RECENTLY_USED_FIRST : bReadOnly = rOpt.bROIsShowEntriesRecentlyUsedFirst; break; + case UPH_IS_AUTO_REPLACE_UNIQUE_ENTRIES : bReadOnly = rOpt.bROIsAutoReplaceUniqueEntries; break; + case UPH_IS_DIRECTION_TO_SIMPLIFIED : bReadOnly = rOpt.bROIsDirectionToSimplified; break; + case UPH_IS_USE_CHARACTER_VARIANTS : bReadOnly = rOpt.bROIsUseCharacterVariants; break; + case UPH_IS_TRANSLATE_COMMON_TERMS : bReadOnly = rOpt.bROIsTranslateCommonTerms; break; + case UPH_IS_REVERSE_MAPPING : bReadOnly = rOpt.bROIsReverseMapping; break; + case UPH_DATA_FILES_CHANGED_CHECK_VALUE : bReadOnly = rOpt.bRODataFilesChangedCheckValue; break; + case UPH_IS_GRAMMAR_AUTO: bReadOnly = rOpt.bROIsGrammarAuto; break; + case UPH_IS_GRAMMAR_INTERACTIVE: bReadOnly = rOpt.bROIsGrammarInteractive; break; + default : + SAL_WARN( "unotools.config", "unexpected property handle" ); + } + return bReadOnly; +} + +static SvtLinguConfigItem *pCfgItem = nullptr; +static sal_Int32 nCfgItemRefCount = 0; + +constexpr OUString aG_Dictionaries = u"Dictionaries"_ustr; + +SvtLinguConfig::SvtLinguConfig() +{ + // Global access, must be guarded (multithreading) + std::unique_lock aGuard(theSvtLinguConfigItemMutex()); + ++nCfgItemRefCount; +} + +SvtLinguConfig::~SvtLinguConfig() +{ + if (pCfgItem && pCfgItem->IsModified()) + pCfgItem->Commit(); + + std::unique_lock aGuard(theSvtLinguConfigItemMutex()); + + if (--nCfgItemRefCount <= 0) + { + delete pCfgItem; + pCfgItem = nullptr; + } +} + +SvtLinguConfigItem & SvtLinguConfig::GetConfigItem() +{ + // Global access, must be guarded (multithreading) + std::unique_lock aGuard(theSvtLinguConfigItemMutex()); + if (!pCfgItem) + { + pCfgItem = new SvtLinguConfigItem; + aGuard.unlock(); + ItemHolder1::holdConfigItem(EItem::LinguConfig); + } + return *pCfgItem; +} + +uno::Sequence< OUString > SvtLinguConfig::GetNodeNames( const OUString &rNode ) const +{ + return GetConfigItem().GetNodeNames( rNode ); +} + +uno::Sequence< uno::Any > SvtLinguConfig::GetProperties( const uno::Sequence< OUString > &rNames ) const +{ + return GetConfigItem().GetProperties(rNames); +} + +bool SvtLinguConfig::ReplaceSetProperties( + const OUString &rNode, const uno::Sequence< beans::PropertyValue >& rValues ) +{ + return GetConfigItem().ReplaceSetProperties( rNode, rValues ); +} + +uno::Any SvtLinguConfig::GetProperty( std::u16string_view rPropertyName ) const +{ + return GetConfigItem().GetProperty( rPropertyName ); +} + +uno::Any SvtLinguConfig::GetProperty( sal_Int32 nPropertyHandle ) const +{ + return GetConfigItem().GetProperty( nPropertyHandle ); +} + +bool SvtLinguConfig::SetProperty( std::u16string_view rPropertyName, const uno::Any &rValue ) +{ + return GetConfigItem().SetProperty( rPropertyName, rValue ); +} + +bool SvtLinguConfig::SetProperty( sal_Int32 nPropertyHandle, const uno::Any &rValue ) +{ + return GetConfigItem().SetProperty( nPropertyHandle, rValue ); +} + +void SvtLinguConfig::GetOptions( SvtLinguOptions &rOptions ) const +{ + GetConfigItem().GetOptions(rOptions); +} + +bool SvtLinguConfig::IsReadOnly( std::u16string_view rPropertyName ) const +{ + return GetConfigItem().IsReadOnly( rPropertyName ); +} + +bool SvtLinguConfig::GetElementNamesFor( + const OUString &rNodeName, + uno::Sequence< OUString > &rElementNames ) const +{ + bool bSuccess = false; + try + { + uno::Reference< container::XNameAccess > xNA( GetMainUpdateAccess(), uno::UNO_QUERY_THROW ); + xNA.set( xNA->getByName("ServiceManager"), uno::UNO_QUERY_THROW ); + xNA.set( xNA->getByName( rNodeName ), uno::UNO_QUERY_THROW ); + rElementNames = xNA->getElementNames(); + bSuccess = true; + } + catch (uno::Exception &) + { + } + return bSuccess; +} + +bool SvtLinguConfig::GetSupportedDictionaryFormatsFor( + const OUString &rSetName, + const OUString &rSetEntry, + uno::Sequence< OUString > &rFormatList ) const +{ + if (rSetName.isEmpty() || rSetEntry.isEmpty()) + return false; + bool bSuccess = false; + try + { + uno::Reference< container::XNameAccess > xNA( GetMainUpdateAccess(), uno::UNO_QUERY_THROW ); + xNA.set( xNA->getByName("ServiceManager"), uno::UNO_QUERY_THROW ); + xNA.set( xNA->getByName( rSetName ), uno::UNO_QUERY_THROW ); + xNA.set( xNA->getByName( rSetEntry ), uno::UNO_QUERY_THROW ); + if (xNA->getByName( "SupportedDictionaryFormats" ) >>= rFormatList) + bSuccess = true; + DBG_ASSERT( rFormatList.hasElements(), "supported dictionary format list is empty" ); + } + catch (uno::Exception &) + { + } + return bSuccess; +} + +bool SvtLinguConfig::GetLocaleListFor( const OUString &rSetName, const OUString &rSetEntry, css::uno::Sequence< OUString > &rLocaleList ) const +{ + if (rSetName.isEmpty() || rSetEntry.isEmpty()) + return false; + bool bSuccess = false; + try + { + uno::Reference< container::XNameAccess > xNA( GetMainUpdateAccess(), uno::UNO_QUERY_THROW ); + xNA.set( xNA->getByName("ServiceManager"), uno::UNO_QUERY_THROW ); + xNA.set( xNA->getByName( rSetName ), uno::UNO_QUERY_THROW ); + xNA.set( xNA->getByName( rSetEntry ), uno::UNO_QUERY_THROW ); + if (xNA->getByName( "Locales" ) >>= rLocaleList) + bSuccess = true; + DBG_ASSERT( rLocaleList.hasElements(), "Locale list is empty" ); + } + catch (uno::Exception &) + { + } + return bSuccess; +} + +static bool lcl_GetFileUrlFromOrigin( + OUString /*out*/ &rFileUrl, + const OUString &rOrigin ) +{ + OUString aURL( + comphelper::getExpandedUri( + comphelper::getProcessComponentContext(), rOrigin)); + if (aURL.startsWith( FILE_PROTOCOL )) + { + rFileUrl = aURL; + return true; + } + else + { + SAL_WARN( + "unotools.config", "not a file URL, <" << aURL << ">" ); + return false; + } +} + +bool SvtLinguConfig::GetDictionaryEntry( + const OUString &rNodeName, + SvtLinguConfigDictionaryEntry &rDicEntry ) const +{ + if (rNodeName.isEmpty()) + return false; + bool bSuccess = false; + try + { + uno::Reference< container::XNameAccess > xNA( GetMainUpdateAccess(), uno::UNO_QUERY_THROW ); + xNA.set( xNA->getByName("ServiceManager"), uno::UNO_QUERY_THROW ); + xNA.set( xNA->getByName( aG_Dictionaries ), uno::UNO_QUERY_THROW ); + xNA.set( xNA->getByName( rNodeName ), uno::UNO_QUERY_THROW ); + + // read group data... + uno::Sequence< OUString > aLocations; + OUString aFormatName; + uno::Sequence< OUString > aLocaleNames; + bSuccess = (xNA->getByName( "Locations" ) >>= aLocations) && + (xNA->getByName( "Format" ) >>= aFormatName) && + (xNA->getByName( "Locales" ) >>= aLocaleNames); + DBG_ASSERT( aLocations.hasElements(), "Dictionary locations not set" ); + DBG_ASSERT( !aFormatName.isEmpty(), "Dictionary format name not set" ); + DBG_ASSERT( aLocaleNames.hasElements(), "No locales set for the dictionary" ); + + // if successful continue + if (bSuccess) + { + // get file URL's for the locations + for (OUString& rLocation : asNonConstRange(aLocations)) + { + if (!lcl_GetFileUrlFromOrigin( rLocation, rLocation )) + bSuccess = false; + } + + // if everything was fine return the result + if (bSuccess) + { + rDicEntry.aLocations = aLocations; + rDicEntry.aFormatName = aFormatName; + rDicEntry.aLocaleNames = aLocaleNames; + } + } + } + catch (uno::Exception &) + { + } + return bSuccess; +} + +uno::Sequence< OUString > SvtLinguConfig::GetDisabledDictionaries() const +{ + uno::Sequence< OUString > aResult; + try + { + uno::Reference< container::XNameAccess > xNA( GetMainUpdateAccess(), uno::UNO_QUERY_THROW ); + xNA.set( xNA->getByName("ServiceManager"), uno::UNO_QUERY_THROW ); + xNA->getByName( "DisabledDictionaries" ) >>= aResult; + } + catch (uno::Exception &) + { + } + return aResult; +} + +std::vector< SvtLinguConfigDictionaryEntry > SvtLinguConfig::GetActiveDictionariesByFormat( + std::u16string_view rFormatName ) const +{ + std::vector< SvtLinguConfigDictionaryEntry > aRes; + if (rFormatName.empty()) + return aRes; + + try + { + uno::Sequence< OUString > aElementNames; + GetElementNamesFor( aG_Dictionaries, aElementNames ); + + const uno::Sequence< OUString > aDisabledDics( GetDisabledDictionaries() ); + + SvtLinguConfigDictionaryEntry aDicEntry; + for (const OUString& rElementName : std::as_const(aElementNames)) + { + // does dictionary match the format we are looking for? + if (GetDictionaryEntry( rElementName, aDicEntry ) && + aDicEntry.aFormatName == rFormatName) + { + // check if it is active or not + bool bDicIsActive = std::none_of(aDisabledDics.begin(), aDisabledDics.end(), + [&rElementName](const OUString& rDic) { return rDic == rElementName; }); + + if (bDicIsActive) + { + DBG_ASSERT( !aDicEntry.aFormatName.isEmpty(), + "FormatName not set" ); + DBG_ASSERT( aDicEntry.aLocations.hasElements(), + "Locations not set" ); + DBG_ASSERT( aDicEntry.aLocaleNames.hasElements(), + "Locales not set" ); + aRes.push_back( aDicEntry ); + } + } + } + } + catch (uno::Exception &) + { + } + + return aRes; +} + +uno::Reference< util::XChangesBatch > const & SvtLinguConfig::GetMainUpdateAccess() const +{ + if (!m_xMainUpdateAccess.is()) + { + try + { + // get configuration provider + uno::Reference< uno::XComponentContext > xContext = comphelper::getProcessComponentContext(); + uno::Reference< lang::XMultiServiceFactory > xConfigurationProvider = + configuration::theDefaultProvider::get( xContext ); + + // get configuration update access + beans::PropertyValue aValue; + aValue.Name = "nodepath"; + aValue.Value <<= OUString("org.openoffice.Office.Linguistic"); + uno::Sequence< uno::Any > aProps{ uno::Any(aValue) }; + m_xMainUpdateAccess.set( + xConfigurationProvider->createInstanceWithArguments( + "com.sun.star.configuration.ConfigurationUpdateAccess", aProps), + uno::UNO_QUERY_THROW ); + } + catch (uno::Exception &) + { + } + } + + return m_xMainUpdateAccess; +} + +OUString SvtLinguConfig::GetVendorImageUrl_Impl( + const OUString &rServiceImplName, + const OUString &rImageName ) const +{ + OUString aRes; + try + { + uno::Reference< container::XNameAccess > xImagesNA( GetMainUpdateAccess(), uno::UNO_QUERY_THROW ); + xImagesNA.set( xImagesNA->getByName("Images"), uno::UNO_QUERY_THROW ); + + uno::Reference< container::XNameAccess > xNA( xImagesNA->getByName("ServiceNameEntries"), uno::UNO_QUERY_THROW ); + xNA.set( xNA->getByName( rServiceImplName ), uno::UNO_QUERY_THROW ); + uno::Any aAny(xNA->getByName("VendorImagesNode")); + OUString aVendorImagesNode; + if (aAny >>= aVendorImagesNode) + { + xNA = xImagesNA; + xNA.set( xNA->getByName("VendorImages"), uno::UNO_QUERY_THROW ); + xNA.set( xNA->getByName( aVendorImagesNode ), uno::UNO_QUERY_THROW ); + aAny = xNA->getByName( rImageName ); + OUString aTmp; + if (aAny >>= aTmp) + { + if (lcl_GetFileUrlFromOrigin( aTmp, aTmp )) + aRes = aTmp; + } + } + } + catch (uno::Exception &) + { + DBG_UNHANDLED_EXCEPTION("unotools"); + } + return aRes; +} + +OUString SvtLinguConfig::GetSpellAndGrammarContextSuggestionImage( + const OUString &rServiceImplName +) const +{ + OUString aRes; + if (!rServiceImplName.isEmpty()) + { + aRes = GetVendorImageUrl_Impl( rServiceImplName, "SpellAndGrammarContextMenuSuggestionImage" ); + } + return aRes; +} + +OUString SvtLinguConfig::GetSpellAndGrammarContextDictionaryImage( + const OUString &rServiceImplName +) const +{ + OUString aRes; + if (!rServiceImplName.isEmpty()) + { + aRes = GetVendorImageUrl_Impl( rServiceImplName, "SpellAndGrammarContextMenuDictionaryImage" ); + } + return aRes; +} + +OUString SvtLinguConfig::GetSynonymsContextImage( + const OUString &rServiceImplName +) const +{ + OUString aRes; + if (!rServiceImplName.isEmpty()) + { + OUString aPath( GetVendorImageUrl_Impl( rServiceImplName, "SynonymsContextMenuImage" ) ); + aRes = aPath; + } + return aRes; +} + +bool SvtLinguConfig::HasGrammarChecker() const +{ + bool bRes = false; + + try + { + uno::Reference< container::XNameAccess > xNA( GetMainUpdateAccess(), uno::UNO_QUERY_THROW ); + xNA.set( xNA->getByName("ServiceManager"), uno::UNO_QUERY_THROW ); + xNA.set( xNA->getByName("GrammarCheckerList"), uno::UNO_QUERY_THROW ); + + uno::Sequence< OUString > aElementNames( xNA->getElementNames() ); + bRes = aElementNames.hasElements(); + } + catch (const uno::Exception&) + { + } + + return bRes; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/config/moduleoptions.cxx b/unotools/source/config/moduleoptions.cxx new file mode 100644 index 0000000000..5918b749e6 --- /dev/null +++ b/unotools/source/config/moduleoptions.cxx @@ -0,0 +1,1117 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <string_view> + +#include <unotools/moduleoptions.hxx> +#include <comphelper/sequenceashashmap.hxx> +#include <unotools/configitem.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/sequence.hxx> +#include <osl/diagnose.h> +#include <o3tl/enumarray.hxx> +#include <o3tl/string_view.hxx> +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/frame/XModel.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/document/XTypeDetection.hpp> +#include <com/sun/star/util/PathSubstitution.hpp> +#include <com/sun/star/util/XStringSubstitution.hpp> + +#include "itemholder1.hxx" + +/*-************************************************************************************************************ + @descr These values are used to define necessary keys from our configuration management to support + all functionality of these implementation. + It's a fast way to make changes if some keys change its name or location! + + Property handle are necessary to specify right position in return list of configuration + for asked values. We ask it with a list of properties to get its values. The returned list + has the same order like our given name list! + e.g.: + NAMELIST[ PROPERTYHANDLE_xxx ] => VALUELIST[ PROPERTYHANDLE_xxx ] +*//*-*************************************************************************************************************/ +constexpr OUStringLiteral ROOTNODE_FACTORIES = u"Setup/Office/Factories"; +#define PATHSEPARATOR "/" + +// Attention: The property "ooSetupFactoryEmptyDocumentURL" is read from configuration but not used! There is +// special code that uses hard coded strings to return them. +#define PROPERTYNAME_SHORTNAME "ooSetupFactoryShortName" +#define PROPERTYNAME_TEMPLATEFILE "ooSetupFactoryTemplateFile" +#define PROPERTYNAME_WINDOWATTRIBUTES "ooSetupFactoryWindowAttributes" +#define PROPERTYNAME_EMPTYDOCUMENTURL "ooSetupFactoryEmptyDocumentURL" +#define PROPERTYNAME_DEFAULTFILTER "ooSetupFactoryDefaultFilter" +#define PROPERTYNAME_ICON "ooSetupFactoryIcon" + +#define PROPERTYHANDLE_SHORTNAME 0 +#define PROPERTYHANDLE_TEMPLATEFILE 1 +#define PROPERTYHANDLE_WINDOWATTRIBUTES 2 +#define PROPERTYHANDLE_EMPTYDOCUMENTURL 3 +#define PROPERTYHANDLE_DEFAULTFILTER 4 +#define PROPERTYHANDLE_ICON 5 + +#define PROPERTYCOUNT 6 + +constexpr OUString FACTORYNAME_WRITER = u"com.sun.star.text.TextDocument"_ustr; +constexpr OUString FACTORYNAME_WRITERWEB = u"com.sun.star.text.WebDocument"_ustr; +constexpr OUString FACTORYNAME_WRITERGLOBAL = u"com.sun.star.text.GlobalDocument"_ustr; +constexpr OUString FACTORYNAME_CALC = u"com.sun.star.sheet.SpreadsheetDocument"_ustr; +constexpr OUString FACTORYNAME_DRAW = u"com.sun.star.drawing.DrawingDocument"_ustr; +constexpr OUString FACTORYNAME_IMPRESS = u"com.sun.star.presentation.PresentationDocument"_ustr; +constexpr OUString FACTORYNAME_MATH = u"com.sun.star.formula.FormulaProperties"_ustr; +constexpr OUString FACTORYNAME_CHART = u"com.sun.star.chart2.ChartDocument"_ustr; +constexpr OUString FACTORYNAME_DATABASE = u"com.sun.star.sdb.OfficeDatabaseDocument"_ustr; +constexpr OUString FACTORYNAME_STARTMODULE = u"com.sun.star.frame.StartModule"_ustr; +constexpr OUString FACTORYNAME_BASIC = u"com.sun.star.script.BasicIDE"_ustr; + +#define FACTORYCOUNT 11 + +namespace { + +/*-************************************************************************************************************ + @descr This struct hold information about one factory. We declare a complete array which can hold infos + for all well known factories. Values of enum "EFactory" (see header!) are directly used as index! + So we can support a fast access on this information. +*//*-*************************************************************************************************************/ +struct FactoryInfo +{ + public: + + // initialize empty struct + FactoryInfo() + { + free(); + } + + // easy way to reset struct member! + void free() + { + bInstalled = false; + sFactory.clear(); + sTemplateFile.clear(); + sDefaultFilter.clear(); + nIcon = 0; + bChangedTemplateFile = false; + bChangedDefaultFilter = false; + bDefaultFilterReadonly = false; + } + + // returns list of properties, which has changed only! + // We use given value of sNodeBase to build full qualified paths ... + // Last sign of it must be "/". because we use it directly, without any additional things! + css::uno::Sequence< css::beans::PropertyValue > getChangedProperties( std::u16string_view sNodeBase ) + { + // a) reserve memory for max. count of changed properties + // b) add names and values of changed ones only and count it + // c) resize return list by using count + css::uno::Sequence< css::beans::PropertyValue > lProperties ( 4 ); + auto plProperties = lProperties.getArray(); + sal_Int8 nRealyChanged = 0; + + if( bChangedTemplateFile ) + { + plProperties[nRealyChanged].Name + = OUString::Concat(sNodeBase) + PROPERTYNAME_TEMPLATEFILE; + + if ( !sTemplateFile.isEmpty() ) + { + plProperties[nRealyChanged].Value + <<= getStringSubstitution() + ->reSubstituteVariables( sTemplateFile ); + } + else + { + plProperties[nRealyChanged].Value <<= sTemplateFile; + } + + ++nRealyChanged; + } + if( bChangedDefaultFilter ) + { + plProperties[nRealyChanged].Name + = OUString::Concat(sNodeBase) + PROPERTYNAME_DEFAULTFILTER; + plProperties[nRealyChanged].Value <<= sDefaultFilter; + ++nRealyChanged; + } + + // Don't forget to reset changed flags! Otherwise we save it again and again and ... + bChangedTemplateFile = false; + bChangedDefaultFilter = false; + + lProperties.realloc( nRealyChanged ); + return lProperties; + } + + // We must support setting AND marking of changed values. + // That's why we can't make our member public. We must use get/set/init methods + // to control access on it! + bool getInstalled () const { return bInstalled; }; + const OUString& getFactory () const { return sFactory; }; + const OUString& getTemplateFile () const { return sTemplateFile; }; + const OUString& getDefaultFilter () const { return sDefaultFilter; }; + bool isDefaultFilterReadonly() const { return bDefaultFilterReadonly; } + sal_Int32 getIcon () const { return nIcon; }; + + // If you call set-methods - we check for changes of values and mark it. + // But if you wish to set it without that... you must initialize it! + void initInstalled () { bInstalled = true; } + void initFactory ( const OUString& sNewFactory ) { sFactory = sNewFactory; } + void initDefaultFilter ( const OUString& sNewDefaultFilter ) { sDefaultFilter = sNewDefaultFilter; } + void setDefaultFilterReadonly( const bool bVal){bDefaultFilterReadonly = bVal;} + void initIcon ( sal_Int32 nNewIcon ) { nIcon = nNewIcon; } + + void initTemplateFile( const OUString& sNewTemplateFile ) + { + if ( !sNewTemplateFile.isEmpty() ) + { + sTemplateFile= getStringSubstitution()->substituteVariables( sNewTemplateFile, false ); + } + else + { + sTemplateFile = sNewTemplateFile; + } + } + + void setTemplateFile( const OUString& sNewTemplateFile ) + { + if( sTemplateFile != sNewTemplateFile ) + { + sTemplateFile = sNewTemplateFile; + bChangedTemplateFile = true; + } + }; + + void setDefaultFilter( const OUString& sNewDefaultFilter ) + { + if( sDefaultFilter != sNewDefaultFilter ) + { + sDefaultFilter = sNewDefaultFilter; + bChangedDefaultFilter = true; + } + }; + + private: + css::uno::Reference< css::util::XStringSubstitution > const & getStringSubstitution() + { + if ( !xSubstVars.is() ) + { + xSubstVars.set( css::util::PathSubstitution::create(::comphelper::getProcessComponentContext()) ); + } + return xSubstVars; + } + + bool bInstalled; + OUString sFactory; + OUString sTemplateFile; + OUString sDefaultFilter; + sal_Int32 nIcon; + + bool bChangedTemplateFile :1; + bool bChangedDefaultFilter :1; + bool bDefaultFilterReadonly :1; + + css::uno::Reference< css::util::XStringSubstitution > xSubstVars; +}; + +} + +class SvtModuleOptions_Impl : public ::utl::ConfigItem +{ + + // public methods + + public: + + // constructor / destructor + + SvtModuleOptions_Impl(); + virtual ~SvtModuleOptions_Impl() override; + + // override methods of baseclass + + virtual void Notify( const css::uno::Sequence< OUString >& lPropertyNames ) override; + + // public interface + + bool IsModuleInstalled ( SvtModuleOptions::EModule eModule ) const; + css::uno::Sequence < OUString > GetAllServiceNames(); + OUString const & GetFactoryName ( SvtModuleOptions::EFactory eFactory ) const; + OUString const & GetFactoryStandardTemplate( SvtModuleOptions::EFactory eFactory ) const; + static OUString GetFactoryEmptyDocumentURL( SvtModuleOptions::EFactory eFactory ); + OUString const & GetFactoryDefaultFilter ( SvtModuleOptions::EFactory eFactory ) const; + bool IsDefaultFilterReadonly( SvtModuleOptions::EFactory eFactory ) const; + sal_Int32 GetFactoryIcon ( SvtModuleOptions::EFactory eFactory ) const; + static bool ClassifyFactoryByName ( std::u16string_view sName , + SvtModuleOptions::EFactory& eFactory ); + void SetFactoryStandardTemplate( SvtModuleOptions::EFactory eFactory , + const OUString& sTemplate ); + void SetFactoryDefaultFilter ( SvtModuleOptions::EFactory eFactory , + const OUString& sFilter ); + void MakeReadonlyStatesAvailable(); + + // private methods + + private: + static css::uno::Sequence< OUString > impl_ExpandSetNames ( const css::uno::Sequence< OUString >& lSetNames ); + void impl_Read ( const css::uno::Sequence< OUString >& lSetNames ); + + virtual void ImplCommit() override; + + // private member + + private: + o3tl::enumarray<SvtModuleOptions::EFactory, FactoryInfo> m_lFactories; + bool m_bReadOnlyStatesWellKnown; +}; + +/*-************************************************************************************************************ + @short default ctor + @descr We open our configuration here and read all necessary values from it. + These values are cached till everyone call Commit(). Then we write changed ones back to cfg. + + @seealso baseclass ConfigItem + @seealso method impl_Read() + @threadsafe no +*//*-*************************************************************************************************************/ +SvtModuleOptions_Impl::SvtModuleOptions_Impl() + : ::utl::ConfigItem( ROOTNODE_FACTORIES ) + , m_bReadOnlyStatesWellKnown( false ) +{ + // First initialize list of factory infos! Otherwise we couldn't guarantee right working of these class. + for( auto & rFactory : m_lFactories ) + rFactory.free(); + + // Get name list of all existing set node names in configuration to read her properties in impl_Read(). + // These list is a list of long names of our factories. + const css::uno::Sequence< OUString > lFactories = GetNodeNames( OUString() ); + impl_Read( lFactories ); + + // Enable notification for changes by using configuration directly. + // So we can update our internal values immediately. + EnableNotification( lFactories ); +} + +SvtModuleOptions_Impl::~SvtModuleOptions_Impl() +{ + assert(!IsModified()); // should have been committed +} + +/*-************************************************************************************************************ + @short called for notify of configmanager + @descr This method is called from the ConfigManager before application ends or from the + PropertyChangeListener if the sub tree broadcasts changes. You must update our + internal values. + + @attention We are registered for pure set node names only. So we can use our internal method "impl_Read()" to + update our info list. Because - this method expand given name list to full qualified property list + and use it to read the values. These values are filled into our internal member list m_lFactories + at right position. + + @seealso method impl_Read() + + @param "lNames" is the list of set node entries which should be updated. + @threadsafe no +*//*-*************************************************************************************************************/ +void SvtModuleOptions_Impl::Notify( const css::uno::Sequence< OUString >& ) +{ + OSL_FAIL( "SvtModuleOptions_Impl::Notify() Not implemented yet!" ); +} + +/*-**************************************************************************************************** + @short write changes to configuration + @descr This method writes the changed values into the sub tree + and should always called in our destructor to guarantee consistency of config data. + + @attention We clear complete set in configuration first and write it completely new! So we don't must + distinguish between existing, added or removed elements. Our internal cached values + are the only and right ones. + + @seealso baseclass ConfigItem + @threadsafe no +*//*-*****************************************************************************************************/ +void SvtModuleOptions_Impl::ImplCommit() +{ + // Reserve memory for ALL possible factory properties! + // Step over all factories and get her really changed values only. + // Build list of these ones and use it for commit. + css::uno::Sequence< css::beans::PropertyValue > lCommitProperties( FACTORYCOUNT*PROPERTYCOUNT ); + sal_Int32 nRealCount = 0; + OUString sBasePath; + for( FactoryInfo & rInfo : m_lFactories ) + { + // These path is used to build full qualified property names... + // See pInfo->getChangedProperties() for further information + sBasePath = PATHSEPARATOR + rInfo.getFactory() + PATHSEPARATOR; + + const css::uno::Sequence< css::beans::PropertyValue > lChangedProperties = rInfo.getChangedProperties ( sBasePath ); + std::copy(lChangedProperties.begin(), lChangedProperties.end(), std::next(lCommitProperties.getArray(), nRealCount)); + nRealCount += lChangedProperties.getLength(); + } + // Resize commit list to real size. + // If nothing to do - suppress calling of configuration... + // It could be too expensive :-) + if( nRealCount > 0 ) + { + lCommitProperties.realloc( nRealCount ); + SetSetProperties( OUString(), lCommitProperties ); + } +} + +/*-**************************************************************************************************** + @short access method to get internal values + @descr These methods implement easy access to our internal values. + You give us right enum value to specify which module interest you ... we return right information. + + @attention Some people use any value as enum ... but we support in header specified values only! + We use it directly as index in our internal list. If enum value isn't right - we crash with an + "index out of range"!!! Please use me right - otherwise there is no guarantee. + @param "eModule" , index in list - specify module + @return Queried information. + + @onerror We return default values. (mostly "not installed"!) + @threadsafe no +*//*-*****************************************************************************************************/ +bool SvtModuleOptions_Impl::IsModuleInstalled( SvtModuleOptions::EModule eModule ) const +{ + switch( eModule ) + { + case SvtModuleOptions::EModule::WRITER: + return m_lFactories[SvtModuleOptions::EFactory::WRITER].getInstalled(); + case SvtModuleOptions::EModule::WEB: + return m_lFactories[SvtModuleOptions::EFactory::WRITERWEB].getInstalled(); + case SvtModuleOptions::EModule::GLOBAL: + return m_lFactories[SvtModuleOptions::EFactory::WRITERGLOBAL].getInstalled(); + case SvtModuleOptions::EModule::CALC: + return m_lFactories[SvtModuleOptions::EFactory::CALC].getInstalled(); + case SvtModuleOptions::EModule::DRAW: + return m_lFactories[SvtModuleOptions::EFactory::DRAW].getInstalled(); + case SvtModuleOptions::EModule::IMPRESS: + return m_lFactories[SvtModuleOptions::EFactory::IMPRESS].getInstalled(); + case SvtModuleOptions::EModule::MATH: + return m_lFactories[SvtModuleOptions::EFactory::MATH].getInstalled(); + case SvtModuleOptions::EModule::CHART: + return m_lFactories[SvtModuleOptions::EFactory::CHART].getInstalled(); + case SvtModuleOptions::EModule::STARTMODULE: + return m_lFactories[SvtModuleOptions::EFactory::STARTMODULE].getInstalled(); + case SvtModuleOptions::EModule::BASIC: + return true; // Couldn't be deselected by setup yet! + case SvtModuleOptions::EModule::DATABASE: + return m_lFactories[SvtModuleOptions::EFactory::DATABASE].getInstalled(); + } + + return false; +} + +css::uno::Sequence < OUString > SvtModuleOptions_Impl::GetAllServiceNames() +{ + std::vector<OUString> aVec; + + for( const auto & rFactory : m_lFactories ) + if( rFactory.getInstalled() ) + aVec.push_back( rFactory.getFactory() ); + + return comphelper::containerToSequence(aVec); +} + +OUString const & SvtModuleOptions_Impl::GetFactoryName( SvtModuleOptions::EFactory eFactory ) const +{ + return m_lFactories[eFactory].getFactory(); +} + +OUString SvtModuleOptions::GetFactoryShortName(SvtModuleOptions::EFactory eFactory) +{ + // Attention: Hard configured yet ... because it's not fine to make changes possible by xml file yet. + // But it's good to plan further possibilities! + + //return m_lFactories[eFactory].sShortName; + + OUString sShortName; + switch( eFactory ) + { + case SvtModuleOptions::EFactory::WRITER : sShortName = "swriter"; + break; + case SvtModuleOptions::EFactory::WRITERWEB: sShortName = "swriter/web"; + break; + case SvtModuleOptions::EFactory::WRITERGLOBAL: sShortName = "swriter/GlobalDocument"; + break; + case SvtModuleOptions::EFactory::CALC : sShortName = "scalc"; + break; + case SvtModuleOptions::EFactory::DRAW : sShortName = "sdraw"; + break; + case SvtModuleOptions::EFactory::IMPRESS : sShortName = "simpress"; + break; + case SvtModuleOptions::EFactory::MATH : sShortName = "smath"; + break; + case SvtModuleOptions::EFactory::CHART : sShortName = "schart"; + break; + case SvtModuleOptions::EFactory::BASIC : sShortName = "sbasic"; + break; + case SvtModuleOptions::EFactory::DATABASE : sShortName = "sdatabase"; + break; + case SvtModuleOptions::EFactory::STARTMODULE : sShortName = "startmodule"; + break; + default: + OSL_FAIL( "unknown factory" ); + break; + } + + return sShortName; +} + +OUString const & SvtModuleOptions_Impl::GetFactoryStandardTemplate( SvtModuleOptions::EFactory eFactory ) const +{ + return m_lFactories[eFactory].getTemplateFile(); +} + +OUString SvtModuleOptions_Impl::GetFactoryEmptyDocumentURL( SvtModuleOptions::EFactory eFactory ) +{ + // Attention: Hard configured yet ... because it's not fine to make changes possible by xml file yet. + // But it's good to plan further possibilities! + + //return m_lFactories[eFactory].getEmptyDocumentURL(); + + OUString sURL; + switch( eFactory ) + { + case SvtModuleOptions::EFactory::WRITER : sURL = "private:factory/swriter"; + break; + case SvtModuleOptions::EFactory::WRITERWEB : sURL = "private:factory/swriter/web"; + break; + case SvtModuleOptions::EFactory::WRITERGLOBAL : sURL = "private:factory/swriter/GlobalDocument"; + break; + case SvtModuleOptions::EFactory::CALC : sURL = "private:factory/scalc"; + break; + case SvtModuleOptions::EFactory::DRAW : sURL = "private:factory/sdraw"; + break; + case SvtModuleOptions::EFactory::IMPRESS : sURL = "private:factory/simpress?slot=6686"; + break; + case SvtModuleOptions::EFactory::MATH : sURL = "private:factory/smath"; + break; + case SvtModuleOptions::EFactory::CHART : sURL = "private:factory/schart"; + break; + case SvtModuleOptions::EFactory::BASIC : sURL = "private:factory/sbasic"; + break; + case SvtModuleOptions::EFactory::DATABASE : sURL = "private:factory/sdatabase?Interactive"; + break; + default: + OSL_FAIL( "unknown factory" ); + break; + } + return sURL; +} + +OUString const & SvtModuleOptions_Impl::GetFactoryDefaultFilter( SvtModuleOptions::EFactory eFactory ) const +{ + return m_lFactories[eFactory].getDefaultFilter(); +} + +bool SvtModuleOptions_Impl::IsDefaultFilterReadonly( SvtModuleOptions::EFactory eFactory ) const +{ + return m_lFactories[eFactory].isDefaultFilterReadonly(); +} + +sal_Int32 SvtModuleOptions_Impl::GetFactoryIcon( SvtModuleOptions::EFactory eFactory ) const +{ + return m_lFactories[eFactory].getIcon(); +} + +void SvtModuleOptions_Impl::SetFactoryStandardTemplate( SvtModuleOptions::EFactory eFactory , + const OUString& sTemplate ) +{ + m_lFactories[eFactory].setTemplateFile( sTemplate ); + SetModified(); +} + +void SvtModuleOptions_Impl::SetFactoryDefaultFilter( SvtModuleOptions::EFactory eFactory, + const OUString& sFilter ) +{ + m_lFactories[eFactory].setDefaultFilter( sFilter ); + SetModified(); +} + +/*-************************************************************************************************************ + @short return list of key names of our configuration management which represent our module tree + @descr You give use a list of current existing set node names .. and we expand it for all + well known properties which are necessary for this implementation. + These full expanded list should be used to get values of this properties. + + @seealso ctor + @return List of all relative addressed properties of given set entry names. + + @onerror List will be empty. + @threadsafe no +*//*-*************************************************************************************************************/ +css::uno::Sequence< OUString > SvtModuleOptions_Impl::impl_ExpandSetNames( const css::uno::Sequence< OUString >& lSetNames ) +{ + sal_Int32 nCount = lSetNames.getLength(); + css::uno::Sequence< OUString > lPropNames ( nCount*PROPERTYCOUNT ); + OUString* pPropNames = lPropNames.getArray(); + sal_Int32 nPropStart = 0; + + for( const auto& rSetName : lSetNames ) + { + pPropNames[nPropStart+PROPERTYHANDLE_SHORTNAME ] = rSetName + PATHSEPARATOR PROPERTYNAME_SHORTNAME; + pPropNames[nPropStart+PROPERTYHANDLE_TEMPLATEFILE ] = rSetName + PATHSEPARATOR PROPERTYNAME_TEMPLATEFILE; + pPropNames[nPropStart+PROPERTYHANDLE_WINDOWATTRIBUTES] = rSetName + PATHSEPARATOR PROPERTYNAME_WINDOWATTRIBUTES; + pPropNames[nPropStart+PROPERTYHANDLE_EMPTYDOCUMENTURL] = rSetName + PATHSEPARATOR PROPERTYNAME_EMPTYDOCUMENTURL; + pPropNames[nPropStart+PROPERTYHANDLE_DEFAULTFILTER ] = rSetName + PATHSEPARATOR PROPERTYNAME_DEFAULTFILTER; + pPropNames[nPropStart+PROPERTYHANDLE_ICON ] = rSetName + PATHSEPARATOR PROPERTYNAME_ICON; + nPropStart += PROPERTYCOUNT; + } + + return lPropNames; +} + +/*-************************************************************************************************************ + @short helper to classify given factory by name + @descr Every factory has its own long and short name. So we can match right enum value for internal using. + + @attention We change in/out parameter "eFactory" in every case! But you should use it only, if return value is sal_True! + Algorithm: Set out-parameter to probably value ... and check the longname. + If it matches with these factory - break operation and return true AND right set parameter. + Otherwise try next one and so on. If no factory was found return false. Out parameter eFactory + is set to last tried value but shouldn't be used! Because our return value is false! + @param "sLongName" , long name of factory, which should be classified + @return "eFactory" , right enum value, which match given long name + and true for successfully classification, false otherwise + + @onerror We return false. + @threadsafe no +*//*-*************************************************************************************************************/ +bool SvtModuleOptions_Impl::ClassifyFactoryByName( std::u16string_view sName, SvtModuleOptions::EFactory& eFactory ) +{ + bool bState; + + eFactory = SvtModuleOptions::EFactory::WRITER; + bState = ( sName == FACTORYNAME_WRITER ); + + if( !bState ) + { + eFactory = SvtModuleOptions::EFactory::WRITERWEB; + bState = ( sName == FACTORYNAME_WRITERWEB ); + } + // no else! + if( !bState ) + { + eFactory = SvtModuleOptions::EFactory::WRITERGLOBAL; + bState = ( sName == FACTORYNAME_WRITERGLOBAL ); + } + // no else! + if( !bState ) + { + eFactory = SvtModuleOptions::EFactory::CALC; + bState = ( sName == FACTORYNAME_CALC ); + } + // no else! + if( !bState ) + { + eFactory = SvtModuleOptions::EFactory::DRAW; + bState = ( sName == FACTORYNAME_DRAW ); + } + // no else! + if( !bState ) + { + eFactory = SvtModuleOptions::EFactory::IMPRESS; + bState = ( sName == FACTORYNAME_IMPRESS ); + } + // no else! + if( !bState ) + { + eFactory = SvtModuleOptions::EFactory::MATH; + bState = ( sName == FACTORYNAME_MATH ); + } + // no else! + if( !bState ) + { + eFactory = SvtModuleOptions::EFactory::CHART; + bState = ( sName == FACTORYNAME_CHART ); + } + // no else! + if( !bState ) + { + eFactory = SvtModuleOptions::EFactory::DATABASE; + bState = ( sName == FACTORYNAME_DATABASE ); + } + // no else! + if( !bState ) + { + eFactory = SvtModuleOptions::EFactory::STARTMODULE; + bState = ( sName == FACTORYNAME_STARTMODULE); + } + // no else! + if( !bState ) + { + eFactory = SvtModuleOptions::EFactory::BASIC; + bState = ( sName == FACTORYNAME_BASIC); + } + + return bState; +} + +/*-************************************************************************************************************ + @short read factory configuration + @descr Give us a list of pure factory names (long names!) which can be used as + direct set node names... and we read her property values and fill internal list. + These method can be used by initial reading at ctor and later updating by "Notify()". + + @seealso ctor + @seealso method Notify() + + @param "lFactories" is the list of set node entries which should be read. + @onerror We do nothing. + @threadsafe no +*//*-*************************************************************************************************************/ +void SvtModuleOptions_Impl::impl_Read( const css::uno::Sequence< OUString >& lFactories ) +{ + // Expand every set node name in lFactories to full qualified paths to its properties + // and get right values from configuration. + const css::uno::Sequence< OUString > lProperties = impl_ExpandSetNames( lFactories ); + const css::uno::Sequence< css::uno::Any > lValues = GetProperties( lProperties ); + + // Safe impossible cases. + // We need values from ALL configuration keys. + // Follow assignment use order of values in relation to our list of key names! + OSL_ENSURE( !(lProperties.getLength()!=lValues.getLength()), "SvtModuleOptions_Impl::impl_Read()\nI miss some values of configuration keys!" ); + + // Algorithm: We step over all given factory names and classify it. These enum value can be used as direct index + // in our member list m_lFactories! VAriable nPropertyStart marks start position of every factory + // and her properties in expanded property/value list. The defines PROPERTHANDLE_xxx are used as offset values + // added to nPropertyStart. So we can address every property relative in these lists. + // If we found any valid values ... we reset all existing information for corresponding m_lFactories-entry and + // use a pointer to these struct in memory directly to set new values. + // But we set it only, if bInstalled is true. Otherwise all other values of a factory can be undeclared .. They + // shouldn't be used then. + // Attention: If a propertyset of a factory will be ignored we must step to next start position of next factory infos! + // see "nPropertyStart += PROPERTYCOUNT" ... + + sal_Int32 nPropertyStart = 0; + FactoryInfo* pInfo = nullptr; + SvtModuleOptions::EFactory eFactory; + + for( const OUString& sFactoryName : lFactories ) + { + if( ClassifyFactoryByName( sFactoryName, eFactory ) ) + { + OUString sTemp; + sal_Int32 nTemp = 0; + + pInfo = &(m_lFactories[eFactory]); + pInfo->free(); + + pInfo->initInstalled(); + pInfo->initFactory ( sFactoryName ); + + if (lValues[nPropertyStart+PROPERTYHANDLE_TEMPLATEFILE] >>= sTemp) + pInfo->initTemplateFile( sTemp ); + if (lValues[nPropertyStart+PROPERTYHANDLE_DEFAULTFILTER ] >>= sTemp) + pInfo->initDefaultFilter( sTemp ); + if (lValues[nPropertyStart+PROPERTYHANDLE_ICON] >>= nTemp) + pInfo->initIcon( nTemp ); + } + nPropertyStart += PROPERTYCOUNT; + } +} + +void SvtModuleOptions_Impl::MakeReadonlyStatesAvailable() +{ + if (m_bReadOnlyStatesWellKnown) + return; + + css::uno::Sequence< OUString > lFactories = GetNodeNames(OUString()); + for (OUString& rFactory : asNonConstRange(lFactories)) + rFactory += PATHSEPARATOR PROPERTYNAME_DEFAULTFILTER; + + css::uno::Sequence< sal_Bool > lReadonlyStates = GetReadOnlyStates(lFactories); + sal_Int32 c = lFactories.getLength(); + for (sal_Int32 i=0; i<c; ++i) + { + const OUString& rFactoryName = std::as_const(lFactories)[i]; + SvtModuleOptions::EFactory eFactory; + + if (!ClassifyFactoryByName(rFactoryName, eFactory)) + continue; + + FactoryInfo& rInfo = m_lFactories[eFactory]; + rInfo.setDefaultFilterReadonly(lReadonlyStates[i]); + } + + m_bReadOnlyStatesWellKnown = true; +} + +namespace { + //global + std::weak_ptr<SvtModuleOptions_Impl> g_pModuleOptions; + +std::mutex& impl_GetOwnStaticMutex() +{ + static std::mutex s_Mutex; + return s_Mutex; +} +} + +/*-************************************************************************************************************ + @short standard constructor and destructor + @descr This will initialize an instance with default values. We initialize/deinitialize our static data + container and create a static mutex, which is used for threadsafe code in further time of this object. + + @seealso method impl_GetOwnStaticMutex() + @threadsafe yes +*//*-*************************************************************************************************************/ +SvtModuleOptions::SvtModuleOptions() +{ + // no need to take the mutex yet, shared_ptr/weak_ptr are thread-safe + m_pImpl = g_pModuleOptions.lock(); + if( m_pImpl ) + return; + + // take the mutex, so we don't accidentally create more than one + std::unique_lock aGuard( impl_GetOwnStaticMutex() ); + + m_pImpl = g_pModuleOptions.lock(); + if( !m_pImpl ) + { + m_pImpl = std::make_shared<SvtModuleOptions_Impl>(); + g_pModuleOptions = m_pImpl; + aGuard.unlock(); // because holdConfigItem will call this constructor + ItemHolder1::holdConfigItem(EItem::ModuleOptions); + } +} + +SvtModuleOptions::~SvtModuleOptions() +{ + m_pImpl.reset(); +} + +/*-************************************************************************************************************ + @short access to configuration data + @descr This methods allow read/write access to configuration values. + They are threadsafe. All calls are forwarded to impl-data-container. See there for further information! + + @seealso method impl_GetOwnStaticMutex() + @threadsafe yes +*//*-*************************************************************************************************************/ +bool SvtModuleOptions::IsModuleInstalled( EModule eModule ) const +{ + // doesn't need mutex, never modified + return m_pImpl->IsModuleInstalled( eModule ); +} + +const OUString & SvtModuleOptions::GetFactoryName( EFactory eFactory ) const +{ + // doesn't need mutex, never modified + return m_pImpl->GetFactoryName( eFactory ); +} + +OUString SvtModuleOptions::GetFactoryStandardTemplate( EFactory eFactory ) const +{ + std::unique_lock aGuard( impl_GetOwnStaticMutex() ); + return m_pImpl->GetFactoryStandardTemplate( eFactory ); +} + +OUString SvtModuleOptions::GetFactoryEmptyDocumentURL( EFactory eFactory ) const +{ + std::unique_lock aGuard( impl_GetOwnStaticMutex() ); + return SvtModuleOptions_Impl::GetFactoryEmptyDocumentURL( eFactory ); +} + +OUString SvtModuleOptions::GetFactoryDefaultFilter( EFactory eFactory ) const +{ + std::unique_lock aGuard( impl_GetOwnStaticMutex() ); + return m_pImpl->GetFactoryDefaultFilter( eFactory ); +} + +bool SvtModuleOptions::IsDefaultFilterReadonly( EFactory eFactory ) const +{ + std::unique_lock aGuard( impl_GetOwnStaticMutex() ); + m_pImpl->MakeReadonlyStatesAvailable(); + return m_pImpl->IsDefaultFilterReadonly( eFactory ); +} + +sal_Int32 SvtModuleOptions::GetFactoryIcon( EFactory eFactory ) const +{ + std::unique_lock aGuard( impl_GetOwnStaticMutex() ); + return m_pImpl->GetFactoryIcon( eFactory ); +} + +bool SvtModuleOptions::ClassifyFactoryByName( std::u16string_view sName , + EFactory& eFactory ) +{ + // We don't need any mutex here ... because we don't use any member here! + return SvtModuleOptions_Impl::ClassifyFactoryByName( sName, eFactory ); +} + +void SvtModuleOptions::SetFactoryStandardTemplate( EFactory eFactory , + const OUString& sTemplate ) +{ + std::unique_lock aGuard( impl_GetOwnStaticMutex() ); + m_pImpl->SetFactoryStandardTemplate( eFactory, sTemplate ); +} + +void SvtModuleOptions::SetFactoryDefaultFilter( EFactory eFactory, + const OUString& sFilter ) +{ + std::unique_lock aGuard( impl_GetOwnStaticMutex() ); + m_pImpl->SetFactoryDefaultFilter( eFactory, sFilter ); +} + +bool SvtModuleOptions::IsMath() const +{ + // doesn't need mutex, never modified + return m_pImpl->IsModuleInstalled( EModule::MATH ); +} + +bool SvtModuleOptions::IsChart() const +{ + // doesn't need mutex, never modified + return m_pImpl->IsModuleInstalled( EModule::CHART ); +} + +bool SvtModuleOptions::IsCalc() const +{ + // doesn't need mutex, never modified + return m_pImpl->IsModuleInstalled( EModule::CALC ); +} + +bool SvtModuleOptions::IsDraw() const +{ + // doesn't need mutex, never modified + return m_pImpl->IsModuleInstalled( EModule::DRAW ); +} + +bool SvtModuleOptions::IsWriter() const +{ + std::unique_lock aGuard( impl_GetOwnStaticMutex() ); + return m_pImpl->IsModuleInstalled( EModule::WRITER ); +} + +bool SvtModuleOptions::IsImpress() const +{ + // doesn't need mutex, never modified + return m_pImpl->IsModuleInstalled( EModule::IMPRESS ); +} + +bool SvtModuleOptions::IsDataBase() const +{ + // doesn't need mutex, never modified + return m_pImpl->IsModuleInstalled( EModule::DATABASE ); +} + +OUString SvtModuleOptions::GetModuleName( EModule eModule ) const +{ + switch( eModule ) + { + case SvtModuleOptions::EModule::WRITER : { return "Writer"; } + case SvtModuleOptions::EModule::WEB : { return "Web"; } + case SvtModuleOptions::EModule::GLOBAL : { return "Global"; } + case SvtModuleOptions::EModule::CALC : { return "Calc"; } + case SvtModuleOptions::EModule::DRAW : { return "Draw"; } + case SvtModuleOptions::EModule::IMPRESS : { return "Impress"; } + case SvtModuleOptions::EModule::MATH : { return "Math"; } + case SvtModuleOptions::EModule::CHART : { return "Chart"; } + case SvtModuleOptions::EModule::BASIC : { return "Basic"; } + case SvtModuleOptions::EModule::DATABASE : { return "Database"; } + default: + OSL_FAIL( "unknown module" ); + break; + } + + return OUString(); +} + +SvtModuleOptions::EFactory SvtModuleOptions::ClassifyFactoryByShortName(std::u16string_view sName) +{ + if ( sName == u"swriter" ) + return EFactory::WRITER; + if (o3tl::equalsIgnoreAsciiCase(sName, u"swriter/Web")) // sometimes they are registered for swriter/web :-( + return EFactory::WRITERWEB; + if (o3tl::equalsIgnoreAsciiCase(sName, u"swriter/GlobalDocument")) // sometimes they are registered for swriter/globaldocument :-( + return EFactory::WRITERGLOBAL; + if ( sName == u"scalc" ) + return EFactory::CALC; + if ( sName == u"sdraw" ) + return EFactory::DRAW; + if ( sName == u"simpress" ) + return EFactory::IMPRESS; + if ( sName == u"schart" ) + return EFactory::CHART; + if ( sName == u"smath" ) + return EFactory::MATH; + if ( sName == u"sbasic" ) + return EFactory::BASIC; + if ( sName == u"sdatabase" ) + return EFactory::DATABASE; + + return EFactory::UNKNOWN_FACTORY; +} + +SvtModuleOptions::EFactory SvtModuleOptions::ClassifyFactoryByServiceName(std::u16string_view sName) +{ + if (sName == FACTORYNAME_WRITERGLOBAL) + return EFactory::WRITERGLOBAL; + if (sName == FACTORYNAME_WRITERWEB) + return EFactory::WRITERWEB; + if (sName == FACTORYNAME_WRITER) + return EFactory::WRITER; + if (sName == FACTORYNAME_CALC) + return EFactory::CALC; + if (sName == FACTORYNAME_DRAW) + return EFactory::DRAW; + if (sName == FACTORYNAME_IMPRESS) + return EFactory::IMPRESS; + if (sName == FACTORYNAME_MATH) + return EFactory::MATH; + if (sName == FACTORYNAME_CHART) + return EFactory::CHART; + if (sName == FACTORYNAME_DATABASE) + return EFactory::DATABASE; + if (sName == FACTORYNAME_STARTMODULE) + return EFactory::STARTMODULE; + if (sName == FACTORYNAME_BASIC) + return EFactory::BASIC; + + return EFactory::UNKNOWN_FACTORY; +} + +SvtModuleOptions::EFactory SvtModuleOptions::ClassifyFactoryByURL(const OUString& sURL , + const css::uno::Sequence< css::beans::PropertyValue >& lMediaDescriptor) +{ + css::uno::Reference< css::uno::XComponentContext > xContext = ::comphelper::getProcessComponentContext(); + + css::uno::Reference< css::container::XNameAccess > xFilterCfg; + css::uno::Reference< css::container::XNameAccess > xTypeCfg; + try + { + xFilterCfg.set( + xContext->getServiceManager()->createInstanceWithContext("com.sun.star.document.FilterFactory", xContext), css::uno::UNO_QUERY); + xTypeCfg.set( + xContext->getServiceManager()->createInstanceWithContext("com.sun.star.document.TypeDetection", xContext), css::uno::UNO_QUERY); + } + catch(const css::uno::RuntimeException&) + { throw; } + catch(const css::uno::Exception&) + { return EFactory::UNKNOWN_FACTORY; } + + ::comphelper::SequenceAsHashMap stlDesc(lMediaDescriptor); + + // is there already a filter inside the descriptor? + OUString sFilterName = stlDesc.getUnpackedValueOrDefault("FilterName", OUString()); + if (!sFilterName.isEmpty()) + { + try + { + ::comphelper::SequenceAsHashMap stlFilterProps (xFilterCfg->getByName(sFilterName)); + OUString sDocumentService = stlFilterProps.getUnpackedValueOrDefault("DocumentService", OUString()); + SvtModuleOptions::EFactory eApp = SvtModuleOptions::ClassifyFactoryByServiceName(sDocumentService); + + if (eApp != EFactory::UNKNOWN_FACTORY) + return eApp; + } + catch(const css::uno::RuntimeException&) + { throw; } + catch(const css::uno::Exception&) + { /* do nothing here ... may the following code can help!*/ } + } + + // is there already a type inside the descriptor? + OUString sTypeName = stlDesc.getUnpackedValueOrDefault("TypeName", OUString()); + if (sTypeName.isEmpty()) + { + // no :-( + // start flat detection of URL + css::uno::Reference< css::document::XTypeDetection > xDetect(xTypeCfg, css::uno::UNO_QUERY); + sTypeName = xDetect->queryTypeByURL(sURL); + } + + if (sTypeName.isEmpty()) + return EFactory::UNKNOWN_FACTORY; + + // yes - there is a type info + // Try to find the preferred filter. + try + { + ::comphelper::SequenceAsHashMap stlTypeProps (xTypeCfg->getByName(sTypeName)); + OUString sPreferredFilter = stlTypeProps.getUnpackedValueOrDefault("PreferredFilter", OUString()); + ::comphelper::SequenceAsHashMap stlFilterProps (xFilterCfg->getByName(sPreferredFilter)); + OUString sDocumentService = stlFilterProps.getUnpackedValueOrDefault("DocumentService", OUString()); + SvtModuleOptions::EFactory eApp = SvtModuleOptions::ClassifyFactoryByServiceName(sDocumentService); + + if (eApp != EFactory::UNKNOWN_FACTORY) + return eApp; + } + catch(const css::uno::RuntimeException&) + { throw; } + catch(const css::uno::Exception&) + { /* do nothing here ... may the following code can help!*/ } + + // no filter/no type/no detection result => no fun :-) + return EFactory::UNKNOWN_FACTORY; +} + +SvtModuleOptions::EFactory SvtModuleOptions::ClassifyFactoryByModel(const css::uno::Reference< css::frame::XModel >& xModel) +{ + css::uno::Reference< css::lang::XServiceInfo > xInfo(xModel, css::uno::UNO_QUERY); + if (!xInfo.is()) + return EFactory::UNKNOWN_FACTORY; + + const css::uno::Sequence< OUString > lServices = xInfo->getSupportedServiceNames(); + + for (const OUString& rService : lServices) + { + SvtModuleOptions::EFactory eApp = SvtModuleOptions::ClassifyFactoryByServiceName(rService); + if (eApp != EFactory::UNKNOWN_FACTORY) + return eApp; + } + + return EFactory::UNKNOWN_FACTORY; +} + +css::uno::Sequence < OUString > SvtModuleOptions::GetAllServiceNames() +{ + std::unique_lock aGuard( impl_GetOwnStaticMutex() ); + return m_pImpl->GetAllServiceNames(); +} + +OUString SvtModuleOptions::GetDefaultModuleName() const +{ + OUString aModule; + if (m_pImpl->IsModuleInstalled(SvtModuleOptions::EModule::WRITER)) + aModule = GetFactoryShortName(SvtModuleOptions::EFactory::WRITER); + else if (m_pImpl->IsModuleInstalled(SvtModuleOptions::EModule::CALC)) + aModule = GetFactoryShortName(SvtModuleOptions::EFactory::CALC); + else if (m_pImpl->IsModuleInstalled(SvtModuleOptions::EModule::IMPRESS)) + aModule = GetFactoryShortName(SvtModuleOptions::EFactory::IMPRESS); + else if (m_pImpl->IsModuleInstalled(SvtModuleOptions::EModule::DATABASE)) + aModule = GetFactoryShortName(SvtModuleOptions::EFactory::DATABASE); + else if (m_pImpl->IsModuleInstalled(SvtModuleOptions::EModule::DRAW)) + aModule = GetFactoryShortName(SvtModuleOptions::EFactory::DRAW); + else if (m_pImpl->IsModuleInstalled(SvtModuleOptions::EModule::WEB)) + aModule = GetFactoryShortName(SvtModuleOptions::EFactory::WRITERWEB); + else if (m_pImpl->IsModuleInstalled(SvtModuleOptions::EModule::GLOBAL)) + aModule = GetFactoryShortName(SvtModuleOptions::EFactory::WRITERGLOBAL); + else if (m_pImpl->IsModuleInstalled(SvtModuleOptions::EModule::MATH)) + aModule = GetFactoryShortName(SvtModuleOptions::EFactory::MATH); + return aModule; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/config/options.cxx b/unotools/source/config/options.cxx new file mode 100644 index 0000000000..4da44fa274 --- /dev/null +++ b/unotools/source/config/options.cxx @@ -0,0 +1,114 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> +#include <unotools/options.hxx> + +#include <algorithm> + +using utl::detail::Options; +using utl::ConfigurationBroadcaster; + +utl::ConfigurationListener::~ConfigurationListener() {} + +ConfigurationBroadcaster::ConfigurationBroadcaster() +: m_nBroadcastBlocked( 0 ) +, m_nBlockedHint( ConfigurationHints::NONE ) +{ +} + +ConfigurationBroadcaster::ConfigurationBroadcaster(ConfigurationBroadcaster const & rSource) +: mpList( rSource.mpList ? new IMPL_ConfigurationListenerList(*rSource.mpList) : nullptr ) +, m_nBroadcastBlocked( rSource.m_nBroadcastBlocked ) +, m_nBlockedHint( rSource.m_nBlockedHint ) +{ +} + +ConfigurationBroadcaster::~ConfigurationBroadcaster() +{ +} + +ConfigurationBroadcaster & ConfigurationBroadcaster::operator =( + ConfigurationBroadcaster const & other) +{ + if (&other != this) { + mpList.reset( + other.mpList == nullptr ? nullptr : new IMPL_ConfigurationListenerList(*other.mpList)); + m_nBroadcastBlocked = other.m_nBroadcastBlocked; + m_nBlockedHint = other.m_nBlockedHint; + } + return *this; +} + +void ConfigurationBroadcaster::AddListener( utl::ConfigurationListener* pListener ) +{ + if ( !mpList ) + mpList.reset(new IMPL_ConfigurationListenerList); + mpList->push_back( pListener ); +} + +void ConfigurationBroadcaster::RemoveListener( utl::ConfigurationListener const * pListener ) +{ + if ( mpList ) { + auto it = std::find(mpList->begin(), mpList->end(), pListener); + if ( it != mpList->end() ) + mpList->erase( it ); + } +} + +void ConfigurationBroadcaster::NotifyListeners( ConfigurationHints nHint ) +{ + if ( m_nBroadcastBlocked ) + m_nBlockedHint |= nHint; + else + { + nHint |= m_nBlockedHint; + m_nBlockedHint = ConfigurationHints::NONE; + if ( mpList ) { + for ( size_t n = 0; n < mpList->size(); n++ ) + (*mpList)[ n ]->ConfigurationChanged( this, nHint ); + } + } +} + +void ConfigurationBroadcaster::BlockBroadcasts( bool bBlock ) +{ + if ( bBlock ) + ++m_nBroadcastBlocked; + else if ( m_nBroadcastBlocked ) + { + if ( --m_nBroadcastBlocked == 0 ) + NotifyListeners( ConfigurationHints::NONE ); + } +} + +Options::Options() +{ +} + +Options::~Options() +{ +} + +void Options::ConfigurationChanged( ConfigurationBroadcaster*, ConfigurationHints nHint ) +{ + NotifyListeners( nHint ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/config/optionsdlg.cxx b/unotools/source/config/optionsdlg.cxx new file mode 100644 index 0000000000..c88bd09510 --- /dev/null +++ b/unotools/source/config/optionsdlg.cxx @@ -0,0 +1,152 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <unotools/optionsdlg.hxx> +#include <unotools/configitem.hxx> +#include <unotools/configmgr.hxx> +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/Sequence.hxx> + +#include <cassert> + +using namespace com::sun::star::beans; +using namespace com::sun::star::uno; + +constexpr OUString ROOT_NODE = u"OptionsDialogGroups"_ustr; +constexpr OUString PAGES_NODE = u"Pages"_ustr; +constexpr OUString OPTIONS_NODE = u"Options"_ustr; + +namespace { + enum NodeType{ NT_Group, NT_Page, NT_Option }; +} +constexpr OUString g_sPathDelimiter = u"/"_ustr; +static void ReadNode( + const Reference<css::container::XHierarchicalNameAccess>& xHierarchyAccess, + SvtOptionsDialogOptions::OptionNodeList & aOptionNodeList, + std::u16string_view _rNode, NodeType _eType ); + + +SvtOptionsDialogOptions::SvtOptionsDialogOptions() +{ + Reference<css::container::XHierarchicalNameAccess> xHierarchyAccess = utl::ConfigManager::acquireTree(u"Office.OptionsDialog"); + const Sequence< OUString > aNodeSeq = utl::ConfigItem::GetNodeNames( xHierarchyAccess, ROOT_NODE, utl::ConfigNameFormat::LocalPath); + OUString sNode( ROOT_NODE + g_sPathDelimiter ); + for ( const auto& rNode : aNodeSeq ) + { + OUString sSubNode( sNode + rNode ); + ReadNode( xHierarchyAccess, m_aOptionNodeList, sSubNode, NT_Group ); + } +} + + +static void ReadNode( + const Reference<css::container::XHierarchicalNameAccess>& xHierarchyAccess, + SvtOptionsDialogOptions::OptionNodeList & aOptionNodeList, + std::u16string_view _rNode, NodeType _eType ) +{ + OUString sNode( _rNode + g_sPathDelimiter ); + OUString sSet; + sal_Int32 nLen = 0; + switch ( _eType ) + { + case NT_Group : + { + sSet = PAGES_NODE; + nLen = 2; + break; + } + + case NT_Page : + { + sSet = OPTIONS_NODE; + nLen = 2; + break; + } + + case NT_Option : + { + nLen = 1; + break; + } + } + + assert(nLen > 0); + + Sequence< OUString > lResult( nLen ); + auto plResult = lResult.getArray(); + plResult[0] = sNode + "Hide"; + if ( _eType != NT_Option ) + plResult[1] = sNode + sSet; + + Sequence< Any > aValues = utl::ConfigItem::GetProperties( xHierarchyAccess, lResult, /*bAllLocales*/false ); + bool bHide = false; + if ( aValues[0] >>= bHide ) + aOptionNodeList.emplace( sNode, bHide ); + + if ( _eType != NT_Option ) + { + OUString sNodes( sNode + sSet ); + const Sequence< OUString > aNodes = utl::ConfigItem::GetNodeNames( xHierarchyAccess, sNodes, utl::ConfigNameFormat::LocalPath ); + for ( const auto& rNode : aNodes ) + { + OUString sSubNodeName( sNodes + g_sPathDelimiter + rNode ); + ReadNode( xHierarchyAccess, aOptionNodeList, sSubNodeName, _eType == NT_Group ? NT_Page : NT_Option ); + } + } +} + +static OUString getGroupPath( std::u16string_view _rGroup ) +{ + return OUString( OUString::Concat(ROOT_NODE) + "/" + _rGroup + "/" ); +} +static OUString getPagePath( std::u16string_view _rPage ) +{ + return OUString( OUString::Concat(PAGES_NODE) + "/" + _rPage + "/" ); +} +static OUString getOptionPath( std::u16string_view _rOption ) +{ + return OUString( OUString::Concat(OPTIONS_NODE) + "/" + _rOption + "/" ); +} + +bool SvtOptionsDialogOptions::IsHidden( const OUString& _rPath ) const +{ + bool bRet = false; + OptionNodeList::const_iterator pIter = m_aOptionNodeList.find( _rPath ); + if ( pIter != m_aOptionNodeList.end() ) + bRet = pIter->second; + return bRet; +} + +bool SvtOptionsDialogOptions::IsGroupHidden( std::u16string_view _rGroup ) const +{ + return IsHidden( getGroupPath( _rGroup ) ); +} + +bool SvtOptionsDialogOptions::IsPageHidden( std::u16string_view _rPage, std::u16string_view _rGroup ) const +{ + return IsHidden( getGroupPath( _rGroup ) + getPagePath( _rPage ) ); +} + +bool SvtOptionsDialogOptions::IsOptionHidden( + std::u16string_view _rOption, std::u16string_view _rPage, std::u16string_view _rGroup ) const +{ + return IsHidden( getGroupPath( _rGroup ) + getPagePath( _rPage ) + getOptionPath( _rOption ) ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/config/pathoptions.cxx b/unotools/source/config/pathoptions.cxx new file mode 100644 index 0000000000..24c1fb323d --- /dev/null +++ b/unotools/source/config/pathoptions.cxx @@ -0,0 +1,737 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <sal/log.hxx> +#include <unotools/pathoptions.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <tools/urlobj.hxx> +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/Sequence.hxx> +#include <osl/mutex.hxx> +#include <osl/file.hxx> + +#include <unotools/ucbhelper.hxx> +#include <comphelper/getexpandeduri.hxx> +#include <comphelper/processfactory.hxx> +#include <com/sun/star/beans/XFastPropertySet.hpp> +#include <com/sun/star/beans/XPropertySetInfo.hpp> +#include <com/sun/star/util/thePathSettings.hpp> +#include <com/sun/star/util/PathSubstitution.hpp> +#include <com/sun/star/util/XStringSubstitution.hpp> +#include <com/sun/star/util/theMacroExpander.hpp> +#include <o3tl/enumarray.hxx> +#include <o3tl/string_view.hxx> + +#include "itemholder1.hxx" + +#include <set> +#include <unordered_map> + +using namespace osl; +using namespace utl; +using namespace com::sun::star::uno; +using namespace com::sun::star::beans; +using namespace com::sun::star::util; +using namespace com::sun::star::lang; + +#define SEARCHPATH_DELIMITER ';' +#define SIGN_STARTVARIABLE "$(" +#define SIGN_ENDVARIABLE ")" + +// Supported variables by the old SvtPathOptions implementation +#define SUBSTITUTE_INSTPATH "$(instpath)" +#define SUBSTITUTE_PROGPATH "$(progpath)" +#define SUBSTITUTE_USERPATH "$(userpath)" +#define SUBSTITUTE_PATH "$(path)" + +#define STRPOS_NOTFOUND -1 + +typedef std::unordered_map<OUString, sal_Int32> NameToHandleMap; + +typedef std::set<OUString> VarNameSet; + +// class SvtPathOptions_Impl --------------------------------------------- +class SvtPathOptions_Impl +{ + private: + // Local variables to return const references + o3tl::enumarray< SvtPathOptions::Paths, OUString > m_aPathArray; + Reference< XFastPropertySet > m_xPathSettings; + Reference< XStringSubstitution > m_xSubstVariables; + Reference< XMacroExpander > m_xMacroExpander; + mutable std::unordered_map<SvtPathOptions::Paths, sal_Int32> + m_aMapEnumToPropHandle; + VarNameSet m_aSystemPathVarNames; + + OUString m_aEmptyString; + mutable std::mutex m_aMutex; + + public: + SvtPathOptions_Impl(); + + // get the paths, not const because of using a mutex + const OUString& GetPath( SvtPathOptions::Paths ); + const OUString& GetAddinPath() { return GetPath( SvtPathOptions::Paths::AddIn ); } + const OUString& GetAutoCorrectPath() { return GetPath( SvtPathOptions::Paths::AutoCorrect ); } + const OUString& GetAutoTextPath() { return GetPath( SvtPathOptions::Paths::AutoText ); } + const OUString& GetBackupPath() { return GetPath( SvtPathOptions::Paths::Backup ); } + const OUString& GetBasicPath() { return GetPath( SvtPathOptions::Paths::Basic ); } + const OUString& GetBitmapPath() { return GetPath( SvtPathOptions::Paths::Bitmap ); } + const OUString& GetConfigPath() { return GetPath( SvtPathOptions::Paths::Config ); } + const OUString& GetDictionaryPath() { return GetPath( SvtPathOptions::Paths::Dictionary ); } + const OUString& GetFavoritesPath() { return GetPath( SvtPathOptions::Paths::Favorites ); } + const OUString& GetFilterPath() { return GetPath( SvtPathOptions::Paths::Filter ); } + const OUString& GetGalleryPath() { return GetPath( SvtPathOptions::Paths::Gallery ); } + const OUString& GetGraphicPath() { return GetPath( SvtPathOptions::Paths::Graphic ); } + const OUString& GetHelpPath() { return GetPath( SvtPathOptions::Paths::Help ); } + const OUString& GetLinguisticPath() { return GetPath( SvtPathOptions::Paths::Linguistic ); } + const OUString& GetModulePath() { return GetPath( SvtPathOptions::Paths::Module ); } + const OUString& GetPalettePath() { return GetPath( SvtPathOptions::Paths::Palette ); } + const OUString& GetIconsetPath() { return GetPath( SvtPathOptions::Paths::IconSet); } + const OUString& GetPluginPath() { return GetPath( SvtPathOptions::Paths::Plugin ); } + const OUString& GetStoragePath() { return GetPath( SvtPathOptions::Paths::Storage ); } + const OUString& GetTempPath() { return GetPath( SvtPathOptions::Paths::Temp ); } + const OUString& GetTemplatePath() { return GetPath( SvtPathOptions::Paths::Template ); } + const OUString& GetUserConfigPath() { return GetPath( SvtPathOptions::Paths::UserConfig ); } + const OUString& GetWorkPath() { return GetPath( SvtPathOptions::Paths::Work ); } + const OUString& GetUIConfigPath() { return GetPath( SvtPathOptions::Paths::UIConfig ); } + const OUString& GetFingerprintPath() { return GetPath( SvtPathOptions::Paths::Fingerprint ); } + const OUString& GetNumbertextPath() { return GetPath( SvtPathOptions::Paths::NumberText ); } + const OUString& GetClassificationPath() { return GetPath( SvtPathOptions::Paths::Classification ); } + + // set the paths + void SetPath( SvtPathOptions::Paths, const OUString& rNewPath ); + void SetAutoTextPath( const OUString& rPath ) { SetPath( SvtPathOptions::Paths::AutoText, rPath ); } + void SetBasicPath( const OUString& rPath ) { SetPath( SvtPathOptions::Paths::Basic, rPath ); } + void SetTempPath( const OUString& rPath ) { SetPath( SvtPathOptions::Paths::Temp, rPath ); } + + OUString SubstVar( const OUString& rVar ) const; + OUString ExpandMacros( const OUString& rPath ) const; + OUString UsePathVariables( const OUString& rPath ) const; +}; + +// global ---------------------------------------------------------------- + +static std::weak_ptr<SvtPathOptions_Impl> g_pOptions; + +namespace { + +// functions ------------------------------------------------------------- +struct PropertyStruct +{ + const char* pPropName; // The ascii name of the Office path + SvtPathOptions::Paths ePath; // The enum value used by SvtPathOptions +}; + +struct VarNameAttribute +{ + const char* pVarName; // The name of the path variable +}; + +} + +const PropertyStruct aPropNames[] = +{ + { "Addin", SvtPathOptions::Paths::AddIn }, + { "AutoCorrect", SvtPathOptions::Paths::AutoCorrect }, + { "AutoText", SvtPathOptions::Paths::AutoText }, + { "Backup", SvtPathOptions::Paths::Backup }, + { "Basic", SvtPathOptions::Paths::Basic }, + { "Bitmap", SvtPathOptions::Paths::Bitmap }, + { "Config", SvtPathOptions::Paths::Config }, + { "Dictionary", SvtPathOptions::Paths::Dictionary }, + { "Favorite", SvtPathOptions::Paths::Favorites }, + { "Filter", SvtPathOptions::Paths::Filter }, + { "Gallery", SvtPathOptions::Paths::Gallery }, + { "Graphic", SvtPathOptions::Paths::Graphic }, + { "Help", SvtPathOptions::Paths::Help }, + { "Iconset", SvtPathOptions::Paths::IconSet }, + { "Linguistic", SvtPathOptions::Paths::Linguistic }, + { "Module", SvtPathOptions::Paths::Module }, + { "Palette", SvtPathOptions::Paths::Palette }, + { "Plugin", SvtPathOptions::Paths::Plugin }, + { "Storage", SvtPathOptions::Paths::Storage }, + { "Temp", SvtPathOptions::Paths::Temp }, + { "Template", SvtPathOptions::Paths::Template }, + { "UserConfig", SvtPathOptions::Paths::UserConfig }, + { "Work", SvtPathOptions::Paths::Work }, + { "UIConfig", SvtPathOptions::Paths::UIConfig }, + { "Fingerprint", SvtPathOptions::Paths::Fingerprint }, + { "Numbertext", SvtPathOptions::Paths::NumberText }, + { "Classification", SvtPathOptions::Paths::Classification } +}; + +const VarNameAttribute aVarNameAttribute[] = +{ + { SUBSTITUTE_INSTPATH }, // $(instpath) + { SUBSTITUTE_PROGPATH }, // $(progpath) + { SUBSTITUTE_USERPATH }, // $(userpath) + { SUBSTITUTE_PATH }, // $(path) +}; + +// class SvtPathOptions_Impl --------------------------------------------- + +const OUString& SvtPathOptions_Impl::GetPath( SvtPathOptions::Paths ePath ) +{ + std::unique_lock aGuard( m_aMutex ); + + try + { + OUString aPathValue; + sal_Int32 nHandle = m_aMapEnumToPropHandle[ePath]; + + // Substitution is done by the service itself using the substitution service + Any a = m_xPathSettings->getFastPropertyValue( nHandle ); + a >>= aPathValue; + if( ePath == SvtPathOptions::Paths::AddIn || + ePath == SvtPathOptions::Paths::Filter || + ePath == SvtPathOptions::Paths::Help || + ePath == SvtPathOptions::Paths::Module || + ePath == SvtPathOptions::Paths::Plugin || + ePath == SvtPathOptions::Paths::Storage + ) + { + // These office paths have to be converted to system pates + OUString aResult; + osl::FileBase::getSystemPathFromFileURL( aPathValue, aResult ); + aPathValue = aResult; + } + else if (ePath == SvtPathOptions::Paths::Palette || + ePath == SvtPathOptions::Paths::IconSet) + { + auto ctx = comphelper::getProcessComponentContext(); + OUStringBuffer buf(aPathValue.getLength()*2); + for (sal_Int32 i = 0;;) + { + buf.append( + comphelper::getExpandedUri( + ctx, aPathValue.getToken(0, ';', i))); + if (i == -1) { + break; + } + buf.append(';'); + } + aPathValue = buf.makeStringAndClear(); + } + + m_aPathArray[ ePath ] = aPathValue; + return m_aPathArray[ ePath ]; + } + catch (UnknownPropertyException &) + { + } + + return m_aEmptyString; +} + +void SvtPathOptions_Impl::SetPath( SvtPathOptions::Paths ePath, const OUString& rNewPath ) +{ + std::unique_lock aGuard( m_aMutex ); + + OUString aResult; + OUString aNewValue; + Any a; + + switch ( ePath ) + { + case SvtPathOptions::Paths::AddIn: + case SvtPathOptions::Paths::Filter: + case SvtPathOptions::Paths::Help: + case SvtPathOptions::Paths::Module: + case SvtPathOptions::Paths::Plugin: + case SvtPathOptions::Paths::Storage: + { + // These office paths have to be convert back to UCB-URL's + osl::FileBase::getFileURLFromSystemPath( rNewPath, aResult ); + aNewValue = aResult; + } + break; + + default: + aNewValue = rNewPath; + } + + // Resubstitution is done by the service itself using the substitution service + a <<= aNewValue; + try + { + m_xPathSettings->setFastPropertyValue( m_aMapEnumToPropHandle[ePath], a ); + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION("unotools.config", "SetPath"); + } +} + +OUString SvtPathOptions_Impl::ExpandMacros( const OUString& rPath ) const +{ + OUString sExpanded( rPath ); + + const INetURLObject aParser( rPath ); + if ( aParser.GetProtocol() == INetProtocol::VndSunStarExpand ) + sExpanded = m_xMacroExpander->expandMacros( aParser.GetURLPath( INetURLObject::DecodeMechanism::WithCharset ) ); + + return sExpanded; +} + +OUString SvtPathOptions_Impl::UsePathVariables( const OUString& rPath ) const +{ + return m_xSubstVariables->reSubstituteVariables( rPath ); +} + +OUString SvtPathOptions_Impl::SubstVar( const OUString& rVar ) const +{ + // Don't work at parameter-string directly. Copy it. + OUString aWorkText = rVar; + + // Convert the returned path to system path! + bool bConvertLocal = false; + + // Search for first occurrence of "$(...". + sal_Int32 nPosition = aWorkText.indexOf( SIGN_STARTVARIABLE ); // = first position of "$(" in string + sal_Int32 nLength = 0; // = count of letters from "$(" to ")" in string + + // Have we found any variable like "$(...)"? + if ( nPosition != STRPOS_NOTFOUND ) + { + // Yes; Get length of found variable. + // If no ")" was found - nLength is set to 0 by default! see before. + sal_Int32 nEndPosition = aWorkText.indexOf( SIGN_ENDVARIABLE, nPosition ); + if ( nEndPosition != STRPOS_NOTFOUND ) + nLength = nEndPosition - nPosition + 1; + } + + // Is there another path variable? + while ( ( nPosition != STRPOS_NOTFOUND ) && ( nLength > 0 ) ) + { + // YES; Get the next variable for replace. + OUString aSubString = aWorkText.copy( nPosition, nLength ); + aSubString = aSubString.toAsciiLowerCase(); + + // Look for special variable that needs a system path. + VarNameSet::const_iterator pIter = m_aSystemPathVarNames.find( aSubString ); + if ( pIter != m_aSystemPathVarNames.end() ) + bConvertLocal = true; + + nPosition += nLength; + + // We must control index in string before call something at OUString! + // The OUString-implementation don't do it for us :-( but the result is not defined otherwise. + if ( nPosition + 1 > aWorkText.getLength() ) + { + // Position is out of range. Break loop! + nPosition = STRPOS_NOTFOUND; + nLength = 0; + } + else + { + // Else; Position is valid. Search for next variable. + nPosition = aWorkText.indexOf( SIGN_STARTVARIABLE, nPosition ); + // Have we found any variable like "$(...)"? + if ( nPosition != STRPOS_NOTFOUND ) + { + // Yes; Get length of found variable. If no ")" was found - nLength must set to 0! + nLength = 0; + sal_Int32 nEndPosition = aWorkText.indexOf( SIGN_ENDVARIABLE, nPosition ); + if ( nEndPosition != STRPOS_NOTFOUND ) + nLength = nEndPosition - nPosition + 1; + } + } + } + + aWorkText = m_xSubstVariables->substituteVariables( rVar, false ); + + if ( bConvertLocal ) + { + // Convert the URL to a system path for special path variables + OUString aReturn; + osl::FileBase::getSystemPathFromFileURL( aWorkText, aReturn ); + return aReturn; + } + + return aWorkText; +} + +SvtPathOptions_Impl::SvtPathOptions_Impl() +{ + Reference< XComponentContext > xContext = comphelper::getProcessComponentContext(); + + // Create necessary services + Reference< XPathSettings > xPathSettings = thePathSettings::get(xContext); + m_xPathSettings.set( xPathSettings, UNO_QUERY_THROW ); + m_xSubstVariables.set( PathSubstitution::create(xContext) ); + m_xMacroExpander = theMacroExpander::get(xContext); + + // Create temporary hash map to have a mapping between property names and property handles + Reference< XPropertySetInfo > xPropSetInfo = xPathSettings->getPropertySetInfo(); + const Sequence< Property > aPathPropSeq = xPropSetInfo->getProperties(); + + NameToHandleMap aTempHashMap; + for ( const css::beans::Property& aProperty : aPathPropSeq ) + { + aTempHashMap.emplace(aProperty.Name, aProperty.Handle); + } + + // Create mapping between internal enum (SvtPathOptions::Paths) and property handle + for ( auto const & p : aPropNames ) + { + NameToHandleMap::const_iterator pIter = + aTempHashMap.find( OUString::createFromAscii( p.pPropName )); + + if ( pIter != aTempHashMap.end() ) + { + sal_Int32 nHandle = pIter->second; + SvtPathOptions::Paths nEnum = p.ePath; + m_aMapEnumToPropHandle.emplace( nEnum, nHandle ); + } + } + + // Create hash map for path variables that need a system path as a return value! + for ( auto const & i : aVarNameAttribute ) + { + m_aSystemPathVarNames.insert( OUString::createFromAscii( i.pVarName ) ); + } +} + +// class SvtPathOptions -------------------------------------------------- + +namespace +{ + std::mutex& lclMutex() + { + static std::mutex SINGLETON; + return SINGLETON; + } +} + +SvtPathOptions::SvtPathOptions() +{ + // Global access, must be guarded (multithreading) + std::unique_lock aGuard( lclMutex() ); + pImpl = g_pOptions.lock(); + if ( !pImpl ) + { + pImpl = std::make_shared<SvtPathOptions_Impl>(); + g_pOptions = pImpl; + aGuard.unlock(); // because holdConfigItem will call this constructor + ItemHolder1::holdConfigItem(EItem::PathOptions); + } +} + +SvtPathOptions::~SvtPathOptions() +{ + // Global access, must be guarded (multithreading) + std::unique_lock aGuard( lclMutex() ); + + pImpl.reset(); +} + +const OUString& SvtPathOptions::GetAddinPath() const +{ + return pImpl->GetAddinPath(); +} + +const OUString& SvtPathOptions::GetAutoCorrectPath() const +{ + return pImpl->GetAutoCorrectPath(); +} + +const OUString& SvtPathOptions::GetAutoTextPath() const +{ + return pImpl->GetAutoTextPath(); +} + +const OUString& SvtPathOptions::GetBackupPath() const +{ + return pImpl->GetBackupPath(); +} + +const OUString& SvtPathOptions::GetBasicPath() const +{ + return pImpl->GetBasicPath(); +} + +const OUString& SvtPathOptions::GetBitmapPath() const +{ + return pImpl->GetBitmapPath(); +} + +const OUString& SvtPathOptions::GetConfigPath() const +{ + return pImpl->GetConfigPath(); +} + +const OUString& SvtPathOptions::GetDictionaryPath() const +{ + return pImpl->GetDictionaryPath(); +} + +const OUString& SvtPathOptions::GetFavoritesPath() const +{ + return pImpl->GetFavoritesPath(); +} + +const OUString& SvtPathOptions::GetFilterPath() const +{ + return pImpl->GetFilterPath(); +} + +const OUString& SvtPathOptions::GetGalleryPath() const +{ + return pImpl->GetGalleryPath(); +} + +const OUString& SvtPathOptions::GetGraphicPath() const +{ + return pImpl->GetGraphicPath(); +} + +const OUString& SvtPathOptions::GetHelpPath() const +{ + return pImpl->GetHelpPath(); +} + +const OUString& SvtPathOptions::GetLinguisticPath() const +{ + return pImpl->GetLinguisticPath(); +} + +const OUString& SvtPathOptions::GetFingerprintPath() const +{ + return pImpl->GetFingerprintPath(); +} + +const OUString& SvtPathOptions::GetNumbertextPath() const +{ + return pImpl->GetNumbertextPath(); +} + +const OUString& SvtPathOptions::GetModulePath() const +{ + return pImpl->GetModulePath(); +} + +const OUString& SvtPathOptions::GetPalettePath() const +{ + return pImpl->GetPalettePath(); +} + +const OUString& SvtPathOptions::GetIconsetPath() const +{ + return pImpl->GetIconsetPath(); +} + +const OUString& SvtPathOptions::GetPluginPath() const +{ + return pImpl->GetPluginPath(); +} + +const OUString& SvtPathOptions::GetStoragePath() const +{ + return pImpl->GetStoragePath(); +} + +const OUString& SvtPathOptions::GetTempPath() const +{ + return pImpl->GetTempPath(); +} + +const OUString& SvtPathOptions::GetTemplatePath() const +{ + return pImpl->GetTemplatePath(); +} + +const OUString& SvtPathOptions::GetUserConfigPath() const +{ + return pImpl->GetUserConfigPath(); +} + +const OUString& SvtPathOptions::GetWorkPath() const +{ + return pImpl->GetWorkPath(); +} + +const OUString& SvtPathOptions::GetClassificationPath() const +{ + return pImpl->GetClassificationPath(); +} + +void SvtPathOptions::SetAutoTextPath( const OUString& rPath ) +{ + pImpl->SetAutoTextPath( rPath ); +} + +void SvtPathOptions::SetBasicPath( const OUString& rPath ) +{ + pImpl->SetBasicPath( rPath ); +} + +void SvtPathOptions::SetTempPath( const OUString& rPath ) +{ + pImpl->SetTempPath( rPath ); +} + +OUString SvtPathOptions::SubstituteVariable( const OUString& rVar ) const +{ + return pImpl->SubstVar( rVar ); +} + +OUString SvtPathOptions::ExpandMacros( const OUString& rPath ) const +{ + return pImpl->ExpandMacros( rPath ); +} + +OUString SvtPathOptions::UseVariable( const OUString& rPath ) const +{ + return pImpl->UsePathVariables( rPath ); +} + +bool SvtPathOptions::SearchFile( OUString& rIniFile, SvtPathOptions::Paths ePath ) +{ + // check parameter: empty inifile name? + if ( rIniFile.isEmpty() ) + { + SAL_WARN( "unotools.config", "SvtPathOptions::SearchFile(): invalid parameter" ); + return false; + } + + OUString aIniFile = pImpl->SubstVar( rIniFile ); + bool bRet = false; + + switch ( ePath ) + { + case SvtPathOptions::Paths::UserConfig: + { + // path is a URL + bRet = true; + INetURLObject aObj( GetUserConfigPath() ); + + sal_Int32 nIniIndex = 0; + do + { + std::u16string_view aToken = o3tl::getToken(aIniFile, 0, '/', nIniIndex ); + aObj.insertName(aToken); + } + while ( nIniIndex >= 0 ); + + if ( !::utl::UCBContentHelper::Exists( aObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ) ) ) + { + aObj.SetSmartURL( GetConfigPath() ); + aObj.insertName( aIniFile ); + bRet = ::utl::UCBContentHelper::Exists( aObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ) ); + } + + if ( bRet ) + rIniFile = aObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + + break; + } + + default: + { + OUString aPath; + switch ( ePath ) + { + case SvtPathOptions::Paths::AddIn: aPath = GetAddinPath(); break; + case SvtPathOptions::Paths::AutoCorrect: aPath = GetAutoCorrectPath(); break; + case SvtPathOptions::Paths::AutoText: aPath = GetAutoTextPath(); break; + case SvtPathOptions::Paths::Backup: aPath = GetBackupPath(); break; + case SvtPathOptions::Paths::Basic: aPath = GetBasicPath(); break; + case SvtPathOptions::Paths::Bitmap: aPath = GetBitmapPath(); break; + case SvtPathOptions::Paths::Config: aPath = GetConfigPath(); break; + case SvtPathOptions::Paths::Dictionary: aPath = GetDictionaryPath(); break; + case SvtPathOptions::Paths::Favorites: aPath = GetFavoritesPath(); break; + case SvtPathOptions::Paths::Filter: aPath = GetFilterPath(); break; + case SvtPathOptions::Paths::Gallery: aPath = GetGalleryPath(); break; + case SvtPathOptions::Paths::Graphic: aPath = GetGraphicPath(); break; + case SvtPathOptions::Paths::Help: aPath = GetHelpPath(); break; + case SvtPathOptions::Paths::Linguistic: aPath = GetLinguisticPath(); break; + case SvtPathOptions::Paths::Module: aPath = GetModulePath(); break; + case SvtPathOptions::Paths::Palette: aPath = GetPalettePath(); break; + case SvtPathOptions::Paths::IconSet: aPath = GetIconsetPath(); break; + case SvtPathOptions::Paths::Plugin: aPath = GetPluginPath(); break; + case SvtPathOptions::Paths::Storage: aPath = GetStoragePath(); break; + case SvtPathOptions::Paths::Temp: aPath = GetTempPath(); break; + case SvtPathOptions::Paths::Template: aPath = GetTemplatePath(); break; + case SvtPathOptions::Paths::Work: aPath = GetWorkPath(); break; + case SvtPathOptions::Paths::UIConfig: aPath = pImpl->GetUIConfigPath(); break; + case SvtPathOptions::Paths::Fingerprint: aPath = GetFingerprintPath(); break; + case SvtPathOptions::Paths::NumberText: aPath = GetNumbertextPath(); break; + case SvtPathOptions::Paths::Classification: aPath = GetClassificationPath(); break; + // coverity[dead_error_begin] - following conditions exist to avoid compiler warning + case SvtPathOptions::Paths::UserConfig: + case SvtPathOptions::Paths::LAST: + break; + } + + sal_Int32 nPathIndex = 0; + do + { + bool bIsURL = true; + OUString aPathToken( aPath.getToken( 0, SEARCHPATH_DELIMITER, nPathIndex ) ); + INetURLObject aObj( aPathToken ); + if ( aObj.HasError() ) + { + bIsURL = false; + OUString aURL; + if ( osl::FileBase::getFileURLFromSystemPath( aPathToken, aURL ) + == osl::FileBase::E_None ) + aObj.SetURL( aURL ); + } + if ( aObj.GetProtocol() == INetProtocol::VndSunStarExpand ) + { + Reference< XMacroExpander > xMacroExpander = theMacroExpander::get( ::comphelper::getProcessComponentContext() ); + const OUString sExpandedPath = xMacroExpander->expandMacros( aObj.GetURLPath( INetURLObject::DecodeMechanism::WithCharset ) ); + aObj.SetURL( sExpandedPath ); + } + + sal_Int32 nIniIndex = 0; + do + { + std::u16string_view aToken = o3tl::getToken(aIniFile, 0, '/', nIniIndex ); + aObj.insertName(aToken); + } + while ( nIniIndex >= 0 ); + + bRet = ::utl::UCBContentHelper::Exists( aObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ) ); + + if ( bRet ) + { + if ( !bIsURL ) + { + OUString sTmp; + osl::FileBase::getSystemPathFromFileURL( + aObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ), sTmp ); + rIniFile = sTmp; + } + else + rIniFile = aObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + break; + } + } + while ( nPathIndex >= 0 ); + } + } + + return bRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/config/saveopt.cxx b/unotools/source/config/saveopt.cxx new file mode 100644 index 0000000000..b91ba86396 --- /dev/null +++ b/unotools/source/config/saveopt.cxx @@ -0,0 +1,94 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <sal/log.hxx> +#include <unotools/saveopt.hxx> +#include <unotools/configmgr.hxx> + +#include <officecfg/Office/Common.hxx> + +using namespace utl; +using namespace com::sun::star::uno; + +void SetODFDefaultVersion( SvtSaveOptions::ODFDefaultVersion eVersion, const std::shared_ptr<comphelper::ConfigurationChanges>& xChanges ) +{ + sal_Int16 nTmp = (eVersion == SvtSaveOptions::ODFVER_LATEST) ? sal_Int16( 3 ) : sal_Int16( eVersion ); + officecfg::Office::Common::Save::ODF::DefaultVersion::set(nTmp, xChanges); +} + +void SetODFDefaultVersion( SvtSaveOptions::ODFDefaultVersion eVersion ) +{ + auto xChanges = comphelper::ConfigurationChanges::create(); + SetODFDefaultVersion(eVersion, xChanges); + xChanges->commit(); +} + +SvtSaveOptions::ODFDefaultVersion GetODFDefaultVersion() +{ + SvtSaveOptions::ODFDefaultVersion nRet; + sal_Int16 nTmp = officecfg::Office::Common::Save::ODF::DefaultVersion::get(); + if( nTmp == 3 ) + nRet = SvtSaveOptions::ODFVER_LATEST; + else + nRet = SvtSaveOptions::ODFDefaultVersion( nTmp ); + SAL_WARN_IF(nRet == SvtSaveOptions::ODFVER_UNKNOWN, "unotools.config", "DefaultVersion is ODFVER_UNKNOWN?"); + return (nRet == SvtSaveOptions::ODFVER_UNKNOWN) ? SvtSaveOptions::ODFVER_LATEST : nRet; +} + +SvtSaveOptions::ODFSaneDefaultVersion GetODFSaneDefaultVersion() +{ + SvtSaveOptions::ODFDefaultVersion nRet; + sal_Int16 nTmp = officecfg::Office::Common::Save::ODF::DefaultVersion::get(); + if( nTmp == 3 ) + nRet = SvtSaveOptions::ODFVER_LATEST; + else + nRet = SvtSaveOptions::ODFDefaultVersion( nTmp ); + + return GetODFSaneDefaultVersion(nRet); +} + +SvtSaveOptions::ODFSaneDefaultVersion GetODFSaneDefaultVersion(SvtSaveOptions::ODFDefaultVersion eODFDefaultVersion) +{ + switch (eODFDefaultVersion) + { + default: + assert(!"map new ODFDefaultVersion to ODFSaneDefaultVersion"); + break; + case SvtSaveOptions::ODFVER_UNKNOWN: + case SvtSaveOptions::ODFVER_LATEST: + return SvtSaveOptions::ODFSVER_LATEST_EXTENDED; + case SvtSaveOptions::ODFVER_010: + return SvtSaveOptions::ODFSVER_010; + case SvtSaveOptions::ODFVER_011: + return SvtSaveOptions::ODFSVER_011; + case SvtSaveOptions::ODFVER_012: + return SvtSaveOptions::ODFSVER_012; + case SvtSaveOptions::ODFVER_012_EXT_COMPAT: + return SvtSaveOptions::ODFSVER_012_EXT_COMPAT; + case SvtSaveOptions::ODFVER_012_EXTENDED: + return SvtSaveOptions::ODFSVER_012_EXTENDED; + case SvtSaveOptions::ODFVER_013: + return SvtSaveOptions::ODFSVER_013; + } + return SvtSaveOptions::ODFSVER_LATEST_EXTENDED; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/config/searchopt.cxx b/unotools/source/config/searchopt.cxx new file mode 100644 index 0000000000..4237fd7281 --- /dev/null +++ b/unotools/source/config/searchopt.cxx @@ -0,0 +1,612 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <unotools/searchopt.hxx> +#include <tools/debug.hxx> +#include <unotools/configitem.hxx> +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/uno/Any.h> +#include <osl/diagnose.h> +#include <i18nutil/transliteration.hxx> + +using namespace utl; +using namespace com::sun::star::uno; + +#define MAX_FLAGS_OFFSET 29 + +class SvtSearchOptions_Impl : public ConfigItem +{ + sal_Int32 nFlags; + bool bModified; + + SvtSearchOptions_Impl(const SvtSearchOptions_Impl&) = delete; + SvtSearchOptions_Impl& operator=(const SvtSearchOptions_Impl&) = delete; + + // ConfigItem + virtual void ImplCommit() override; + +protected: + bool IsModified() const { return bModified; } + using ConfigItem::SetModified; + void SetModified( bool bVal ); + void Load(); + bool Save(); + + static Sequence< OUString > GetPropertyNames(); + +public: + SvtSearchOptions_Impl(); + virtual ~SvtSearchOptions_Impl() override; + + virtual void Notify( const css::uno::Sequence< OUString >& aPropertyNames ) override; + + bool GetFlag( sal_uInt16 nOffset ) const; + void SetFlag( sal_uInt16 nOffset, bool bVal ); + void SetSearchAlgorithm( sal_uInt16 nOffset, bool bVal ); +}; + +SvtSearchOptions_Impl::SvtSearchOptions_Impl() : + ConfigItem( "Office.Common/SearchOptions" ), + nFlags(0x0003FFFF) // set all options values to 'true' + +{ + Load(); + SetModified( false ); +} + +SvtSearchOptions_Impl::~SvtSearchOptions_Impl() +{ + assert(!IsModified()); // should have been committed +} + +void SvtSearchOptions_Impl::ImplCommit() +{ + if (IsModified()) + Save(); +} + +void SvtSearchOptions_Impl::Notify( const Sequence< OUString >& ) +{ +} + +bool SvtSearchOptions_Impl::GetFlag( sal_uInt16 nOffset ) const +{ + DBG_ASSERT( nOffset <= MAX_FLAGS_OFFSET, "offset out of range"); + return ((nFlags >> nOffset) & 0x01) != 0; +} + +void SvtSearchOptions_Impl::SetFlag( sal_uInt16 nOffset, bool bVal ) +{ + DBG_ASSERT( nOffset <= MAX_FLAGS_OFFSET, "offset out of range"); + sal_Int32 nOldFlags = nFlags; + sal_Int32 nMask = (sal_Int32(1)) << nOffset; + if (bVal) + nFlags |= nMask; + else + nFlags &= ~nMask; + if (nFlags != nOldFlags) + SetModified( true ); +} + +void SvtSearchOptions_Impl::SetModified( bool bVal ) +{ + bModified = bVal; + if (bModified) + { + ConfigItem::SetModified(); + } +} + +Sequence< OUString > SvtSearchOptions_Impl::GetPropertyNames() +{ + static const char* aPropNames[ MAX_FLAGS_OFFSET + 1 ] = + { + "IsWholeWordsOnly", // 0 + "IsBackwards", // 1 + "IsUseRegularExpression", // 2 + //"IsCurrentSelectionOnly", // interactively set or not... + "IsSearchForStyles", // 3 + "IsSimilaritySearch", // 4 + "IsUseAsianOptions", // 5 + "IsMatchCase", // 6 + "Japanese/IsMatchFullHalfWidthForms", // 7 + "Japanese/IsMatchHiraganaKatakana", // 8 + "Japanese/IsMatchContractions", // 9 + "Japanese/IsMatchMinusDashCho-on", // 10 + "Japanese/IsMatchRepeatCharMarks", // 11 + "Japanese/IsMatchVariantFormKanji", // 12 + "Japanese/IsMatchOldKanaForms", // 13 + "Japanese/IsMatch_DiZi_DuZu", // 14 + "Japanese/IsMatch_BaVa_HaFa", // 15 + "Japanese/IsMatch_TsiThiChi_DhiZi", // 16 + "Japanese/IsMatch_HyuIyu_ByuVyu", // 17 + "Japanese/IsMatch_SeShe_ZeJe", // 18 + "Japanese/IsMatch_IaIya", // 19 + "Japanese/IsMatch_KiKu", // 20 + "Japanese/IsIgnorePunctuation", // 21 + "Japanese/IsIgnoreWhitespace", // 22 + "Japanese/IsIgnoreProlongedSoundMark", // 23 + "Japanese/IsIgnoreMiddleDot", // 24 + "IsNotes", // 25 + "IsIgnoreDiacritics_CTL", // 26 + "IsIgnoreKashida_CTL", // 27 + "IsSearchFormatted", // 28 + "IsUseWildcard" // 29 + }; + + Sequence< OUString > aNames(std::size(aPropNames)); + OUString* pNames = aNames.getArray(); + for (std::size_t i = 0; i < std::size(aPropNames); ++i) + pNames[i] = OUString::createFromAscii( aPropNames[i] ); + + return aNames; +} + +void SvtSearchOptions_Impl::SetSearchAlgorithm( sal_uInt16 nOffset, bool bVal ) +{ + if (bVal) + { + // Search algorithms are mutually exclusive. + if (nOffset != 2 && GetFlag(2)) + SetFlag( 2, false ); + if (nOffset != 4 && GetFlag(4)) + SetFlag( 4, false ); + if (nOffset != 29 && GetFlag(29)) + SetFlag( 29, false ); + } + SetFlag( nOffset, bVal ); +} + +void SvtSearchOptions_Impl::Load() +{ + bool bSucc = false; + + Sequence< OUString > aNames = GetPropertyNames(); + sal_Int32 nProps = aNames.getLength(); + + const Sequence< Any > aValues = GetProperties( aNames ); + DBG_ASSERT( aValues.getLength() == aNames.getLength(), + "GetProperties failed" ); + //EnableNotification( aNames ); + + if (nProps && aValues.getLength() == nProps) + { + bSucc = true; + + const Any* pValues = aValues.getConstArray(); + for (sal_Int32 i = 0; i < nProps; ++i) + { + const Any &rVal = pValues[i]; + DBG_ASSERT( rVal.hasValue(), "property value missing" ); + if (rVal.hasValue()) + { + bool bVal = bool(); + if (rVal >>= bVal) + { + if (i <= MAX_FLAGS_OFFSET) + { + // use index in sequence as flag index + SetFlag( i, bVal ); + } + else { + OSL_FAIL( "unexpected index" ); + } + } + else + { + OSL_FAIL( "unexpected type" ); + bSucc = false; + } + } + else + { + OSL_FAIL( "value missing" ); + bSucc = false; + } + } + } + DBG_ASSERT( bSucc, "LoadConfig failed" ); +} + +bool SvtSearchOptions_Impl::Save() +{ + bool bSucc = false; + + const Sequence< OUString > aNames = GetPropertyNames(); + sal_Int32 nProps = aNames.getLength(); + + Sequence< Any > aValues( nProps ); + Any *pValue = aValues.getArray(); + + DBG_ASSERT( nProps == MAX_FLAGS_OFFSET + 1, + "unexpected size of index" ); + if (nProps == MAX_FLAGS_OFFSET + 1) + { + for (sal_Int32 i = 0; i < nProps; ++i) + pValue[i] <<= GetFlag(i); + bSucc |= PutProperties( aNames, aValues ); + } + + if (bSucc) + SetModified( false ); + + return bSucc; +} + +SvtSearchOptions::SvtSearchOptions() + : pImpl( new SvtSearchOptions_Impl ) +{ +} + +SvtSearchOptions::~SvtSearchOptions() +{ +} + +void SvtSearchOptions::Commit() +{ + pImpl->Commit(); +} + +TransliterationFlags SvtSearchOptions::GetTransliterationFlags() const +{ + TransliterationFlags nRes = TransliterationFlags::NONE; + + if (!IsMatchCase()) // 'IsMatchCase' means act case sensitive + nRes |= TransliterationFlags::IGNORE_CASE; + if ( IsMatchFullHalfWidthForms()) + nRes |= TransliterationFlags::IGNORE_WIDTH; + if ( IsMatchHiraganaKatakana()) + nRes |= TransliterationFlags::IGNORE_KANA; + if ( IsMatchContractions()) + nRes |= TransliterationFlags::ignoreSize_ja_JP; + if ( IsMatchMinusDashChoon()) + nRes |= TransliterationFlags::ignoreMinusSign_ja_JP; + if ( IsMatchRepeatCharMarks()) + nRes |= TransliterationFlags::ignoreIterationMark_ja_JP; + if ( IsMatchVariantFormKanji()) + nRes |= TransliterationFlags::ignoreTraditionalKanji_ja_JP; + if ( IsMatchOldKanaForms()) + nRes |= TransliterationFlags::ignoreTraditionalKana_ja_JP; + if ( IsMatchDiziDuzu()) + nRes |= TransliterationFlags::ignoreZiZu_ja_JP; + if ( IsMatchBavaHafa()) + nRes |= TransliterationFlags::ignoreBaFa_ja_JP; + if ( IsMatchTsithichiDhizi()) + nRes |= TransliterationFlags::ignoreTiJi_ja_JP; + if ( IsMatchHyuiyuByuvyu()) + nRes |= TransliterationFlags::ignoreHyuByu_ja_JP; + if ( IsMatchSesheZeje()) + nRes |= TransliterationFlags::ignoreSeZe_ja_JP; + if ( IsMatchIaiya()) + nRes |= TransliterationFlags::ignoreIandEfollowedByYa_ja_JP; + if ( IsMatchKiku()) + nRes |= TransliterationFlags::ignoreKiKuFollowedBySa_ja_JP; + if ( IsIgnorePunctuation()) + nRes |= TransliterationFlags::ignoreSeparator_ja_JP; + if ( IsIgnoreWhitespace()) + nRes |= TransliterationFlags::ignoreSpace_ja_JP; + if ( IsIgnoreProlongedSoundMark()) + nRes |= TransliterationFlags::ignoreProlongedSoundMark_ja_JP; + if ( IsIgnoreMiddleDot()) + nRes |= TransliterationFlags::ignoreMiddleDot_ja_JP; + if ( IsIgnoreDiacritics_CTL()) + nRes |= TransliterationFlags::IGNORE_DIACRITICS_CTL; + if ( IsIgnoreKashida_CTL()) + nRes |= TransliterationFlags::IGNORE_KASHIDA_CTL; + return nRes; +} + +bool SvtSearchOptions::IsWholeWordsOnly() const +{ + return pImpl->GetFlag( 0 ); +} + +void SvtSearchOptions::SetWholeWordsOnly( bool bVal ) +{ + pImpl->SetFlag( 0, bVal ); +} + +bool SvtSearchOptions::IsBackwards() const +{ + return pImpl->GetFlag( 1 ); +} + +void SvtSearchOptions::SetBackwards( bool bVal ) +{ + pImpl->SetFlag( 1, bVal ); +} + +bool SvtSearchOptions::IsUseRegularExpression() const +{ + return pImpl->GetFlag( 2 ); +} + +void SvtSearchOptions::SetUseRegularExpression( bool bVal ) +{ + pImpl->SetSearchAlgorithm( 2, bVal ); +} + +void SvtSearchOptions::SetSearchForStyles( bool bVal ) +{ + pImpl->SetFlag( 3, bVal ); +} + +bool SvtSearchOptions::IsSimilaritySearch() const +{ + return pImpl->GetFlag( 4 ); +} + +void SvtSearchOptions::SetSimilaritySearch( bool bVal ) +{ + pImpl->SetSearchAlgorithm( 4, bVal ); +} + +bool SvtSearchOptions::IsUseAsianOptions() const +{ + return pImpl->GetFlag( 5 ); +} + +void SvtSearchOptions::SetUseAsianOptions( bool bVal ) +{ + pImpl->SetFlag( 5, bVal ); +} + +bool SvtSearchOptions::IsMatchCase() const +{ + return pImpl->GetFlag( 6 ); +} + +void SvtSearchOptions::SetMatchCase( bool bVal ) +{ + pImpl->SetFlag( 6, bVal ); +} + +bool SvtSearchOptions::IsMatchFullHalfWidthForms() const +{ + return pImpl->GetFlag( 7 ); +} + +void SvtSearchOptions::SetMatchFullHalfWidthForms( bool bVal ) +{ + pImpl->SetFlag( 7, bVal ); +} + +bool SvtSearchOptions::IsMatchHiraganaKatakana() const +{ + return pImpl->GetFlag( 8 ); +} + +void SvtSearchOptions::SetMatchHiraganaKatakana( bool bVal ) +{ + pImpl->SetFlag( 8, bVal ); +} + +bool SvtSearchOptions::IsMatchContractions() const +{ + return pImpl->GetFlag( 9 ); +} + +void SvtSearchOptions::SetMatchContractions( bool bVal ) +{ + pImpl->SetFlag( 9, bVal ); +} + +bool SvtSearchOptions::IsMatchMinusDashChoon() const +{ + return pImpl->GetFlag( 10 ); +} + +void SvtSearchOptions::SetMatchMinusDashChoon( bool bVal ) +{ + pImpl->SetFlag( 10, bVal ); +} + +bool SvtSearchOptions::IsMatchRepeatCharMarks() const +{ + return pImpl->GetFlag( 11 ); +} + +void SvtSearchOptions::SetMatchRepeatCharMarks( bool bVal ) +{ + pImpl->SetFlag( 11, bVal ); +} + +bool SvtSearchOptions::IsMatchVariantFormKanji() const +{ + return pImpl->GetFlag( 12 ); +} + +void SvtSearchOptions::SetMatchVariantFormKanji( bool bVal ) +{ + pImpl->SetFlag( 12, bVal ); +} + +bool SvtSearchOptions::IsMatchOldKanaForms() const +{ + return pImpl->GetFlag( 13 ); +} + +void SvtSearchOptions::SetMatchOldKanaForms( bool bVal ) +{ + pImpl->SetFlag( 13, bVal ); +} + +bool SvtSearchOptions::IsMatchDiziDuzu() const +{ + return pImpl->GetFlag( 14 ); +} + +void SvtSearchOptions::SetMatchDiziDuzu( bool bVal ) +{ + pImpl->SetFlag( 14, bVal ); +} + +bool SvtSearchOptions::IsMatchBavaHafa() const +{ + return pImpl->GetFlag( 15 ); +} + +void SvtSearchOptions::SetMatchBavaHafa( bool bVal ) +{ + pImpl->SetFlag( 15, bVal ); +} + +bool SvtSearchOptions::IsMatchTsithichiDhizi() const +{ + return pImpl->GetFlag( 16 ); +} + +void SvtSearchOptions::SetMatchTsithichiDhizi( bool bVal ) +{ + pImpl->SetFlag( 16, bVal ); +} + +bool SvtSearchOptions::IsMatchHyuiyuByuvyu() const +{ + return pImpl->GetFlag( 17 ); +} + +void SvtSearchOptions::SetMatchHyuiyuByuvyu( bool bVal ) +{ + pImpl->SetFlag( 17, bVal ); +} + +bool SvtSearchOptions::IsMatchSesheZeje() const +{ + return pImpl->GetFlag( 18 ); +} + +void SvtSearchOptions::SetMatchSesheZeje( bool bVal ) +{ + pImpl->SetFlag( 18, bVal ); +} + +bool SvtSearchOptions::IsMatchIaiya() const +{ + return pImpl->GetFlag( 19 ); +} + +void SvtSearchOptions::SetMatchIaiya( bool bVal ) +{ + pImpl->SetFlag( 19, bVal ); +} + +bool SvtSearchOptions::IsMatchKiku() const +{ + return pImpl->GetFlag( 20 ); +} + +void SvtSearchOptions::SetMatchKiku( bool bVal ) +{ + pImpl->SetFlag( 20, bVal ); +} + +bool SvtSearchOptions::IsIgnorePunctuation() const +{ + return pImpl->GetFlag( 21 ); +} + +void SvtSearchOptions::SetIgnorePunctuation( bool bVal ) +{ + pImpl->SetFlag( 21, bVal ); +} + +bool SvtSearchOptions::IsIgnoreWhitespace() const +{ + return pImpl->GetFlag( 22 ); +} + +void SvtSearchOptions::SetIgnoreWhitespace( bool bVal ) +{ + pImpl->SetFlag( 22, bVal ); +} + +bool SvtSearchOptions::IsIgnoreProlongedSoundMark() const +{ + return pImpl->GetFlag( 23 ); +} + +void SvtSearchOptions::SetIgnoreProlongedSoundMark( bool bVal ) +{ + pImpl->SetFlag( 23, bVal ); +} + +bool SvtSearchOptions::IsIgnoreMiddleDot() const +{ + return pImpl->GetFlag( 24 ); +} + +void SvtSearchOptions::SetIgnoreMiddleDot( bool bVal ) +{ + pImpl->SetFlag( 24, bVal ); +} + +bool SvtSearchOptions::IsNotes() const +{ + return pImpl->GetFlag( 25 ); +} + +void SvtSearchOptions::SetNotes( bool bVal ) +{ + pImpl->SetFlag( 25, bVal ); +} + +bool SvtSearchOptions::IsIgnoreDiacritics_CTL() const +{ + return pImpl->GetFlag( 26 ); +} + +void SvtSearchOptions::SetIgnoreDiacritics_CTL( bool bVal ) +{ + pImpl->SetFlag( 26, bVal ); +} + +bool SvtSearchOptions::IsIgnoreKashida_CTL() const +{ + return pImpl->GetFlag( 27 ); +} + +void SvtSearchOptions::SetIgnoreKashida_CTL( bool bVal ) +{ + pImpl->SetFlag( 27, bVal ); +} + +bool SvtSearchOptions::IsSearchFormatted() const +{ + return pImpl->GetFlag( 28 ); +} + +void SvtSearchOptions::SetSearchFormatted( bool bVal ) +{ + pImpl->SetFlag( 28, bVal ); +} + +bool SvtSearchOptions::IsUseWildcard() const +{ + return pImpl->GetFlag( 29 ); +} + +void SvtSearchOptions::SetUseWildcard( bool bVal ) +{ + pImpl->SetSearchAlgorithm( 29, bVal ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/config/securityoptions.cxx b/unotools/source/config/securityoptions.cxx new file mode 100644 index 0000000000..1c2070ab95 --- /dev/null +++ b/unotools/source/config/securityoptions.cxx @@ -0,0 +1,410 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <unotools/securityoptions.hxx> +#include <unotools/configmgr.hxx> +#include <unotools/configitem.hxx> +#include <unotools/ucbhelper.hxx> +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/Sequence.hxx> + +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/container/XNameContainer.hpp> +#include <com/sun/star/container/XHierarchicalNameAccess.hpp> +#include <com/sun/star/util/XChangesBatch.hpp> +#include <comphelper/propertyvalue.hxx> +#include <comphelper/sequence.hxx> +#include <tools/urlobj.hxx> + +#include <unotools/pathoptions.hxx> +#include <officecfg/Office/Common.hxx> + +// namespaces + +using namespace ::com::sun::star::uno; + +constexpr OUString PROPERTYNAME_MACRO_TRUSTEDAUTHORS = u"TrustedAuthors"_ustr; +constexpr OUString PROPERTYNAME_TRUSTEDAUTHOR_SUBJECTNAME = u"SubjectName"_ustr; +constexpr OUString PROPERTYNAME_TRUSTEDAUTHOR_SERIALNUMBER = u"SerialNumber"_ustr; +constexpr OUString PROPERTYNAME_TRUSTEDAUTHOR_RAWDATA = u"RawData"_ustr; + + +namespace SvtSecurityOptions +{ + +bool IsReadOnly( EOption eOption ) +{ + bool bReadonly; + switch(eOption) + { + case SvtSecurityOptions::EOption::SecureUrls : + bReadonly = officecfg::Office::Common::Security::Scripting::SecureURL::isReadOnly(); + break; + case SvtSecurityOptions::EOption::DocWarnSaveOrSend: + bReadonly = officecfg::Office::Common::Security::Scripting::WarnSaveOrSendDoc::isReadOnly(); + break; + case SvtSecurityOptions::EOption::DocWarnSigning: + bReadonly = officecfg::Office::Common::Security::Scripting::WarnSignDoc::isReadOnly(); + break; + case SvtSecurityOptions::EOption::DocWarnPrint: + bReadonly = officecfg::Office::Common::Security::Scripting::WarnPrintDoc::isReadOnly(); + break; + case SvtSecurityOptions::EOption::DocWarnCreatePdf: + bReadonly = officecfg::Office::Common::Security::Scripting::WarnCreatePDF::isReadOnly(); + break; + case SvtSecurityOptions::EOption::DocWarnRemovePersonalInfo: + bReadonly = officecfg::Office::Common::Security::Scripting::RemovePersonalInfoOnSaving::isReadOnly(); + break; + case SvtSecurityOptions::EOption::DocWarnKeepRedlineInfo: + bReadonly = officecfg::Office::Common::Security::Scripting::KeepRedlineInfoOnSaving::isReadOnly(); + break; + case SvtSecurityOptions::EOption::DocWarnKeepDocUserInfo: + bReadonly = officecfg::Office::Common::Security::Scripting::KeepDocUserInfoOnSaving::isReadOnly(); + break; + case SvtSecurityOptions::EOption::DocWarnKeepNoteAuthorDateInfo: + bReadonly = officecfg::Office::Common::Security::Scripting::KeepNoteAuthorDateInfoOnSaving::isReadOnly(); + break; + case SvtSecurityOptions::EOption::DocWarnKeepDocVersionInfo: + bReadonly = officecfg::Office::Common::Security::Scripting::KeepDocVersionInfoOnSaving::isReadOnly(); + break; + case SvtSecurityOptions::EOption::DocWarnRecommendPassword: + bReadonly = officecfg::Office::Common::Security::Scripting::RecommendPasswordProtection::isReadOnly(); + break; + case SvtSecurityOptions::EOption::MacroSecLevel: + bReadonly = officecfg::Office::Common::Security::Scripting::MacroSecurityLevel::isReadOnly(); + break; + case SvtSecurityOptions::EOption::MacroTrustedAuthors: + // the officecfg does not expose isReadOnly for a ConfigurationSet, so we have to code this ourself + bReadonly = + comphelper::detail::ConfigurationWrapper::get().isReadOnly(u"/org.openoffice.Office.Common/Security/Scripting/TrustedAuthors"_ustr); + break; + case SvtSecurityOptions::EOption::CtrlClickHyperlink: + bReadonly = officecfg::Office::Common::Security::Scripting::HyperlinksWithCtrlClick::isReadOnly(); + break; + case SvtSecurityOptions::EOption::BlockUntrustedRefererLinks: + bReadonly = officecfg::Office::Common::Security::Scripting::BlockUntrustedRefererLinks::isReadOnly(); + break; + + default: + assert(false); + bReadonly = true; + } + + return bReadonly; +} + +std::vector< OUString > GetSecureURLs() +{ + if (utl::ConfigManager::IsFuzzing()) + return {}; + std::vector<OUString> aRet = comphelper::sequenceToContainer<std::vector<OUString>>( + officecfg::Office::Common::Security::Scripting::SecureURL::get()); + SvtPathOptions aOpt; + std::transform(aRet.begin(), aRet.end(), aRet.begin(), + [&aOpt](const OUString& rUrl) -> OUString { return aOpt.SubstituteVariable( rUrl ); }); + return aRet; +} + +void SetSecureURLs( std::vector< OUString >&& urlList ) +{ +// DBG_ASSERT(!officecfg::SecureURL::isReadOnly(), "SvtSecurityOptions_Impl::SetSecureURLs()\nYou tried to write on a readonly value!\n"); +// if (officecfg::SecureURL::isReadOnly()) +// return; + std::vector< OUString > lURLs( std::move(urlList) ); + SvtPathOptions aOpt; + std::transform(lURLs.begin(), lURLs.end(), lURLs.begin(), + [&aOpt](const OUString& rUrl) -> OUString { return aOpt.UseVariable( rUrl ); }); + std::shared_ptr<comphelper::ConfigurationChanges> xChanges = comphelper::ConfigurationChanges::create(); + officecfg::Office::Common::Security::Scripting::SecureURL::set(comphelper::containerToSequence(lURLs), xChanges); + xChanges->commit(); +} + +bool isSecureMacroUri( + OUString const & uri, OUString const & referer) +{ + switch (INetURLObject(uri).GetProtocol()) { + case INetProtocol::Macro: + if (uri.startsWithIgnoreAsciiCase("macro:///")) { + // Denotes an App-BASIC macro (see SfxMacroLoader::loadMacro), which + // is considered safe: + return true; + } + [[fallthrough]]; + case INetProtocol::Slot: + return referer.equalsIgnoreAsciiCase("private:user") + || isTrustedLocationUri(referer); + default: + return true; + } +} + +bool isUntrustedReferer(OUString const & referer) +{ + return IsOptionSet(EOption::BlockUntrustedRefererLinks) + && !(referer.isEmpty() || referer.startsWithIgnoreAsciiCase("private:") + || isTrustedLocationUri(referer)); +} + +bool isTrustedLocationUri(OUString const & uri) +{ + for (const auto & url : GetSecureURLs()) + { + if (utl::UCBContentHelper::IsSubPath(url, uri)) + { + return true; + } + } + return false; +} + +bool isTrustedLocationUriForUpdatingLinks(OUString const & uri) +{ + return GetMacroSecurityLevel() == 0 || uri.isEmpty() + || uri.startsWithIgnoreAsciiCase("private:") + || isTrustedLocationUri(uri); +} + +sal_Int32 GetMacroSecurityLevel() +{ + return utl::ConfigManager::IsFuzzing() ? 3 : officecfg::Office::Common::Security::Scripting::MacroSecurityLevel::get(); +} + +void SetMacroSecurityLevel( sal_Int32 _nLevel ) +{ + if (utl::ConfigManager::IsFuzzing() || officecfg::Office::Common::Security::Scripting::MacroSecurityLevel::isReadOnly()) + return; + + if( _nLevel > 3 || _nLevel < 0 ) + _nLevel = 3; + + std::shared_ptr<comphelper::ConfigurationChanges> xChanges = comphelper::ConfigurationChanges::create(); + officecfg::Office::Common::Security::Scripting::MacroSecurityLevel::set(_nLevel, xChanges); + xChanges->commit(); +} + +bool IsMacroDisabled() +{ + return utl::ConfigManager::IsFuzzing() || officecfg::Office::Common::Security::Scripting::DisableMacrosExecution::get(); +} + +std::vector< SvtSecurityOptions::Certificate > GetTrustedAuthors() +{ + Reference<css::container::XHierarchicalNameAccess> xHierarchyAccess = utl::ConfigManager::acquireTree(u"Office.Common/Security/Scripting"); + const Sequence< OUString > lAuthors = utl::ConfigItem::GetNodeNames( xHierarchyAccess, PROPERTYNAME_MACRO_TRUSTEDAUTHORS, utl::ConfigNameFormat::LocalPath ); + sal_Int32 c1 = lAuthors.getLength(); + if( !c1 ) + return {}; + + sal_Int32 c2 = c1 * 3; // 3 Properties inside Struct TrustedAuthor + Sequence< OUString > lAllAuthors( c2 ); + auto plAllAuthors = lAllAuthors.getArray(); + sal_Int32 i2 = 0; + OUString aSep( "/" ); + for( const auto& rAuthor : lAuthors ) + { + plAllAuthors[ i2 ] = PROPERTYNAME_MACRO_TRUSTEDAUTHORS + aSep + rAuthor + aSep + PROPERTYNAME_TRUSTEDAUTHOR_SUBJECTNAME; + ++i2; + plAllAuthors[ i2 ] = PROPERTYNAME_MACRO_TRUSTEDAUTHORS + aSep + rAuthor + aSep + PROPERTYNAME_TRUSTEDAUTHOR_SERIALNUMBER; + ++i2; + plAllAuthors[ i2 ] = PROPERTYNAME_MACRO_TRUSTEDAUTHORS + aSep + rAuthor + aSep + PROPERTYNAME_TRUSTEDAUTHOR_RAWDATA; + ++i2; + } + + Sequence< Any > lValues = utl::ConfigItem::GetProperties( xHierarchyAccess, lAllAuthors, /*bAllLocales*/false ); + if( lValues.getLength() != c2 ) + return {}; + + std::vector< SvtSecurityOptions::Certificate > aTrustedAuthors; + SvtSecurityOptions::Certificate aCert; + i2 = 0; + for( sal_Int32 i1 = 0; i1 < c1; ++i1 ) + { + lValues[ i2 ] >>= aCert.SubjectName; + ++i2; + lValues[ i2 ] >>= aCert.SerialNumber; + ++i2; + lValues[ i2 ] >>= aCert.RawData; + ++i2; + // Filter out TrustedAuthor entries with empty RawData, which + // would cause an unexpected std::bad_alloc in + // SecurityEnvironment_NssImpl::createCertificateFromAscii and + // have been observed in the wild (fdo#55019): + if( !aCert.RawData.isEmpty() ) + { + aTrustedAuthors.push_back( aCert ); + } + } + return aTrustedAuthors; +} + +void SetTrustedAuthors( const std::vector< Certificate >& rAuthors ) +{ +// DBG_ASSERT(!m_bROTrustedAuthors, "SvtSecurityOptions_Impl::SetTrustedAuthors()\nYou tried to write on a readonly value!\n"); +// if( m_bROTrustedAuthors ) +// return; + + Reference<css::container::XHierarchicalNameAccess> xHierarchyAccess = utl::ConfigManager::acquireTree(u"Office.Common/Security/Scripting"); + + // first, clear existing entries + { + Reference<css::container::XNameContainer> xCont; + xHierarchyAccess->getByHierarchicalName(PROPERTYNAME_MACRO_TRUSTEDAUTHORS) >>= xCont; + const Sequence< OUString > aNames = xCont->getElementNames(); + Reference<css::util::XChangesBatch> xBatch(xHierarchyAccess, UNO_QUERY); + for (const OUString& rName : aNames) + xCont->removeByName(rName); + xBatch->commitChanges(); + } + + sal_Int32 nCnt = rAuthors.size(); + for( sal_Int32 i = 0; i < nCnt; ++i ) + { + OUString aPrefix( + PROPERTYNAME_MACRO_TRUSTEDAUTHORS + "/a" + + OUString::number(i) + "/"); + Sequence< css::beans::PropertyValue > lPropertyValues{ + comphelper::makePropertyValue(aPrefix + PROPERTYNAME_TRUSTEDAUTHOR_SUBJECTNAME, + rAuthors[i].SubjectName), + comphelper::makePropertyValue(aPrefix + PROPERTYNAME_TRUSTEDAUTHOR_SERIALNUMBER, + rAuthors[i].SerialNumber), + comphelper::makePropertyValue(aPrefix + PROPERTYNAME_TRUSTEDAUTHOR_RAWDATA, + rAuthors[i].RawData) + }; + + utl::ConfigItem::SetSetProperties( xHierarchyAccess, PROPERTYNAME_MACRO_TRUSTEDAUTHORS, lPropertyValues ); + } +} + +bool IsOptionSet( EOption eOption ) +{ + if (utl::ConfigManager::IsFuzzing()) + return false; + bool bSet = false; + switch(eOption) + { + case SvtSecurityOptions::EOption::DocWarnSaveOrSend: + bSet = officecfg::Office::Common::Security::Scripting::WarnSaveOrSendDoc::get(); + break; + case SvtSecurityOptions::EOption::DocWarnSigning: + bSet = officecfg::Office::Common::Security::Scripting::WarnSignDoc::get(); + break; + case SvtSecurityOptions::EOption::DocWarnPrint: + bSet = officecfg::Office::Common::Security::Scripting::WarnPrintDoc::get(); + break; + case SvtSecurityOptions::EOption::DocWarnCreatePdf: + bSet = officecfg::Office::Common::Security::Scripting::WarnCreatePDF::get(); + break; + case SvtSecurityOptions::EOption::DocWarnRemovePersonalInfo: + bSet = officecfg::Office::Common::Security::Scripting::RemovePersonalInfoOnSaving::get(); + break; + case SvtSecurityOptions::EOption::DocWarnKeepRedlineInfo: + bSet = officecfg::Office::Common::Security::Scripting::KeepRedlineInfoOnSaving::get(); + break; + case SvtSecurityOptions::EOption::DocWarnKeepDocUserInfo: + bSet = officecfg::Office::Common::Security::Scripting::KeepDocUserInfoOnSaving::get(); + break; + case SvtSecurityOptions::EOption::DocWarnKeepNoteAuthorDateInfo: + bSet = officecfg::Office::Common::Security::Scripting::KeepNoteAuthorDateInfoOnSaving::get(); + break; + case SvtSecurityOptions::EOption::DocWarnKeepDocVersionInfo: + bSet = officecfg::Office::Common::Security::Scripting::KeepDocVersionInfoOnSaving::get(); + break; + case SvtSecurityOptions::EOption::DocWarnRecommendPassword: + bSet = officecfg::Office::Common::Security::Scripting::RecommendPasswordProtection::get(); + break; + case SvtSecurityOptions::EOption::CtrlClickHyperlink: + bSet = officecfg::Office::Common::Security::Scripting::HyperlinksWithCtrlClick::get(); + break; + case SvtSecurityOptions::EOption::BlockUntrustedRefererLinks: + bSet = officecfg::Office::Common::Security::Scripting::BlockUntrustedRefererLinks::get(); + break; + + default: + assert(false); + } + + return bSet; +} + +void SetOption( EOption eOption, bool bValue ) +{ + std::shared_ptr<comphelper::ConfigurationChanges> xChanges = comphelper::ConfigurationChanges::create(); + switch(eOption) + { + case SvtSecurityOptions::EOption::DocWarnSaveOrSend: + officecfg::Office::Common::Security::Scripting::WarnSaveOrSendDoc::set(bValue, xChanges); + break; + case SvtSecurityOptions::EOption::DocWarnSigning: + officecfg::Office::Common::Security::Scripting::WarnSignDoc::set(bValue, xChanges); + break; + case SvtSecurityOptions::EOption::DocWarnPrint: + officecfg::Office::Common::Security::Scripting::WarnPrintDoc::set(bValue, xChanges); + break; + case SvtSecurityOptions::EOption::DocWarnCreatePdf: + officecfg::Office::Common::Security::Scripting::WarnCreatePDF::set(bValue, xChanges); + break; + case SvtSecurityOptions::EOption::DocWarnRemovePersonalInfo: + officecfg::Office::Common::Security::Scripting::RemovePersonalInfoOnSaving::set(bValue, xChanges); + break; + case SvtSecurityOptions::EOption::DocWarnKeepRedlineInfo: + officecfg::Office::Common::Security::Scripting::KeepRedlineInfoOnSaving::set(bValue, xChanges); + break; + case SvtSecurityOptions::EOption::DocWarnKeepDocUserInfo: + officecfg::Office::Common::Security::Scripting::KeepDocUserInfoOnSaving::set(bValue, xChanges); + break; + case SvtSecurityOptions::EOption::DocWarnKeepNoteAuthorDateInfo: + officecfg::Office::Common::Security::Scripting::KeepNoteAuthorDateInfoOnSaving::set(bValue, xChanges); + break; + case SvtSecurityOptions::EOption::DocWarnKeepDocVersionInfo: + officecfg::Office::Common::Security::Scripting::KeepDocVersionInfoOnSaving::set(bValue, xChanges); + break; + case SvtSecurityOptions::EOption::DocWarnRecommendPassword: + officecfg::Office::Common::Security::Scripting::RecommendPasswordProtection::set(bValue, xChanges); + break; + case SvtSecurityOptions::EOption::CtrlClickHyperlink: + officecfg::Office::Common::Security::Scripting::HyperlinksWithCtrlClick::set(bValue, xChanges); + break; + case SvtSecurityOptions::EOption::BlockUntrustedRefererLinks: + officecfg::Office::Common::Security::Scripting::BlockUntrustedRefererLinks::set(bValue, xChanges); + break; + + default: + assert(false); + } + xChanges->commit(); +} + +} // namespace SvtSecurityOptions + + +// map personal info strings to 1, 2, ... to remove personal info +size_t SvtSecurityMapPersonalInfo::GetInfoID( const OUString sPersonalInfo ) +{ + auto aIter = aInfoIDs.find( sPersonalInfo ); + if ( aIter == aInfoIDs.end() ) + { + size_t nNewID = aInfoIDs.size() + 1; + aInfoIDs[sPersonalInfo] = nNewID; + return nNewID; + } + + return aIter->second; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/config/syslocaleoptions.cxx b/unotools/source/config/syslocaleoptions.cxx new file mode 100644 index 0000000000..70177c5c05 --- /dev/null +++ b/unotools/source/config/syslocaleoptions.cxx @@ -0,0 +1,718 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <com/sun/star/uno/Sequence.hxx> +#include <sal/log.hxx> +#include <i18nlangtag/mslangid.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <tools/debug.hxx> +#include <tools/link.hxx> +#include <unotools/syslocaleoptions.hxx> +#include <unotools/configmgr.hxx> +#include <unotools/configitem.hxx> +#include <com/sun/star/uno/Any.hxx> + +#include "itemholder1.hxx" + +#define CFG_READONLY_DEFAULT false + +using namespace osl; +using namespace utl; +using namespace com::sun::star::uno; +using namespace com::sun::star::lang; + +namespace +{ + std::weak_ptr<SvtSysLocaleOptions_Impl> g_pSysLocaleOptions; + Link<LinkParamNone*,void> g_CurrencyChangeLink; + +Mutex& GetMutex() +{ + // #i77768# Due to a static reference in the toolkit lib + // we need a mutex that lives longer than the svl library. + // Otherwise the dtor would use a destructed mutex!! + static Mutex* persistentMutex(new Mutex); + + return *persistentMutex; +} + +} + +class SvtSysLocaleOptions_Impl : public utl::ConfigItem +{ + LanguageTag m_aRealLocale; + LanguageTag m_aRealUILocale; + OUString m_aLocaleString; // en-US or de-DE or empty for SYSTEM + OUString m_aUILocaleString; // en-US or de-DE or empty for SYSTEM + OUString m_aCurrencyString; // USD-en-US or EUR-de-DE + OUString m_aDatePatternsString; // "Y-M-D;M-D" + bool m_bDecimalSeparator; //use decimal separator same as locale + bool m_bIgnoreLanguageChange; //OS language change doesn't affect LO document language + + bool m_bROLocale; + bool m_bROUILocale; + bool m_bROCurrency; + bool m_bRODatePatterns; + bool m_bRODecimalSeparator; + bool m_bROIgnoreLanguageChange; + + static Sequence<OUString> GetPropertyNames(); + void MakeRealLocale(); + void MakeRealUILocale(); + + virtual void ImplCommit() override; + +public: + SvtSysLocaleOptions_Impl(); + virtual ~SvtSysLocaleOptions_Impl() override; + + virtual void Notify( const css::uno::Sequence< OUString >& aPropertyNames ) override; + + const OUString& GetLocaleString() const + { return m_aLocaleString; } + void SetLocaleString( const OUString& rStr ); + + void SetUILocaleString( const OUString& rStr ); + + const OUString& GetCurrencyString() const + { return m_aCurrencyString; } + void SetCurrencyString( const OUString& rStr ); + + const OUString& GetDatePatternsString() const + { return m_aDatePatternsString; } + void SetDatePatternsString( const OUString& rStr ); + + bool IsDecimalSeparatorAsLocale() const { return m_bDecimalSeparator;} + void SetDecimalSeparatorAsLocale( bool bSet); + + bool IsIgnoreLanguageChange() const { return m_bIgnoreLanguageChange;} + void SetIgnoreLanguageChange( bool bSet); + + bool IsReadOnly( SvtSysLocaleOptions::EOption eOption ) const; + const LanguageTag& GetRealLocale() const { return m_aRealLocale; } + const LanguageTag& GetRealUILocale() const { return m_aRealUILocale; } +}; + +constexpr OUStringLiteral ROOTNODE_SYSLOCALE = u"Setup/L10N"; + +constexpr OUString PROPERTYNAME_LOCALE = u"ooSetupSystemLocale"_ustr; +constexpr OUString PROPERTYNAME_UILOCALE = u"ooLocale"_ustr; +constexpr OUString PROPERTYNAME_CURRENCY = u"ooSetupCurrency"_ustr; +constexpr OUString PROPERTYNAME_DECIMALSEPARATOR = u"DecimalSeparatorAsLocale"_ustr; +constexpr OUString PROPERTYNAME_DATEPATTERNS = u"DateAcceptancePatterns"_ustr; +constexpr OUString PROPERTYNAME_IGNORELANGCHANGE = u"IgnoreLanguageChange"_ustr; + +#define PROPERTYHANDLE_LOCALE 0 +#define PROPERTYHANDLE_UILOCALE 1 +#define PROPERTYHANDLE_CURRENCY 2 +#define PROPERTYHANDLE_DECIMALSEPARATOR 3 +#define PROPERTYHANDLE_DATEPATTERNS 4 +#define PROPERTYHANDLE_IGNORELANGCHANGE 5 + +Sequence< OUString > SvtSysLocaleOptions_Impl::GetPropertyNames() +{ + return Sequence< OUString > + { + PROPERTYNAME_LOCALE, + PROPERTYNAME_UILOCALE, + PROPERTYNAME_CURRENCY, + PROPERTYNAME_DECIMALSEPARATOR, + PROPERTYNAME_DATEPATTERNS, + PROPERTYNAME_IGNORELANGCHANGE + }; +} + +SvtSysLocaleOptions_Impl::SvtSysLocaleOptions_Impl() + : ConfigItem( ROOTNODE_SYSLOCALE ) + , m_aRealLocale( LANGUAGE_SYSTEM) + , m_aRealUILocale( LANGUAGE_SYSTEM) + , m_bDecimalSeparator( true ) + , m_bIgnoreLanguageChange( false) + , m_bROLocale(CFG_READONLY_DEFAULT) + , m_bROUILocale(CFG_READONLY_DEFAULT) + , m_bROCurrency(CFG_READONLY_DEFAULT) + , m_bRODatePatterns(CFG_READONLY_DEFAULT) + , m_bRODecimalSeparator(false) + , m_bROIgnoreLanguageChange(false) + +{ + const Sequence< OUString > aNames = GetPropertyNames(); + Sequence< Any > aValues = GetProperties( aNames ); + Sequence< sal_Bool > aROStates = GetReadOnlyStates( aNames ); + const Any* pValues = aValues.getConstArray(); + const sal_Bool* pROStates = aROStates.getConstArray(); + DBG_ASSERT( aValues.getLength() == aNames.getLength(), "GetProperties failed" ); + DBG_ASSERT( aROStates.getLength() == aNames.getLength(), "GetReadOnlyStates failed" ); + if ( aValues.getLength() == aNames.getLength() && aROStates.getLength() == aNames.getLength() ) + { + for ( sal_Int32 nProp = 0; nProp < aNames.getLength(); nProp++ ) + { + if ( pValues[nProp].hasValue() ) + { + switch ( nProp ) + { + case PROPERTYHANDLE_LOCALE : + { + OUString aStr; + if ( pValues[nProp] >>= aStr ) + m_aLocaleString = aStr; + else + { + SAL_WARN( "unotools.config", "Wrong property type!" ); + } + m_bROLocale = pROStates[nProp]; + } + break; + case PROPERTYHANDLE_UILOCALE : + { + OUString aStr; + if ( pValues[nProp] >>= aStr ) + m_aUILocaleString = aStr; + else + { + SAL_WARN( "unotools.config", "Wrong property type!" ); + } + m_bROUILocale = pROStates[nProp]; + } + break; + case PROPERTYHANDLE_CURRENCY : + { + OUString aStr; + if ( pValues[nProp] >>= aStr ) + m_aCurrencyString = aStr; + else + { + SAL_WARN( "unotools.config", "Wrong property type!" ); + } + m_bROCurrency = pROStates[nProp]; + } + break; + case PROPERTYHANDLE_DECIMALSEPARATOR: + { + bool bValue = false; + if ( pValues[nProp] >>= bValue ) + m_bDecimalSeparator = bValue; + else + { + SAL_WARN( "unotools.config", "Wrong property type!" ); + } + m_bRODecimalSeparator = pROStates[nProp]; + } + break; + case PROPERTYHANDLE_DATEPATTERNS : + { + OUString aStr; + if ( pValues[nProp] >>= aStr ) + m_aDatePatternsString = aStr; + else + { + SAL_WARN( "unotools.config", "Wrong property type!" ); + } + m_bRODatePatterns = pROStates[nProp]; + } + break; + case PROPERTYHANDLE_IGNORELANGCHANGE : + { + bool bValue = false; + if ( pValues[nProp] >>= bValue ) + m_bIgnoreLanguageChange = bValue; + else + { + SAL_WARN( "unotools.config", "Wrong property type!" ); + } + m_bROIgnoreLanguageChange = pROStates[nProp]; + } + break; + default: + SAL_WARN( "unotools.config", "Wrong property type!" ); + } + } + } + } + EnableNotification( aNames ); + + MakeRealLocale(); + MakeRealUILocale(); +} + +SvtSysLocaleOptions_Impl::~SvtSysLocaleOptions_Impl() +{ + assert(!IsModified()); // should have been committed +} + +void SvtSysLocaleOptions_Impl::MakeRealLocale() +{ + if (m_aLocaleString.isEmpty()) + { + LanguageType nLang = MsLangId::getConfiguredSystemLanguage(); + m_aRealLocale.reset( nLang).makeFallback(); + } + else + { + m_aRealLocale.reset( m_aLocaleString).makeFallback(); + } +} + +void SvtSysLocaleOptions_Impl::MakeRealUILocale() +{ + if (m_aUILocaleString.isEmpty()) + { + LanguageType nLang = MsLangId::getConfiguredSystemUILanguage(); + m_aRealUILocale.reset( nLang).makeFallback(); + } + else + { + m_aRealUILocale.reset( m_aUILocaleString).makeFallback(); + } +} + +bool SvtSysLocaleOptions_Impl::IsReadOnly( SvtSysLocaleOptions::EOption eOption ) const +{ + bool bReadOnly = CFG_READONLY_DEFAULT; + switch(eOption) + { + case SvtSysLocaleOptions::EOption::Locale : + { + bReadOnly = m_bROLocale; + break; + } + case SvtSysLocaleOptions::EOption::Currency : + { + bReadOnly = m_bROCurrency; + break; + } + case SvtSysLocaleOptions::EOption::DatePatterns : + { + bReadOnly = m_bRODatePatterns; + break; + } + case SvtSysLocaleOptions::EOption::DecimalSeparator: + { + bReadOnly = m_bRODecimalSeparator; + break; + } + case SvtSysLocaleOptions::EOption::IgnoreLanguageChange: + { + bReadOnly = m_bROIgnoreLanguageChange; + break; + } + } + return bReadOnly; +} + +void SvtSysLocaleOptions_Impl::ImplCommit() +{ + const Sequence< OUString > aOrgNames = GetPropertyNames(); + sal_Int32 nOrgCount = aOrgNames.getLength(); + + Sequence< OUString > aNames( nOrgCount ); + Sequence< Any > aValues( nOrgCount ); + + OUString* pNames = aNames.getArray(); + Any* pValues = aValues.getArray(); + sal_Int32 nRealCount = 0; + + for ( sal_Int32 nProp = 0; nProp < nOrgCount; nProp++ ) + { + switch ( nProp ) + { + case PROPERTYHANDLE_LOCALE : + { + if (!m_bROLocale) + { + pNames[nRealCount] = aOrgNames[nProp]; + pValues[nRealCount] <<= m_aLocaleString; + ++nRealCount; + } + } + break; + case PROPERTYHANDLE_UILOCALE : + { + if (!m_bROUILocale) + { + pNames[nRealCount] = aOrgNames[nProp]; + pValues[nRealCount] <<= m_aUILocaleString; + ++nRealCount; + } + } + break; + case PROPERTYHANDLE_CURRENCY : + { + if (!m_bROCurrency) + { + pNames[nRealCount] = aOrgNames[nProp]; + pValues[nRealCount] <<= m_aCurrencyString; + ++nRealCount; + } + } + break; + case PROPERTYHANDLE_DECIMALSEPARATOR: + if( !m_bRODecimalSeparator ) + { + pNames[nRealCount] = aOrgNames[nProp]; + pValues[nRealCount] <<= m_bDecimalSeparator; + ++nRealCount; + } + break; + case PROPERTYHANDLE_DATEPATTERNS : + if (!m_bRODatePatterns) + { + pNames[nRealCount] = aOrgNames[nProp]; + pValues[nRealCount] <<= m_aDatePatternsString; + ++nRealCount; + } + break; + case PROPERTYHANDLE_IGNORELANGCHANGE : + if (!m_bROIgnoreLanguageChange) + { + pNames[nRealCount] = aOrgNames[nProp]; + pValues[nRealCount] <<= m_bIgnoreLanguageChange; + ++nRealCount; + } + break; + default: + SAL_WARN( "unotools.config", "invalid index to save a path" ); + } + } + aNames.realloc(nRealCount); + aValues.realloc(nRealCount); + PutProperties( aNames, aValues ); +} + +void SvtSysLocaleOptions_Impl::SetLocaleString( const OUString& rStr ) +{ + ConfigurationHints nHint = ConfigurationHints::Locale; + { + MutexGuard aGuard( GetMutex() ); + if (m_bROLocale || rStr == m_aLocaleString ) + { + return; + } + m_aLocaleString = rStr; + MakeRealLocale(); + LanguageTag::setConfiguredSystemLanguage( m_aRealLocale.getLanguageType() ); + SetModified(); + if ( m_aCurrencyString.isEmpty() ) + nHint |= ConfigurationHints::Currency; + } + NotifyListeners( nHint ); +} + +void SvtSysLocaleOptions_Impl::SetUILocaleString( const OUString& rStr ) +{ + { + MutexGuard aGuard( GetMutex() ); + if (m_bROUILocale || rStr == m_aUILocaleString ) + { + return; + } + m_aUILocaleString = rStr; + + // as we can't switch UILocale at runtime, we only store changes in the configuration + MakeRealUILocale(); + SetModified(); + } + NotifyListeners( ConfigurationHints::UiLocale ); +} + +void SvtSysLocaleOptions_Impl::SetCurrencyString( const OUString& rStr ) +{ + { + MutexGuard aGuard( GetMutex() ); + if (m_bROCurrency || rStr == m_aCurrencyString ) + { + return; + } + m_aCurrencyString = rStr; + SetModified(); + } + NotifyListeners( ConfigurationHints::Currency ); +} + +void SvtSysLocaleOptions_Impl::SetDatePatternsString( const OUString& rStr ) +{ + { + MutexGuard aGuard( GetMutex() ); + if (m_bRODatePatterns || rStr == m_aDatePatternsString ) + { + return; + } + m_aDatePatternsString = rStr; + SetModified(); + } + NotifyListeners( ConfigurationHints::DatePatterns ); +} + +void SvtSysLocaleOptions_Impl::SetDecimalSeparatorAsLocale( bool bSet) +{ + { + MutexGuard aGuard( GetMutex() ); + if(bSet == m_bDecimalSeparator) + { + return; + } + m_bDecimalSeparator = bSet; + SetModified(); + } + NotifyListeners( ConfigurationHints::DecSep ); +} + +void SvtSysLocaleOptions_Impl::SetIgnoreLanguageChange( bool bSet) +{ + { + MutexGuard aGuard( GetMutex() ); + if(bSet == m_bIgnoreLanguageChange) + { + return; + } + m_bIgnoreLanguageChange = bSet; + SetModified(); + } + NotifyListeners( ConfigurationHints::IgnoreLang ); +} + +void SvtSysLocaleOptions_Impl::Notify( const Sequence< OUString >& seqPropertyNames ) +{ + ConfigurationHints nHint = ConfigurationHints::NONE; + Sequence< Any > seqValues = GetProperties( seqPropertyNames ); + Sequence< sal_Bool > seqROStates = GetReadOnlyStates( seqPropertyNames ); + sal_Int32 nCount = seqPropertyNames.getLength(); + for( sal_Int32 nProp = 0; nProp < nCount; ++nProp ) + { + if( seqPropertyNames[nProp] == PROPERTYNAME_LOCALE ) + { + DBG_ASSERT( seqValues[nProp].getValueTypeClass() == TypeClass_STRING, "Locale property type" ); + seqValues[nProp] >>= m_aLocaleString; + m_bROLocale = seqROStates[nProp]; + nHint |= ConfigurationHints::Locale; + if ( m_aCurrencyString.isEmpty() ) + nHint |= ConfigurationHints::Currency; + MakeRealLocale(); + } + if( seqPropertyNames[nProp] == PROPERTYNAME_UILOCALE ) + { + DBG_ASSERT( seqValues[nProp].getValueTypeClass() == TypeClass_STRING, "Locale property type" ); + seqValues[nProp] >>= m_aUILocaleString; + m_bROUILocale = seqROStates[nProp]; + nHint |= ConfigurationHints::UiLocale; + MakeRealUILocale(); + } + else if( seqPropertyNames[nProp] == PROPERTYNAME_CURRENCY ) + { + DBG_ASSERT( seqValues[nProp].getValueTypeClass() == TypeClass_STRING, "Currency property type" ); + seqValues[nProp] >>= m_aCurrencyString; + m_bROCurrency = seqROStates[nProp]; + nHint |= ConfigurationHints::Currency; + } + else if( seqPropertyNames[nProp] == PROPERTYNAME_DECIMALSEPARATOR ) + { + seqValues[nProp] >>= m_bDecimalSeparator; + m_bRODecimalSeparator = seqROStates[nProp]; + } + else if( seqPropertyNames[nProp] == PROPERTYNAME_IGNORELANGCHANGE ) + { + seqValues[nProp] >>= m_bIgnoreLanguageChange; + m_bROIgnoreLanguageChange = seqROStates[nProp]; + } + else if( seqPropertyNames[nProp] == PROPERTYNAME_DATEPATTERNS ) + { + DBG_ASSERT( seqValues[nProp].getValueTypeClass() == TypeClass_STRING, "DatePatterns property type" ); + seqValues[nProp] >>= m_aDatePatternsString; + m_bRODatePatterns = seqROStates[nProp]; + nHint |= ConfigurationHints::DatePatterns; + } + } + if ( nHint != ConfigurationHints::NONE ) + NotifyListeners( nHint ); +} + +SvtSysLocaleOptions::SvtSysLocaleOptions() +{ + MutexGuard aGuard( GetMutex() ); + pImpl = g_pSysLocaleOptions.lock(); + if ( !pImpl ) + { + pImpl = std::make_shared<SvtSysLocaleOptions_Impl>(); + g_pSysLocaleOptions = pImpl; + if (!utl::ConfigManager::IsFuzzing()) + ItemHolder1::holdConfigItem(EItem::SysLocaleOptions); + } + pImpl->AddListener(this); +} + +SvtSysLocaleOptions::~SvtSysLocaleOptions() +{ + MutexGuard aGuard( GetMutex() ); + pImpl->RemoveListener(this); + pImpl.reset(); +} + +bool SvtSysLocaleOptions::IsModified() const +{ + MutexGuard aGuard( GetMutex() ); + return pImpl->IsModified(); +} + +void SvtSysLocaleOptions::Commit() +{ + MutexGuard aGuard( GetMutex() ); + pImpl->Commit(); +} + +void SvtSysLocaleOptions::BlockBroadcasts( bool bBlock ) +{ + MutexGuard aGuard( GetMutex() ); + pImpl->BlockBroadcasts( bBlock ); +} + +void SvtSysLocaleOptions::SetLocaleConfigString( const OUString& rStr ) +{ + pImpl->SetLocaleString( rStr ); +} + +void SvtSysLocaleOptions::SetUILocaleConfigString( const OUString& rStr ) +{ + pImpl->SetUILocaleString( rStr ); +} + +const OUString& SvtSysLocaleOptions::GetCurrencyConfigString() const +{ + MutexGuard aGuard( GetMutex() ); + return pImpl->GetCurrencyString(); +} + +void SvtSysLocaleOptions::SetCurrencyConfigString( const OUString& rStr ) +{ + pImpl->SetCurrencyString( rStr ); +} + +const OUString& SvtSysLocaleOptions::GetDatePatternsConfigString() const +{ + MutexGuard aGuard( GetMutex() ); + return pImpl->GetDatePatternsString(); +} + +void SvtSysLocaleOptions::SetDatePatternsConfigString( const OUString& rStr ) +{ + pImpl->SetDatePatternsString( rStr ); +} + +bool SvtSysLocaleOptions::IsDecimalSeparatorAsLocale() const +{ + MutexGuard aGuard( GetMutex() ); + return pImpl->IsDecimalSeparatorAsLocale(); +} + +void SvtSysLocaleOptions::SetDecimalSeparatorAsLocale( bool bSet) +{ + pImpl->SetDecimalSeparatorAsLocale(bSet); +} + +bool SvtSysLocaleOptions::IsIgnoreLanguageChange() const +{ + MutexGuard aGuard( GetMutex() ); + return pImpl->IsIgnoreLanguageChange(); +} + +void SvtSysLocaleOptions::SetIgnoreLanguageChange( bool bSet) +{ + pImpl->SetIgnoreLanguageChange(bSet); +} + +bool SvtSysLocaleOptions::IsReadOnly( EOption eOption ) const +{ + MutexGuard aGuard( GetMutex() ); + return pImpl->IsReadOnly( eOption ); +} + +// static +void SvtSysLocaleOptions::GetCurrencyAbbrevAndLanguage( OUString& rAbbrev, + LanguageType& eLang, + const OUString& rConfigString ) +{ + sal_Int32 nDelim = rConfigString.indexOf( '-' ); + if ( nDelim >= 0 ) + { + rAbbrev = rConfigString.copy( 0, nDelim ); + OUString aIsoStr( rConfigString.copy( nDelim+1 ) ); + eLang = LanguageTag::convertToLanguageTypeWithFallback( aIsoStr ); + } + else + { + rAbbrev = rConfigString; + eLang = (rAbbrev.isEmpty() ? LANGUAGE_SYSTEM : LANGUAGE_NONE); + } +} + +// static +OUString SvtSysLocaleOptions::CreateCurrencyConfigString( + const OUString& rAbbrev, LanguageType eLang ) +{ + OUString aIsoStr( LanguageTag::convertToBcp47( eLang ) ); + if ( !aIsoStr.isEmpty() ) + { + return rAbbrev + "-" + aIsoStr; + } + else + return rAbbrev; +} + +// static +void SvtSysLocaleOptions::SetCurrencyChangeLink( const Link<LinkParamNone*,void>& rLink ) +{ + MutexGuard aGuard( GetMutex() ); + DBG_ASSERT( !g_CurrencyChangeLink.IsSet(), "SvtSysLocaleOptions::SetCurrencyChangeLink: already set" ); + g_CurrencyChangeLink = rLink; +} + +// static +const Link<LinkParamNone*,void>& SvtSysLocaleOptions::GetCurrencyChangeLink() +{ + MutexGuard aGuard( GetMutex() ); + return g_CurrencyChangeLink; +} + +void SvtSysLocaleOptions::ConfigurationChanged( utl::ConfigurationBroadcaster* p, ConfigurationHints nHint ) +{ + if ( nHint & ConfigurationHints::Currency ) + { + const Link<LinkParamNone*,void>& rLink = GetCurrencyChangeLink(); + rLink.Call( nullptr ); + } + + ::utl::detail::Options::ConfigurationChanged( p, nHint ); +} + +LanguageTag SvtSysLocaleOptions::GetLanguageTag() const +{ + MutexGuard aGuard( GetMutex() ); + return LanguageTag( pImpl->GetLocaleString() ); +} + +const LanguageTag & SvtSysLocaleOptions::GetRealLanguageTag() const +{ + return pImpl->GetRealLocale(); +} + +const LanguageTag & SvtSysLocaleOptions::GetRealUILanguageTag() const +{ + return pImpl->GetRealUILocale(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/config/useroptions.cxx b/unotools/source/config/useroptions.cxx new file mode 100644 index 0000000000..1e181e8813 --- /dev/null +++ b/unotools/source/config/useroptions.cxx @@ -0,0 +1,346 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <unotools/useroptions.hxx> +#include <unotools/syslocale.hxx> +#include <com/sun/star/uno/Any.hxx> +#include "itemholder1.hxx" + +#include <cppuhelper/implbase.hxx> +#include <com/sun/star/beans/Property.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/util/XChangesListener.hpp> +#include <com/sun/star/util/XChangesNotifier.hpp> +#include <com/sun/star/util/ChangesEvent.hpp> +#include <comphelper/configurationhelper.hxx> +#include <comphelper/processfactory.hxx> +#include <i18nlangtag/mslangid.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <o3tl/enumarray.hxx> +#include <o3tl/string_view.hxx> +#include <comphelper/diagnose_ex.hxx> + +using namespace utl; +using namespace com::sun::star; + +// vOptionNames[] -- names of the user option entries +// The order must correspond to the enum class UserOptToken in useroptions.hxx. +static o3tl::enumarray<UserOptToken, char const *> vOptionNames = { + "l", // UserOptToken::City + "o", // UserOptToken::Company + "c", // UserOptToken::Country + "mail", // UserOptToken::Email + "facsimiletelephonenumber", // UserOptToken::Fax + "givenname", // UserOptToken::FirstName + "sn", // UserOptToken::LastName + "position", // UserOptToken::Position + "st", // UserOptToken::State + "street", // UserOptToken::Street + "homephone", // UserOptToken::TelephoneHome + "telephonenumber", // UserOptToken::TelephoneWork + "title", // UserOptToken::Title + "initials", // UserOptToken::ID + "postalcode", // UserOptToken::Zip + "fathersname", // UserOptToken::FathersName + "apartment", // UserOptToken::Apartment + "signingkey", // UserOptToken::SigningKey + "encryptionkey", // UserOptToken::EncryptionKey + "encrypttoself" // UserOptToken::EncryptToSelf +}; + +std::weak_ptr<SvtUserOptions::Impl> SvtUserOptions::xSharedImpl; + +class SvtUserOptions::ChangeListener : public cppu::WeakImplHelper<util::XChangesListener> +{ +public: + explicit ChangeListener (Impl& rParent): m_rParent(rParent) { } + + // XChangesListener + virtual void SAL_CALL changesOccurred (util::ChangesEvent const& Event) override; + // XEventListener + virtual void SAL_CALL disposing (lang::EventObject const& Source) override; + +private: + Impl& m_rParent; +}; + +class SvtUserOptions::Impl : public utl::ConfigurationBroadcaster +{ +public: + Impl (); + + OUString GetFullName () const; + + bool IsTokenReadonly (UserOptToken nToken) const; + OUString GetToken (UserOptToken nToken) const; + void SetToken (UserOptToken nToken, OUString const& rNewToken); + bool GetBoolValue (UserOptToken nToken) const; + void SetBoolValue (UserOptToken nToken, bool bNewValue); + void Notify (); + +private: + uno::Reference<util::XChangesListener> m_xChangeListener; + uno::Reference<container::XNameAccess> m_xCfg; + uno::Reference<beans::XPropertySet> m_xData; + + template < typename ValueType > + ValueType GetValue_Impl( UserOptToken nToken ) const; + template < typename ValueType > + void SetValue_Impl( UserOptToken nToken, ValueType const& rNewValue ); +}; + +void SvtUserOptions::ChangeListener::changesOccurred (util::ChangesEvent const& rEvent) +{ + if (rEvent.Changes.hasElements()) + m_rParent.Notify(); +} + +void SvtUserOptions::ChangeListener::disposing (lang::EventObject const& rSource) +{ + try + { + uno::Reference<util::XChangesNotifier> xChgNot(rSource.Source, uno::UNO_QUERY_THROW); + xChgNot->removeChangesListener(this); + } + catch (uno::Exception&) + { + } +} + +SvtUserOptions::Impl::Impl() : + m_xChangeListener( new ChangeListener(*this) ) +{ + try + { + m_xCfg.set( + comphelper::ConfigurationHelper::openConfig( + comphelper::getProcessComponentContext(), + "org.openoffice.UserProfile/Data", + comphelper::EConfigurationModes::Standard + ), + uno::UNO_QUERY + ); + + m_xData.set(m_xCfg, uno::UNO_QUERY); + uno::Reference<util::XChangesNotifier> xChgNot(m_xCfg, uno::UNO_QUERY); + try + { + xChgNot->addChangesListener(m_xChangeListener); + } + catch (uno::RuntimeException&) + { + } + } + catch (uno::Exception const&) + { + DBG_UNHANDLED_EXCEPTION("unotools.config"); + m_xCfg.clear(); + } +} + +template < typename ValueType > +ValueType SvtUserOptions::Impl::GetValue_Impl (UserOptToken nToken) const +{ + ValueType sToken = ValueType(); + try + { + if (m_xData.is()) + m_xData->getPropertyValue(OUString::createFromAscii(vOptionNames[nToken])) >>= sToken; + } + catch (uno::Exception const&) + { + DBG_UNHANDLED_EXCEPTION("unotools.config"); + } + return sToken; +} + +template < typename ValueType > +void SvtUserOptions::Impl::SetValue_Impl (UserOptToken nToken, ValueType const& sToken) +{ + try + { + if (m_xData.is()) + m_xData->setPropertyValue(OUString::createFromAscii(vOptionNames[nToken]), uno::Any(sToken)); + comphelper::ConfigurationHelper::flush(m_xCfg); + } + catch (uno::Exception const&) + { + DBG_UNHANDLED_EXCEPTION("unotools.config"); + } +} + +OUString SvtUserOptions::Impl::GetToken (UserOptToken nToken) const +{ + return GetValue_Impl<OUString>( nToken ); +} + +void SvtUserOptions::Impl::SetToken (UserOptToken nToken, OUString const& sToken) +{ + SetValue_Impl<OUString>( nToken, sToken ); +} + +bool SvtUserOptions::Impl::GetBoolValue (UserOptToken nToken) const +{ + return GetValue_Impl<bool>( nToken ); +} + +void SvtUserOptions::Impl::SetBoolValue (UserOptToken nToken, bool bNewValue) +{ + SetValue_Impl<bool>( nToken, bNewValue ); +} + +OUString SvtUserOptions::Impl::GetFullName () const +{ + OUString sFullName; + LanguageType const eLang = SvtSysLocale().GetUILanguageTag().getLanguageType(); + if (eLang == LANGUAGE_RUSSIAN) + { + sFullName = GetToken(UserOptToken::FirstName).trim(); + if (!sFullName.isEmpty()) + sFullName += " "; + sFullName += o3tl::trim(GetToken(UserOptToken::FathersName)); + if (!sFullName.isEmpty()) + sFullName += " "; + sFullName += o3tl::trim(GetToken(UserOptToken::LastName)); + } + else + { + if (MsLangId::isFamilyNameFirst(eLang)) + { + sFullName = GetToken(UserOptToken::LastName).trim(); + if (!sFullName.isEmpty()) + sFullName += " "; + sFullName += o3tl::trim(GetToken(UserOptToken::FirstName)); + } + else + { + sFullName = GetToken(UserOptToken::FirstName).trim(); + if (!sFullName.isEmpty()) + sFullName += " "; + sFullName += o3tl::trim(GetToken(UserOptToken::LastName)); + } + } + sFullName = sFullName.trim(); + + return sFullName; +} + +void SvtUserOptions::Impl::Notify () +{ + NotifyListeners(ConfigurationHints::NONE); +} + +bool SvtUserOptions::Impl::IsTokenReadonly (UserOptToken nToken) const +{ + uno::Reference<beans::XPropertySet> xData(m_xCfg, uno::UNO_QUERY); + uno::Reference<beans::XPropertySetInfo> xInfo = xData->getPropertySetInfo(); + beans::Property aProp = xInfo->getPropertyByName(OUString::createFromAscii(vOptionNames[nToken])); + return ((aProp.Attributes & beans::PropertyAttribute::READONLY) == + beans::PropertyAttribute::READONLY); +} + +static std::recursive_mutex& GetInitMutex() +{ + static std::recursive_mutex gMutex; + return gMutex; +} + + +SvtUserOptions::SvtUserOptions () +{ + // Global access, must be guarded (multithreading) + std::unique_lock aGuard(GetInitMutex()); + + xImpl = xSharedImpl.lock(); + if (!xImpl) + { + xImpl = std::make_shared<Impl>(); + xSharedImpl = xImpl; + aGuard.unlock(); // because holdConfigItem will call this constructor + ItemHolder1::holdConfigItem(EItem::UserOptions); + } + xImpl->AddListener(this); +} + +SvtUserOptions::~SvtUserOptions() +{ + // Global access, must be guarded (multithreading) + std::unique_lock aGuard( GetInitMutex() ); + xImpl->RemoveListener(this); +} + +OUString SvtUserOptions::GetCompany () const { return GetToken(UserOptToken::Company); } +OUString SvtUserOptions::GetFirstName () const { return GetToken(UserOptToken::FirstName); } +OUString SvtUserOptions::GetLastName () const { return GetToken(UserOptToken::LastName); } +OUString SvtUserOptions::GetID () const { return GetToken(UserOptToken::ID); } +OUString SvtUserOptions::GetStreet () const { return GetToken(UserOptToken::Street); } +OUString SvtUserOptions::GetCity () const { return GetToken(UserOptToken::City); } +OUString SvtUserOptions::GetState () const { return GetToken(UserOptToken::State); } +OUString SvtUserOptions::GetZip () const { return GetToken(UserOptToken::Zip); } +OUString SvtUserOptions::GetCountry () const { return GetToken(UserOptToken::Country); } +OUString SvtUserOptions::GetPosition () const { return GetToken(UserOptToken::Position); } +OUString SvtUserOptions::GetTitle () const { return GetToken(UserOptToken::Title); } +OUString SvtUserOptions::GetTelephoneHome () const { return GetToken(UserOptToken::TelephoneHome); } +OUString SvtUserOptions::GetTelephoneWork () const { return GetToken(UserOptToken::TelephoneWork); } +OUString SvtUserOptions::GetFax () const { return GetToken(UserOptToken::Fax); } +OUString SvtUserOptions::GetEmail () const { return GetToken(UserOptToken::Email); } +OUString SvtUserOptions::GetSigningKey () const { return GetToken(UserOptToken::SigningKey); } +OUString SvtUserOptions::GetEncryptionKey () const { return GetToken(UserOptToken::EncryptionKey); } + +bool SvtUserOptions::IsTokenReadonly (UserOptToken nToken) const +{ + std::unique_lock aGuard(GetInitMutex()); + return xImpl->IsTokenReadonly(nToken); +} + +OUString SvtUserOptions::GetToken (UserOptToken nToken) const +{ + std::unique_lock aGuard(GetInitMutex()); + return xImpl->GetToken(nToken); +} + +void SvtUserOptions::SetToken (UserOptToken nToken, OUString const& rNewToken) +{ + std::unique_lock aGuard(GetInitMutex()); + xImpl->SetToken(nToken, rNewToken); +} + +void SvtUserOptions::SetBoolValue (UserOptToken nToken, bool bNewValue) +{ + std::unique_lock aGuard(GetInitMutex()); + xImpl->SetBoolValue(nToken, bNewValue); +} + +bool SvtUserOptions::GetEncryptToSelf() const +{ + std::unique_lock aGuard(GetInitMutex()); + return xImpl->GetBoolValue(UserOptToken::EncryptToSelf); +} + +OUString SvtUserOptions::GetFullName () const +{ + std::unique_lock aGuard(GetInitMutex()); + return xImpl->GetFullName(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/config/viewoptions.cxx b/unotools/source/config/viewoptions.cxx new file mode 100644 index 0000000000..382af97b2c --- /dev/null +++ b/unotools/source/config/viewoptions.cxx @@ -0,0 +1,450 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <unotools/viewoptions.hxx> +#include <com/sun/star/uno/Any.hxx> + +#include <com/sun/star/beans/NamedValue.hpp> +#include <com/sun/star/container/XNameContainer.hpp> +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <unotools/configmgr.hxx> +#include <comphelper/configurationhelper.hxx> +#include <comphelper/processfactory.hxx> +#include <utility> +#include <comphelper/diagnose_ex.hxx> + +constexpr OUStringLiteral PACKAGE_VIEWS = u"org.openoffice.Office.Views"; +constexpr OUString PROPERTY_WINDOWSTATE = u"WindowState"_ustr; +constexpr OUString PROPERTY_PAGEID = u"PageID"_ustr; +constexpr OUString PROPERTY_VISIBLE = u"Visible"_ustr; +constexpr OUString PROPERTY_USERDATA = u"UserData"_ustr; + + +SvtViewOptions::SvtViewOptions( EViewType eType, OUString sViewName ) + : m_eViewType ( eType ) + , m_sViewName (std::move( sViewName )) +{ + (void)m_eViewType; // so the release build does not complain, since we only use it in assert + // we must know, which view type we must support + switch( eType ) + { + case EViewType::Dialog: m_sListName = "Dialogs"; break; + case EViewType::TabDialog: m_sListName = "TabDialogs"; break; + case EViewType::TabPage: m_sListName = "TabPages"; break; + case EViewType::Window: m_sListName = "Windows"; break; + default: assert(false); + } + if (utl::ConfigManager::IsFuzzing()) + return; + + try + { + m_xRoot.set( ::comphelper::ConfigurationHelper::openConfig( + ::comphelper::getProcessComponentContext(), + PACKAGE_VIEWS, + ::comphelper::EConfigurationModes::Standard), + css::uno::UNO_QUERY); + if (m_xRoot.is()) + m_xRoot->getByName(m_sListName) >>= m_xSet; + } + catch(const css::uno::Exception&) + { + TOOLS_WARN_EXCEPTION("unotools", "Unexpected exception"); + m_xRoot.clear(); + m_xSet.clear(); + } +} + +// public method + +/*-************************************************************************************************************ + @short checks for already existing entries + @descr If user don't know, if an entry already exist - he can get this information by calling this method. + + @seealso member m_aList + + @param "sName", name of entry to check exist state + @return true , if item exist + false, otherwise +*//*-*************************************************************************************************************/ +bool SvtViewOptions::Exists() const +{ + bool bExists = false; + + try + { + if (m_xSet.is()) + bExists = m_xSet->hasByName(m_sViewName); + } + catch(const css::uno::Exception&) + { + TOOLS_WARN_EXCEPTION("unotools", "Unexpected exception"); + bExists = false; + } + + return bExists; +} + +// public method + +/*-************************************************************************************************************ + @short delete entry + @descr Use it to delete set entry by given name. + + @seealso member m_aList + + @param "sName", name of entry to delete it +*//*-*************************************************************************************************************/ +void SvtViewOptions::Delete() +{ + try + { + css::uno::Reference< css::container::XNameContainer > xSet(m_xSet, css::uno::UNO_QUERY_THROW); + xSet->removeByName(m_sViewName); + ::comphelper::ConfigurationHelper::flush(m_xRoot); + } + catch(const css::container::NoSuchElementException&) + { } + catch(const css::uno::Exception&) + { + TOOLS_WARN_EXCEPTION("unotools", "Unexpected exception"); + } +} + +// public method + +/*-************************************************************************************************************ + @short read/write access to cache view items and her properties + @descr Follow methods support read/write access to all cache view items. + + @seealso member m_sList +*//*-*************************************************************************************************************/ +OUString SvtViewOptions::GetWindowState() const +{ + OUString sWindowState; + try + { + css::uno::Reference< css::beans::XPropertySet > xNode( + impl_getSetNode(m_sViewName, false), + css::uno::UNO_QUERY); + if (xNode.is()) + xNode->getPropertyValue(PROPERTY_WINDOWSTATE) >>= sWindowState; + } + catch(const css::uno::Exception&) + { + TOOLS_WARN_EXCEPTION("unotools", "Unexpected exception"); + sWindowState.clear(); + } + + return sWindowState; +} + + +// public method + +/*-************************************************************************************************************ + @short ctor + @descr We use it to open right configuration file and let configuration objects fill her caches. + Then we read all existing entries from right list and cached it inside our object too. + Normally we should enable notifications for changes on these values too ... but these feature + isn't full implemented in the moment. + + @seealso baseclass ::utl::ConfigItem + @seealso method Notify() +*//*-*************************************************************************************************************/ +void SvtViewOptions::SetWindowState( const OUString& sState ) +{ + try + { + css::uno::Reference< css::beans::XPropertySet > xNode( + impl_getSetNode(m_sViewName, true), + css::uno::UNO_QUERY_THROW); + xNode->setPropertyValue(PROPERTY_WINDOWSTATE, css::uno::Any(sState)); + ::comphelper::ConfigurationHelper::flush(m_xRoot); + } + catch(const css::uno::Exception&) + { + TOOLS_WARN_EXCEPTION("unotools", "Unexpected exception"); + } +} + +// public method + +OUString SvtViewOptions::GetPageID() const +{ + // Safe impossible cases. + // These call isn't allowed for dialogs, tab-pages or windows! + assert( m_eViewType == EViewType::TabDialog && "SvtViewOptions::GetPageID()\nCall not allowed for Dialogs, TabPages or Windows! I do nothing!" ); + + OUString sID; + try + { + css::uno::Reference< css::beans::XPropertySet > xNode( + impl_getSetNode(m_sViewName, false), + css::uno::UNO_QUERY); + if (xNode.is()) + xNode->getPropertyValue(PROPERTY_PAGEID) >>= sID; + } + catch(const css::uno::Exception&) + { + TOOLS_WARN_EXCEPTION("unotools", "Unexpected exception"); + } + + return sID; +} + + +// public method + +void SvtViewOptions::SetPageID(const OUString& rID) +{ + // Safe impossible cases. + // These call isn't allowed for dialogs, tab-pages or windows! + assert( m_eViewType == EViewType::TabDialog && "SvtViewOptions::SetPageID()\nCall not allowed for Dialogs, TabPages or Windows! I do nothing!" ); + + try + { + css::uno::Reference< css::beans::XPropertySet > xNode( + impl_getSetNode(m_sViewName, true), + css::uno::UNO_QUERY_THROW); + xNode->setPropertyValue(PROPERTY_PAGEID, css::uno::Any(rID)); + ::comphelper::ConfigurationHelper::flush(m_xRoot); + } + catch(const css::uno::Exception&) + { + TOOLS_WARN_EXCEPTION("unotools", "Unexpected exception"); + } +} + + +// public method + +bool SvtViewOptions::IsVisible() const +{ + // Safe impossible cases. + // These call isn't allowed for dialogs, tab-dialogs or tab-pages! + assert( m_eViewType == EViewType::Window && "SvtViewOptions::IsVisible()\nCall not allowed for Dialogs, TabDialogs or TabPages! I do nothing!" ); + + return GetVisible() == STATE_TRUE; +} + +SvtViewOptions::State SvtViewOptions::GetVisible() const +{ + State eState = STATE_NONE; + try + { + css::uno::Reference< css::beans::XPropertySet > xNode( + impl_getSetNode(m_sViewName, false), + css::uno::UNO_QUERY); + if (xNode.is()) + { + bool bVisible = false; + if (xNode->getPropertyValue(PROPERTY_VISIBLE) >>= bVisible) + { + eState = bVisible ? STATE_TRUE : STATE_FALSE; + } + } + } + catch(const css::uno::Exception&) + { + TOOLS_WARN_EXCEPTION("unotools", "Unexpected exception"); + } + return eState; +} + +// public method + +void SvtViewOptions::SetVisible( bool bVisible ) +{ + // Safe impossible cases. + // These call isn't allowed for dialogs, tab-dialogs or tab-pages! + assert(m_eViewType == EViewType::Window && "SvtViewOptions::SetVisible()\nCall not allowed for Dialogs, TabDialogs or TabPages! I do nothing!" ); + + try + { + css::uno::Reference< css::beans::XPropertySet > xNode( + impl_getSetNode(m_sViewName, true), + css::uno::UNO_QUERY_THROW); + xNode->setPropertyValue(PROPERTY_VISIBLE, css::uno::Any(bVisible)); + ::comphelper::ConfigurationHelper::flush(m_xRoot); + } + catch(const css::uno::Exception&) + { + TOOLS_WARN_EXCEPTION("unotools", "Unexpected exception"); + } +} + +// public method + +bool SvtViewOptions::HasVisible() const +{ + // Safe impossible cases. + // These call isn't allowed for dialogs, tab-dialogs or tab-pages! + assert( m_eViewType == EViewType::Window && "SvtViewOptions::IsVisible()\nCall not allowed for Dialogs, TabDialogs or TabPages! I do nothing!" ); + + return GetVisible() != STATE_NONE; +} + +css::uno::Sequence< css::beans::NamedValue > SvtViewOptions::GetUserData() const +{ + try + { + css::uno::Reference< css::container::XNameAccess > xNode( + impl_getSetNode(m_sViewName, false), + css::uno::UNO_QUERY); // no _THROW ! because we don't create missing items here. So we have to live with zero references .-) + css::uno::Reference< css::container::XNameAccess > xUserData; + if (xNode.is()) + xNode->getByName(PROPERTY_USERDATA) >>= xUserData; + if (xUserData.is()) + { + const css::uno::Sequence<OUString> lNames = xUserData->getElementNames(); + sal_Int32 c = lNames.getLength(); + css::uno::Sequence< css::beans::NamedValue > lUserData(c); + + std::transform(lNames.begin(), lNames.end(), lUserData.getArray(), + [&xUserData](const OUString& rName) -> css::beans::NamedValue { + return { rName, xUserData->getByName(rName) }; }); + + return lUserData; + } + } + catch(const css::uno::Exception&) + { + TOOLS_WARN_EXCEPTION("unotools", "Unexpected exception"); + } + + return css::uno::Sequence< css::beans::NamedValue >(); +} + +void SvtViewOptions::SetUserData( const css::uno::Sequence< css::beans::NamedValue >& lData ) +{ + try + { + css::uno::Reference< css::container::XNameAccess > xNode( + impl_getSetNode(m_sViewName, true), + css::uno::UNO_QUERY_THROW); + css::uno::Reference< css::container::XNameContainer > xUserData; + xNode->getByName(PROPERTY_USERDATA) >>= xUserData; + if (xUserData.is()) + { + for (const css::beans::NamedValue& rData : lData) + { + if (xUserData->hasByName(rData.Name)) + xUserData->replaceByName(rData.Name, rData.Value); + else + xUserData->insertByName(rData.Name, rData.Value); + } + } + ::comphelper::ConfigurationHelper::flush(m_xRoot); + } + catch(const css::uno::Exception&) + { + TOOLS_WARN_EXCEPTION("unotools", "Unexpected exception"); + } +} + +css::uno::Any SvtViewOptions::GetUserItem( const OUString& sItemName ) const +{ + css::uno::Any aItem; + try + { + css::uno::Reference< css::container::XNameAccess > xNode( + impl_getSetNode(m_sViewName, false), + css::uno::UNO_QUERY); + css::uno::Reference< css::container::XNameAccess > xUserData; + if (xNode.is()) + xNode->getByName(PROPERTY_USERDATA) >>= xUserData; + if (xUserData.is()) + aItem = xUserData->getByName(sItemName); + } + catch(const css::container::NoSuchElementException&) + { aItem.clear(); } + catch(const css::uno::Exception&) + { + TOOLS_WARN_EXCEPTION("unotools", "Unexpected exception"); + aItem.clear(); + } + + return aItem; +} + +void SvtViewOptions::SetUserItem( const OUString& sItemName , + const css::uno::Any& aValue ) +{ + try + { + css::uno::Reference< css::container::XNameAccess > xNode( + impl_getSetNode(m_sViewName, true), + css::uno::UNO_QUERY_THROW); + css::uno::Reference< css::container::XNameContainer > xUserData; + xNode->getByName(PROPERTY_USERDATA) >>= xUserData; + if (xUserData.is()) + { + if (xUserData->hasByName(sItemName)) + xUserData->replaceByName(sItemName, aValue); + else + xUserData->insertByName(sItemName, aValue); + } + ::comphelper::ConfigurationHelper::flush(m_xRoot); + } + catch(const css::uno::Exception&) + { + TOOLS_WARN_EXCEPTION("unotools", "Unexpected exception"); + } +} + + + +/*-************************************************************************************************************ + @short create new set node with default values on disk + @descr To create a new UserData item - the super node of these property must already exist! + You can call this method to create these new entry with default values and change UserData then. + + @seealso method impl_writeDirectProp() + + @param "sNode", name of new entry +*//*-*************************************************************************************************************/ +css::uno::Reference< css::uno::XInterface > SvtViewOptions::impl_getSetNode( const OUString& sNode , + bool bCreateIfMissing) const +{ + css::uno::Reference< css::uno::XInterface > xNode; + + try + { + if (bCreateIfMissing) + xNode = ::comphelper::ConfigurationHelper::makeSureSetNodeExists(m_xRoot, m_sListName, sNode); + else + { + if (m_xSet.is() && m_xSet->hasByName(sNode) ) + m_xSet->getByName(sNode) >>= xNode; + } + } + catch(const css::container::NoSuchElementException&) + { xNode.clear(); } + catch(const css::uno::Exception&) + { + TOOLS_WARN_EXCEPTION("unotools", "Unexpected exception"); + xNode.clear(); + } + + return xNode; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/i18n/calendarwrapper.cxx b/unotools/source/i18n/calendarwrapper.cxx new file mode 100644 index 0000000000..51e975bbe9 --- /dev/null +++ b/unotools/source/i18n/calendarwrapper.cxx @@ -0,0 +1,342 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/log.hxx> +#include <unotools/calendarwrapper.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <com/sun/star/i18n/LocaleCalendar2.hpp> +#include <comphelper/diagnose_ex.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::i18n; +using namespace ::com::sun::star::uno; + +CalendarWrapper::CalendarWrapper( + const Reference< uno::XComponentContext > & rxContext + ) + : + aEpochStart( Date( 1, 1, 1970 ) ) +{ + xC = LocaleCalendar2::create(rxContext); +} + +CalendarWrapper::~CalendarWrapper() +{ +} + +void CalendarWrapper::loadDefaultCalendar( const css::lang::Locale& rLocale, bool bTimeZoneUTC ) +{ + try + { + if ( xC.is() ) + xC->loadDefaultCalendarTZ( rLocale, (bTimeZoneUTC ? "UTC" : OUString())); + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "loadDefaultCalendar" ); + } +} + +void CalendarWrapper::loadCalendar( const OUString& rUniqueID, const css::lang::Locale& rLocale, bool bTimeZoneUTC ) +{ + try + { + if ( xC.is() ) + xC->loadCalendarTZ( rUniqueID, rLocale, (bTimeZoneUTC ? "UTC" : OUString())); + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "loadCalendar: " + << rUniqueID << " Locale: " << LanguageTag::convertToBcp47(rLocale) ); + } +} + +css::uno::Sequence< OUString > CalendarWrapper::getAllCalendars( const css::lang::Locale& rLocale ) const +{ + try + { + if ( xC.is() ) + return xC->getAllCalendars( rLocale ); + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "getAllCalendars" ); + } + + return {}; +} + +OUString CalendarWrapper::getUniqueID() const +{ + try + { + if ( xC.is() ) + return xC->getUniqueID(); + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "getUniqueID" ); + } + return OUString(); +} + +void CalendarWrapper::setDateTime( double fTimeInDays ) +{ + try + { + if ( xC.is() ) + xC->setDateTime( fTimeInDays ); + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "setDateTime" ); + } +} + +double CalendarWrapper::getDateTime() const +{ + try + { + if ( xC.is() ) + return xC->getDateTime(); + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "getDateTime" ); + } + return 0.0; +} + +void CalendarWrapper::setLocalDateTime( double fTimeInDays ) +{ + try + { + if ( xC.is() ) + { + xC->setLocalDateTime( fTimeInDays ); + } + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "setLocalDateTime" ); + } +} + +double CalendarWrapper::getLocalDateTime() const +{ + try + { + if ( xC.is() ) + { + return xC->getLocalDateTime(); + } + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "getLocalDateTime" ); + } + return 0.0; +} + +void CalendarWrapper::setValue( sal_Int16 nFieldIndex, sal_Int16 nValue ) +{ + try + { + if ( xC.is() ) + xC->setValue( nFieldIndex, nValue ); + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "setValue" ); + } +} + +bool CalendarWrapper::isValid() const +{ + try + { + if ( xC.is() ) + return xC->isValid(); + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "isValid" ); + } + return false; +} + +sal_Int16 CalendarWrapper::getValue( sal_Int16 nFieldIndex ) const +{ + try + { + if ( xC.is() ) + return xC->getValue( nFieldIndex ); + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "getValue" ); + } + return 0; +} + +sal_Int16 CalendarWrapper::getFirstDayOfWeek() const +{ + try + { + if ( xC.is() ) + return xC->getFirstDayOfWeek(); + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "getFirstDayOfWeek" ); + } + return 0; +} + +sal_Int16 CalendarWrapper::getNumberOfMonthsInYear() const +{ + try + { + if ( xC.is() ) + return xC->getNumberOfMonthsInYear(); + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "getNumberOfMonthsInYear" ); + } + return 0; +} + +sal_Int16 CalendarWrapper::getNumberOfDaysInWeek() const +{ + try + { + if ( xC.is() ) + return xC->getNumberOfDaysInWeek(); + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "getNumberOfDaysInWeek" ); + } + return 0; +} + +css::uno::Sequence< css::i18n::CalendarItem2 > CalendarWrapper::getMonths() const +{ + try + { + if ( xC.is() ) + return xC->getMonths2(); + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "getMonths" ); + } + return {}; +} + +css::uno::Sequence< css::i18n::CalendarItem2 > CalendarWrapper::getDays() const +{ + try + { + if ( xC.is() ) + return xC->getDays2(); + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "getDays" ); + } + return {}; +} + +OUString CalendarWrapper::getDisplayName( sal_Int16 nCalendarDisplayIndex, sal_Int16 nIdx, sal_Int16 nNameType ) const +{ + try + { + if ( xC.is() ) + return xC->getDisplayName( nCalendarDisplayIndex, nIdx, nNameType ); + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "getDisplayName" ); + } + return OUString(); +} + +// --- XExtendedCalendar ----------------------------------------------------- + +OUString CalendarWrapper::getDisplayString( sal_Int32 nCalendarDisplayCode, sal_Int16 nNativeNumberMode ) const +{ + try + { + if ( xC.is() ) + return xC->getDisplayString( nCalendarDisplayCode, nNativeNumberMode ); + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "getDisplayString" ); + } + return OUString(); +} + +// --- XCalendar3 ------------------------------------------------------------ + +css::i18n::Calendar2 CalendarWrapper::getLoadedCalendar() const +{ + try + { + if ( xC.is() ) + return xC->getLoadedCalendar2(); + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "getLoadedCalendar2" ); + } + return css::i18n::Calendar2(); +} + +css::uno::Sequence< css::i18n::CalendarItem2 > CalendarWrapper::getGenitiveMonths() const +{ + try + { + if ( xC.is() ) + return xC->getGenitiveMonths2(); + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "getGenitiveMonths" ); + } + return {}; +} + +css::uno::Sequence< css::i18n::CalendarItem2 > CalendarWrapper::getPartitiveMonths() const +{ + try + { + if ( xC.is() ) + return xC->getPartitiveMonths2(); + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "getPartitiveMonths" ); + } + return {}; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/i18n/caserotate.cxx b/unotools/source/i18n/caserotate.cxx new file mode 100644 index 0000000000..a242b855ec --- /dev/null +++ b/unotools/source/i18n/caserotate.cxx @@ -0,0 +1,43 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <unotools/caserotate.hxx> +#include <i18nutil/transliteration.hxx> + +//TODO Use XCharacterClassification::getStringType to determine the current +//(possibly mixed) case type and rotate to the next one + +TransliterationFlags RotateTransliteration::getNextMode() +{ + TransliterationFlags nMode = TransliterationFlags::NONE; + + switch (nF3ShiftCounter) + { + case 0: + nMode = TransliterationFlags::TITLE_CASE; + break; + case 1: //tdf#116315 + nMode = TransliterationFlags::SENTENCE_CASE; + break; + case 2: + nMode = TransliterationFlags::LOWERCASE_UPPERCASE; + break; + default: + case 3: + nMode = TransliterationFlags::UPPERCASE_LOWERCASE; + nF3ShiftCounter = -1; + break; + } + + nF3ShiftCounter++; + + return nMode; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/i18n/charclass.cxx b/unotools/source/i18n/charclass.cxx new file mode 100644 index 0000000000..423f9530f2 --- /dev/null +++ b/unotools/source/i18n/charclass.cxx @@ -0,0 +1,452 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <comphelper/processfactory.hxx> +#include <unotools/charclass.hxx> +#include <rtl/character.hxx> +#include <comphelper/diagnose_ex.hxx> + +#include <com/sun/star/i18n/CharacterClassification.hpp> +#include <utility> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::i18n; +using namespace ::com::sun::star::uno; + +CharClass::CharClass( + const Reference< uno::XComponentContext > & rxContext, + LanguageTag aLanguageTag + ) + : maLanguageTag(std::move( aLanguageTag)) +{ + xCC = CharacterClassification::create( rxContext ); +} + +CharClass::CharClass( LanguageTag aLanguageTag ) + : maLanguageTag(std::move( aLanguageTag)) +{ + xCC = CharacterClassification::create( comphelper::getProcessComponentContext() ); +} + +CharClass::~CharClass() +{ +} + +const LanguageTag& CharClass::getLanguageTag() const +{ + return maLanguageTag; +} + +const css::lang::Locale& CharClass::getMyLocale() const +{ + return maLanguageTag.getLocale(); +} + +// static +bool CharClass::isAsciiNumeric( std::u16string_view rStr ) +{ + if ( rStr.empty() ) + return false; + const sal_Unicode* p = rStr.data(); + const sal_Unicode* const pStop = p + rStr.size(); + + do + { + if ( !rtl::isAsciiDigit( *p ) ) + return false; + } + while ( ++p < pStop ); + + return true; +} + +// static +bool CharClass::isAsciiAlpha( std::u16string_view rStr ) +{ + if ( rStr.empty() ) + return false; + const sal_Unicode* p = rStr.data(); + const sal_Unicode* const pStop = p + rStr.size(); + + do + { + if ( !rtl::isAsciiAlpha( *p ) ) + return false; + } + while ( ++p < pStop ); + + return true; +} + +bool CharClass::isAlpha( const OUString& rStr, sal_Int32 nPos ) const +{ + sal_Unicode c = rStr[nPos]; + if ( c < 128 ) + return rtl::isAsciiAlpha( c ); + + try + { + return (xCC->getCharacterType( rStr, nPos, getMyLocale() ) & + nCharClassAlphaType) != 0; + } + catch ( const Exception& ) + { + TOOLS_WARN_EXCEPTION("unotools.i18n", "" ); + } + return false; +} + +bool CharClass::isLetter( const OUString& rStr, sal_Int32 nPos ) const +{ + sal_Unicode c = rStr[nPos]; + if ( c < 128 ) + return rtl::isAsciiAlpha( c ); + + try + { + return (xCC->getCharacterType( rStr, nPos, getMyLocale() ) & + nCharClassLetterType) != 0; + } + catch ( const Exception& ) + { + TOOLS_WARN_EXCEPTION("unotools.i18n", "" ); + } + return false; +} + +bool CharClass::isLetter( const OUString& rStr ) const +{ + if (rStr.isEmpty()) + return false; + + try + { + sal_Int32 nPos = 0; + while (nPos < rStr.getLength()) + { + if (!isLetter( rStr, nPos)) + return false; + rStr.iterateCodePoints( &nPos); + } + return true; + } + catch ( const Exception& ) + { + TOOLS_WARN_EXCEPTION("unotools.i18n", "" ); + } + return false; +} + +bool CharClass::isDigit( const OUString& rStr, sal_Int32 nPos ) const +{ + sal_Unicode c = rStr[ nPos ]; + if ( c < 128 ) + return rtl::isAsciiDigit( c ); + + try + { + return (xCC->getCharacterType( rStr, nPos, getMyLocale() ) & + KCharacterType::DIGIT) != 0; + } + catch ( const Exception& ) + { + TOOLS_WARN_EXCEPTION("unotools.i18n", "" ); + } + return false; +} + +bool CharClass::isNumeric( const OUString& rStr ) const +{ + if (rStr.isEmpty()) + return false; + + try + { + sal_Int32 nPos = 0; + while (nPos < rStr.getLength()) + { + if (!isDigit( rStr, nPos)) + return false; + rStr.iterateCodePoints( &nPos); + } + return true; + } + catch ( const Exception& ) + { + TOOLS_WARN_EXCEPTION("unotools.i18n", "" ); + } + return false; +} + +bool CharClass::isAlphaNumeric( const OUString& rStr, sal_Int32 nPos ) const +{ + sal_Unicode c = rStr[nPos]; + if ( c < 128 ) + return rtl::isAsciiAlphanumeric( c ); + + try + { + return (xCC->getCharacterType( rStr, nPos, getMyLocale() ) & + (nCharClassAlphaType | nCharClassNumericType)) != 0; + } + catch ( const Exception& ) + { + TOOLS_WARN_EXCEPTION("unotools.i18n", "" ); + } + return false; +} + +bool CharClass::isLetterNumeric( const OUString& rStr, sal_Int32 nPos ) const +{ + sal_Unicode c = rStr[nPos]; + if ( c < 128 ) + return rtl::isAsciiAlphanumeric( c ); + + try + { + return (xCC->getCharacterType( rStr, nPos, getMyLocale() ) & + (nCharClassLetterType | nCharClassNumericType)) != 0; + } + catch ( const Exception& ) + { + TOOLS_WARN_EXCEPTION("unotools.i18n", "" ); + } + return false; +} + +bool CharClass::isLetterNumeric( const OUString& rStr ) const +{ + if (rStr.isEmpty()) + return false; + + try + { + sal_Int32 nPos = 0; + while (nPos < rStr.getLength()) + { + if (!isLetterNumeric( rStr, nPos)) + return false; + rStr.iterateCodePoints( &nPos); + } + return true; + } + catch ( const Exception& ) + { + TOOLS_WARN_EXCEPTION("unotools.i18n", "" ); + } + return false; +} + +bool CharClass::isBase( const OUString& rStr, sal_Int32 nPos ) const +{ + sal_Unicode c = rStr[nPos]; + if ( c < 128 ) + return rtl::isAsciiAlphanumeric( c ); + + try + { + return (xCC->getCharacterType( rStr, nPos, getMyLocale() ) & nCharClassBaseType ) != 0; + } + catch ( const Exception& ) + { + TOOLS_WARN_EXCEPTION("unotools.i18n", "" ); + } + return false; +} + +bool CharClass::isUpper( const OUString& rStr, sal_Int32 nPos ) const +{ + sal_Unicode c = rStr[nPos]; + if ( c < 128 ) + return rtl::isAsciiUpperCase(c); + + try + { + return (xCC->getCharacterType( rStr, nPos, getMyLocale()) & + KCharacterType::UPPER) != 0; + } + catch ( const Exception& ) + { + TOOLS_WARN_EXCEPTION("unotools.i18n", "" ); + } + return false; +} + +bool CharClass::isUpper( const OUString& rStr, sal_Int32 nPos, sal_Int32 nCount ) const +{ + if (rStr.isEmpty()) + return false; + + assert(nPos >= 0 && nPos < rStr.getLength() && nCount > 0); + if (nPos < 0 || nPos >= rStr.getLength() || nCount == 0) + return false; + + try + { + const sal_Int32 nLen = std::min( nPos + nCount, rStr.getLength()); + while (nPos < nLen) + { + if (!isUpper( rStr, nPos)) + return false; + rStr.iterateCodePoints( &nPos); + } + return true; + } + catch ( const Exception& ) + { + TOOLS_WARN_EXCEPTION("unotools.i18n", "" ); + } + return false; +} + +OUString CharClass::titlecase(const OUString& rStr, sal_Int32 nPos, sal_Int32 nCount) const +{ + try + { + return xCC->toTitle( rStr, nPos, nCount, getMyLocale() ); + } + catch ( const Exception& ) + { + TOOLS_WARN_EXCEPTION("unotools.i18n", "" ); + } + return rStr.copy( nPos, nCount ); +} + +OUString CharClass::uppercase( const OUString& rStr, sal_Int32 nPos, sal_Int32 nCount ) const +{ + try + { + return xCC->toUpper( rStr, nPos, nCount, getMyLocale() ); + } + catch ( const Exception& ) + { + TOOLS_WARN_EXCEPTION("unotools.i18n", "" ); + } + return rStr.copy( nPos, nCount ); +} + +OUString CharClass::lowercase( const OUString& rStr, sal_Int32 nPos, sal_Int32 nCount ) const +{ + try + { + return xCC->toLower( rStr, nPos, nCount, getMyLocale() ); + } + catch ( const Exception& ) + { + TOOLS_WARN_EXCEPTION("unotools.i18n", "" ); + } + return rStr.copy( nPos, nCount ); +} + +sal_Int16 CharClass::getType( const OUString& rStr, sal_Int32 nPos ) const +{ + try + { + return xCC->getType( rStr, nPos ); + } + catch ( const Exception& ) + { + TOOLS_WARN_EXCEPTION("unotools.i18n", "" ); + } + return 0; +} + +css::i18n::DirectionProperty CharClass::getCharacterDirection( const OUString& rStr, sal_Int32 nPos ) const +{ + try + { + return static_cast<css::i18n::DirectionProperty>(xCC->getCharacterDirection( rStr, nPos )); + } + catch ( const Exception& ) + { + TOOLS_WARN_EXCEPTION("unotools.i18n", "" ); + } + return css::i18n::DirectionProperty_LEFT_TO_RIGHT; +} + +css::i18n::UnicodeScript CharClass::getScript( const OUString& rStr, sal_Int32 nPos ) const +{ + try + { + return static_cast<css::i18n::UnicodeScript>(xCC->getScript( rStr, nPos )); + } + catch ( const Exception& ) + { + TOOLS_WARN_EXCEPTION("unotools.i18n", "" ); + } + return UnicodeScript_kBasicLatin; +} + +sal_Int32 CharClass::getCharacterType( const OUString& rStr, sal_Int32 nPos ) const +{ + try + { + return xCC->getCharacterType( rStr, nPos, getMyLocale() ); + } + catch ( const Exception& ) + { + TOOLS_WARN_EXCEPTION("unotools.i18n", "" ); + } + return 0; +} + +css::i18n::ParseResult CharClass::parseAnyToken( + const OUString& rStr, + sal_Int32 nPos, + sal_Int32 nStartCharFlags, + const OUString& userDefinedCharactersStart, + sal_Int32 nContCharFlags, + const OUString& userDefinedCharactersCont ) const +{ + try + { + return xCC->parseAnyToken( rStr, nPos, getMyLocale(), + nStartCharFlags, userDefinedCharactersStart, + nContCharFlags, userDefinedCharactersCont ); + } + catch ( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "parseAnyToken" ); + } + return ParseResult(); +} + +css::i18n::ParseResult CharClass::parsePredefinedToken( + sal_Int32 nTokenType, + const OUString& rStr, + sal_Int32 nPos, + sal_Int32 nStartCharFlags, + const OUString& userDefinedCharactersStart, + sal_Int32 nContCharFlags, + const OUString& userDefinedCharactersCont ) const +{ + try + { + return xCC->parsePredefinedToken( nTokenType, rStr, nPos, getMyLocale(), + nStartCharFlags, userDefinedCharactersStart, + nContCharFlags, userDefinedCharactersCont ); + } + catch ( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "parsePredefinedToken" ); + } + return ParseResult(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/i18n/collatorwrapper.cxx b/unotools/source/i18n/collatorwrapper.cxx new file mode 100644 index 0000000000..4da1398e06 --- /dev/null +++ b/unotools/source/i18n/collatorwrapper.cxx @@ -0,0 +1,97 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <sal/log.hxx> +#include <unotools/collatorwrapper.hxx> +#include <com/sun/star/i18n/Collator.hpp> + +using namespace ::com::sun::star; + +CollatorWrapper::CollatorWrapper ( const uno::Reference< uno::XComponentContext > &rxContext ) +{ + mxInternationalCollator = i18n::Collator::create( rxContext ); +} + +sal_Int32 +CollatorWrapper::compareString (const OUString& s1, const OUString& s2) const +{ + try + { + if (mxInternationalCollator.is()) + return mxInternationalCollator->compareString (s1, s2); + } + catch (const uno::RuntimeException&) + { + SAL_WARN( "unotools.i18n","CollatorWrapper: compareString failed"); + } + + return 0; +} + +uno::Sequence< OUString > +CollatorWrapper::listCollatorAlgorithms (const lang::Locale& rLocale) const +{ + try + { + if (mxInternationalCollator.is()) + return mxInternationalCollator->listCollatorAlgorithms (rLocale); + } + catch (const uno::RuntimeException&) + { + SAL_WARN( "unotools.i18n","CollatorWrapper: listCollatorAlgorithms failed"); + } + + return uno::Sequence< OUString > (); +} + +sal_Int32 +CollatorWrapper::loadDefaultCollator (const lang::Locale& rLocale, sal_Int32 nOptions) +{ + try + { + if (mxInternationalCollator.is()) + return mxInternationalCollator->loadDefaultCollator (rLocale, nOptions); + } + catch (const uno::RuntimeException&) + { + SAL_WARN( "unotools.i18n","CollatorWrapper: loadDefaultCollator failed"); + } + + return 0; +} + +void +CollatorWrapper::loadCollatorAlgorithm (const OUString& rAlgorithm, + const lang::Locale& rLocale, sal_Int32 nOptions) +{ + try + { + if (mxInternationalCollator.is()) + mxInternationalCollator->loadCollatorAlgorithm ( + rAlgorithm, rLocale, nOptions); + } + catch (const uno::RuntimeException&) + { + SAL_WARN( "unotools.i18n","CollatorWrapper: loadCollatorAlgorithm failed"); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/i18n/intlwrapper.cxx b/unotools/source/i18n/intlwrapper.cxx new file mode 100644 index 0000000000..19157dab4a --- /dev/null +++ b/unotools/source/i18n/intlwrapper.cxx @@ -0,0 +1,58 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <unotools/intlwrapper.hxx> +#include <unotools/collatorwrapper.hxx> +#include <unotools/localedatawrapper.hxx> +#include <com/sun/star/i18n/CollatorOptions.hpp> +#include <comphelper/processfactory.hxx> +#include <utility> + +IntlWrapper::IntlWrapper( LanguageTag aLanguageTag ) + : + maLanguageTag(std::move( aLanguageTag )), + m_xContext( comphelper::getProcessComponentContext() ) +{ +} + +IntlWrapper::~IntlWrapper() +{ +} + +void IntlWrapper::ImplNewLocaleData() const +{ + const_cast<IntlWrapper*>(this)->pLocaleData.reset( new LocaleDataWrapper( m_xContext, maLanguageTag ) ); +} + +void IntlWrapper::ImplNewCollator( bool bCaseSensitive ) const +{ + if ( bCaseSensitive ) + { + const_cast<IntlWrapper*>(this)->moCaseCollator.emplace(m_xContext); + const_cast<IntlWrapper*>(this)->moCaseCollator->loadDefaultCollator( maLanguageTag.getLocale(), 0 ); + } + else + { + const_cast<IntlWrapper*>(this)->moCollator.emplace(m_xContext); + const_cast<IntlWrapper*>(this)->moCollator->loadDefaultCollator( maLanguageTag.getLocale(), + css::i18n::CollatorOptions::CollatorOptions_IGNORE_CASE ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/i18n/localedatawrapper.cxx b/unotools/source/i18n/localedatawrapper.cxx new file mode 100644 index 0000000000..87299810ab --- /dev/null +++ b/unotools/source/i18n/localedatawrapper.cxx @@ -0,0 +1,1517 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <limits> +#include <stdio.h> +#include <string> + +#include <sal/log.hxx> +#include <unotools/localedatawrapper.hxx> +#include <unotools/digitgroupingiterator.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <tools/debug.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <o3tl/safeint.hxx> + +#include <com/sun/star/i18n/KNumberFormatUsage.hpp> +#include <com/sun/star/i18n/KNumberFormatType.hpp> +#include <com/sun/star/i18n/LocaleData2.hpp> +#include <com/sun/star/i18n/NumberFormatIndex.hpp> +#include <com/sun/star/i18n/NumberFormatMapper.hpp> + +#include <comphelper/processfactory.hxx> +#include <comphelper/sequence.hxx> +#include <rtl/ustrbuf.hxx> +#include <rtl/math.hxx> +#include <tools/date.hxx> +#include <tools/time.hxx> +#include <tools/duration.hxx> +#include <o3tl/string_view.hxx> +#include <utility> + +const sal_uInt16 nCurrFormatDefault = 0; + +using namespace ::com::sun::star; +using namespace ::com::sun::star::i18n; +using namespace ::com::sun::star::uno; + +namespace +{ + uno::Sequence< lang::Locale > gInstalledLocales; + std::vector< LanguageType > gInstalledLanguageTypes; +} + +sal_uInt8 LocaleDataWrapper::nLocaleDataChecking = 0; + +LocaleDataWrapper::LocaleDataWrapper( + const Reference< uno::XComponentContext > & rxContext, + LanguageTag aLanguageTag + ) + : + m_xContext( rxContext ), + xLD( LocaleData2::create(rxContext) ), + maLanguageTag(std::move( aLanguageTag )) +{ + loadData(); + loadDateAcceptancePatterns({}); +} + +LocaleDataWrapper::LocaleDataWrapper( + LanguageTag aLanguageTag, + const std::vector<OUString> & rOverrideDateAcceptancePatterns + ) + : + m_xContext( comphelper::getProcessComponentContext() ), + xLD( LocaleData2::create(m_xContext) ), + maLanguageTag(std::move( aLanguageTag )) +{ + loadData(); + loadDateAcceptancePatterns(rOverrideDateAcceptancePatterns); +} + +LocaleDataWrapper::~LocaleDataWrapper() +{ +} + +const LanguageTag& LocaleDataWrapper::getLanguageTag() const +{ + return maLanguageTag; +} + +const css::lang::Locale& LocaleDataWrapper::getMyLocale() const +{ + return maLanguageTag.getLocale(); +} + +void LocaleDataWrapper::loadData() +{ + const css::lang::Locale& rMyLocale = maLanguageTag.getLocale(); + + { + const Sequence< Currency2 > aCurrSeq = getAllCurrencies(); + if ( !aCurrSeq.hasElements() ) + { + if (areChecksEnabled()) + outputCheckMessage("LocaleDataWrapper::getCurrSymbolsImpl: no currency at all, using ShellsAndPebbles"); + aCurrSymbol = "ShellsAndPebbles"; + aCurrBankSymbol = aCurrSymbol; + nCurrPositiveFormat = nCurrNegativeFormat = nCurrFormatDefault; + nCurrDigits = 2; + } + else + { + auto pCurr = std::find_if(aCurrSeq.begin(), aCurrSeq.end(), + [](const Currency2& rCurr) { return rCurr.Default; }); + if ( pCurr == aCurrSeq.end() ) + { + if (areChecksEnabled()) + { + outputCheckMessage( appendLocaleInfo( u"LocaleDataWrapper::getCurrSymbolsImpl: no default currency" ) ); + } + pCurr = aCurrSeq.begin(); + } + aCurrSymbol = pCurr->Symbol; + aCurrBankSymbol = pCurr->BankSymbol; + nCurrDigits = pCurr->DecimalPlaces; + } + } + + loadCurrencyFormats(); + + { + xDefaultCalendar.reset(); + xSecondaryCalendar.reset(); + const Sequence< Calendar2 > xCals = getAllCalendars(); + if (xCals.getLength() > 1) + { + auto pCal = std::find_if(xCals.begin(), xCals.end(), + [](const Calendar2& rCal) { return !rCal.Default; }); + if (pCal != xCals.end()) + xSecondaryCalendar = std::make_shared<Calendar2>( *pCal); + } + auto pCal = xCals.begin(); + if (xCals.getLength() > 1) + { + pCal = std::find_if(xCals.begin(), xCals.end(), + [](const Calendar2& rCal) { return rCal.Default; }); + if (pCal == xCals.end()) + pCal = xCals.begin(); + } + xDefaultCalendar = std::make_shared<Calendar2>( *pCal); + } + + loadDateOrders(); + + try + { + aDateAcceptancePatterns = xLD->getDateAcceptancePatterns( rMyLocale ); + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "getDateAcceptancePatterns" ); + aDateAcceptancePatterns = {}; + } + + + loadDigitGrouping(); + + try + { + aReservedWords = comphelper::sequenceToContainer<std::vector<OUString>>(xLD->getReservedWord( rMyLocale )); + } + catch ( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "getReservedWord" ); + } + + try + { + aLocaleDataItem = xLD->getLocaleItem2( rMyLocale ); + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "getLocaleItem" ); + static const css::i18n::LocaleDataItem2 aEmptyItem; + aLocaleDataItem = aEmptyItem; + } + + aLocaleItem[LocaleItem::DATE_SEPARATOR] = aLocaleDataItem.dateSeparator; + aLocaleItem[LocaleItem::THOUSAND_SEPARATOR] = aLocaleDataItem.thousandSeparator; + aLocaleItem[LocaleItem::DECIMAL_SEPARATOR] = aLocaleDataItem.decimalSeparator; + aLocaleItem[LocaleItem::TIME_SEPARATOR] = aLocaleDataItem.timeSeparator; + aLocaleItem[LocaleItem::TIME_100SEC_SEPARATOR] = aLocaleDataItem.time100SecSeparator; + aLocaleItem[LocaleItem::LIST_SEPARATOR] = aLocaleDataItem.listSeparator; + aLocaleItem[LocaleItem::SINGLE_QUOTATION_START] = aLocaleDataItem.quotationStart; + aLocaleItem[LocaleItem::SINGLE_QUOTATION_END] = aLocaleDataItem.quotationEnd; + aLocaleItem[LocaleItem::DOUBLE_QUOTATION_START] = aLocaleDataItem.doubleQuotationStart; + aLocaleItem[LocaleItem::DOUBLE_QUOTATION_END] = aLocaleDataItem.doubleQuotationEnd; + aLocaleItem[LocaleItem::MEASUREMENT_SYSTEM] = aLocaleDataItem.measurementSystem; + aLocaleItem[LocaleItem::TIME_AM] = aLocaleDataItem.timeAM; + aLocaleItem[LocaleItem::TIME_PM] = aLocaleDataItem.timePM; + aLocaleItem[LocaleItem::LONG_DATE_DAY_OF_WEEK_SEPARATOR] = aLocaleDataItem.LongDateDayOfWeekSeparator; + aLocaleItem[LocaleItem::LONG_DATE_DAY_SEPARATOR] = aLocaleDataItem.LongDateDaySeparator; + aLocaleItem[LocaleItem::LONG_DATE_MONTH_SEPARATOR] = aLocaleDataItem.LongDateMonthSeparator; + aLocaleItem[LocaleItem::LONG_DATE_YEAR_SEPARATOR] = aLocaleDataItem.LongDateYearSeparator; + aLocaleItem[LocaleItem::DECIMAL_SEPARATOR_ALTERNATIVE] = aLocaleDataItem.decimalSeparatorAlternative; +} + +/* FIXME-BCP47: locale data should provide a language tag instead that could be + * passed on. */ +css::i18n::LanguageCountryInfo LocaleDataWrapper::getLanguageCountryInfo() const +{ + try + { + return xLD->getLanguageCountryInfo( getMyLocale() ); + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "getLanguageCountryInfo" ); + } + return css::i18n::LanguageCountryInfo(); +} + +const css::i18n::LocaleDataItem2& LocaleDataWrapper::getLocaleItem() const +{ + return aLocaleDataItem; +} + +css::uno::Sequence< css::i18n::Currency2 > LocaleDataWrapper::getAllCurrencies() const +{ + try + { + return xLD->getAllCurrencies2( getMyLocale() ); + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "getAllCurrencies" ); + } + return {}; +} + +css::uno::Sequence< css::i18n::FormatElement > LocaleDataWrapper::getAllFormats() const +{ + try + { + return xLD->getAllFormats( getMyLocale() ); + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "getAllFormats" ); + } + return {}; +} + +css::i18n::ForbiddenCharacters LocaleDataWrapper::getForbiddenCharacters() const +{ + try + { + return xLD->getForbiddenCharacters( getMyLocale() ); + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "getForbiddenCharacters" ); + } + return css::i18n::ForbiddenCharacters(); +} + +const css::uno::Sequence< css::lang::Locale > & LocaleDataWrapper::getAllInstalledLocaleNames() const +{ + uno::Sequence< lang::Locale > &rInstalledLocales = gInstalledLocales; + + if ( rInstalledLocales.hasElements() ) + return rInstalledLocales; + + try + { + rInstalledLocales = xLD->getAllInstalledLocaleNames(); + } + catch ( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "getAllInstalledLocaleNames" ); + } + return rInstalledLocales; +} + +// --- Impl and helpers ---------------------------------------------------- + +// static +const css::uno::Sequence< css::lang::Locale >& LocaleDataWrapper::getInstalledLocaleNames() +{ + const uno::Sequence< lang::Locale > &rInstalledLocales = gInstalledLocales; + + if ( !rInstalledLocales.hasElements() ) + { + LocaleDataWrapper aLDW( ::comphelper::getProcessComponentContext(), LanguageTag( LANGUAGE_SYSTEM) ); + aLDW.getAllInstalledLocaleNames(); + } + return rInstalledLocales; +} + +// static +const std::vector< LanguageType >& LocaleDataWrapper::getInstalledLanguageTypes() +{ + std::vector< LanguageType > &rInstalledLanguageTypes = gInstalledLanguageTypes; + + if ( !rInstalledLanguageTypes.empty() ) + return rInstalledLanguageTypes; + + const css::uno::Sequence< css::lang::Locale > xLoc = getInstalledLocaleNames(); + sal_Int32 nCount = xLoc.getLength(); + std::vector< LanguageType > xLang; + xLang.reserve(nCount); + for ( const auto& rLoc : xLoc ) + { + LanguageTag aLanguageTag( rLoc ); + OUString aDebugLocale; + if (areChecksEnabled()) + { + aDebugLocale = aLanguageTag.getBcp47( false); + } + + LanguageType eLang = aLanguageTag.getLanguageType( false); + if (areChecksEnabled() && eLang == LANGUAGE_DONTKNOW) + { + OUString aMsg = "ConvertIsoNamesToLanguage: unknown MS-LCID for locale\n" + + aDebugLocale; + outputCheckMessage(aMsg); + } + + if ( eLang == LANGUAGE_NORWEGIAN) // no_NO, not Bokmal (nb_NO), not Nynorsk (nn_NO) + eLang = LANGUAGE_DONTKNOW; // don't offer "Unknown" language + if ( eLang != LANGUAGE_DONTKNOW ) + { + LanguageTag aBackLanguageTag( eLang); + if ( aLanguageTag != aBackLanguageTag ) + { + // In checks, exclude known problems because no MS-LCID defined + // and default for Language found. + if ( areChecksEnabled() + && aDebugLocale != "ar-SD" // Sudan/ar + && aDebugLocale != "en-CB" // Caribbean is not a country +// && aDebugLocale != "en-BG" // ?!? Bulgaria/en +// && aDebugLocale != "es-BR" // ?!? Brazil/es + ) + { + outputCheckMessage(Concat2View( + "ConvertIsoNamesToLanguage/ConvertLanguageToIsoNames: ambiguous locale (MS-LCID?)\n" + + aDebugLocale + + " -> 0x" + + OUString::number(static_cast<sal_Int32>(static_cast<sal_uInt16>(eLang)), 16) + + " -> " + + aBackLanguageTag.getBcp47() )); + } + eLang = LANGUAGE_DONTKNOW; + } + } + if ( eLang != LANGUAGE_DONTKNOW ) + xLang.push_back(eLang); + } + rInstalledLanguageTypes = xLang; + + return rInstalledLanguageTypes; +} + +const OUString& LocaleDataWrapper::getOneLocaleItem( sal_Int16 nItem ) const +{ + if ( nItem >= LocaleItem::COUNT2 ) + { + SAL_WARN( "unotools.i18n", "getOneLocaleItem: bounds" ); + return aLocaleItem[0]; + } + return aLocaleItem[nItem]; +} + +const OUString& LocaleDataWrapper::getOneReservedWord( sal_Int16 nWord ) const +{ + if ( nWord < 0 || o3tl::make_unsigned(nWord) >= aReservedWords.size() ) + { + SAL_WARN( "unotools.i18n", "getOneReservedWord: bounds" ); + static const OUString EMPTY; + return EMPTY; + } + return aReservedWords[nWord]; +} + +MeasurementSystem LocaleDataWrapper::mapMeasurementStringToEnum( std::u16string_view rMS ) const +{ +//! TODO: could be cached too + if ( o3tl::equalsIgnoreAsciiCase( rMS, u"metric" ) ) + return MeasurementSystem::Metric; +//! TODO: other measurement systems? => extend enum MeasurementSystem + return MeasurementSystem::US; +} + +bool LocaleDataWrapper::doesSecondaryCalendarUseEC( std::u16string_view rName ) const +{ + if (rName.empty()) + return false; + + // Check language tag first to avoid loading all calendars of this locale. + LanguageTag aLoaded( getLoadedLanguageTag()); + const OUString& aBcp47( aLoaded.getBcp47()); + // So far determine only by locale, we know for a few. + /* TODO: check date format codes? or add to locale data? */ + if ( aBcp47 != "ja-JP" && + aBcp47 != "lo-LA" && + aBcp47 != "zh-TW") + return false; + + if (!xSecondaryCalendar) + return false; + if (!xSecondaryCalendar->Name.equalsIgnoreAsciiCase( rName)) + return false; + + return true; +} + +const std::shared_ptr< css::i18n::Calendar2 >& LocaleDataWrapper::getDefaultCalendar() const +{ + return xDefaultCalendar; +} + +css::uno::Sequence< css::i18n::CalendarItem2 > const & LocaleDataWrapper::getDefaultCalendarDays() const +{ + return getDefaultCalendar()->Days; +} + +css::uno::Sequence< css::i18n::CalendarItem2 > const & LocaleDataWrapper::getDefaultCalendarMonths() const +{ + return getDefaultCalendar()->Months; +} + +// --- currencies ----------------------------------------------------- + +const OUString& LocaleDataWrapper::getCurrSymbol() const +{ + return aCurrSymbol; +} + +const OUString& LocaleDataWrapper::getCurrBankSymbol() const +{ + return aCurrBankSymbol; +} + +sal_uInt16 LocaleDataWrapper::getCurrPositiveFormat() const +{ + return nCurrPositiveFormat; +} + +sal_uInt16 LocaleDataWrapper::getCurrNegativeFormat() const +{ + return nCurrNegativeFormat; +} + +sal_uInt16 LocaleDataWrapper::getCurrDigits() const +{ + return nCurrDigits; +} + +void LocaleDataWrapper::scanCurrFormatImpl( std::u16string_view rCode, + sal_Int32 nStart, sal_Int32& nSign, sal_Int32& nPar, + sal_Int32& nNum, sal_Int32& nBlank, sal_Int32& nSym ) const +{ + nSign = nPar = nNum = nBlank = nSym = -1; + const sal_Unicode* const pStr = rCode.data(); + const sal_Unicode* const pStop = pStr + rCode.size(); + const sal_Unicode* p = pStr + nStart; + int nInSection = 0; + bool bQuote = false; + while ( p < pStop ) + { + if ( bQuote ) + { + if ( *p == '"' && *(p-1) != '\\' ) + bQuote = false; + } + else + { + switch ( *p ) + { + case '"' : + if ( pStr == p || *(p-1) != '\\' ) + bQuote = true; + break; + case '-' : + if (!nInSection && nSign == -1) + nSign = p - pStr; + break; + case '(' : + if (!nInSection && nPar == -1) + nPar = p - pStr; + break; + case '0' : + case '#' : + if (!nInSection && nNum == -1) + nNum = p - pStr; + break; + case '[' : + nInSection++; + break; + case ']' : + if ( nInSection ) + { + nInSection--; + if (!nInSection && nBlank == -1 + && nSym != -1 && p < pStop-1 && *(p+1) == ' ' ) + nBlank = p - pStr + 1; + } + break; + case '$' : + if (nSym == -1 && nInSection && *(p-1) == '[') + { + nSym = p - pStr + 1; + if (nNum != -1 && *(p-2) == ' ') + nBlank = p - pStr - 2; + } + break; + case ';' : + if ( !nInSection ) + p = pStop; + break; + default: + if (!nInSection && nSym == -1 && o3tl::starts_with(rCode.substr(static_cast<sal_Int32>(p - pStr)), aCurrSymbol)) + { // currency symbol not surrounded by [$...] + nSym = p - pStr; + if (nBlank == -1 && pStr < p && *(p-1) == ' ') + nBlank = p - pStr - 1; + p += aCurrSymbol.getLength() - 1; + if (nBlank == -1 && p < pStop-2 && *(p+2) == ' ') + nBlank = p - pStr + 2; + } + } + } + p++; + } +} + +void LocaleDataWrapper::loadCurrencyFormats() +{ + css::uno::Reference< css::i18n::XNumberFormatCode > xNFC = i18n::NumberFormatMapper::create( m_xContext ); + uno::Sequence< NumberFormatCode > aFormatSeq = xNFC->getAllFormatCode( KNumberFormatUsage::CURRENCY, maLanguageTag.getLocale() ); + sal_Int32 nCnt = aFormatSeq.getLength(); + if ( !nCnt ) + { // bad luck + if (areChecksEnabled()) + { + outputCheckMessage( appendLocaleInfo( u"LocaleDataWrapper::getCurrFormatsImpl: no currency formats" ) ); + } + nCurrPositiveFormat = nCurrNegativeFormat = nCurrFormatDefault; + return; + } + // find a negative code (medium preferred) and a default (medium preferred) (not necessarily the same) + NumberFormatCode const * const pFormatArr = aFormatSeq.getArray(); + sal_Int32 nElem, nDef, nNeg, nMedium; + nDef = nNeg = nMedium = -1; + for ( nElem = 0; nElem < nCnt; nElem++ ) + { + if ( pFormatArr[nElem].Type == KNumberFormatType::MEDIUM ) + { + if ( pFormatArr[nElem].Default ) + { + nDef = nElem; + nMedium = nElem; + if ( pFormatArr[nElem].Code.indexOf( ';' ) >= 0 ) + nNeg = nElem; + } + else + { + if ( (nNeg == -1 || nMedium == -1) && pFormatArr[nElem].Code.indexOf( ';' ) >= 0 ) + nNeg = nElem; + if ( nMedium == -1 ) + nMedium = nElem; + } + } + else + { + if ( nDef == -1 && pFormatArr[nElem].Default ) + nDef = nElem; + if ( nNeg == -1 && pFormatArr[nElem].Code.indexOf( ';' ) >= 0 ) + nNeg = nElem; + } + } + + sal_Int32 nSign, nPar, nNum, nBlank, nSym; + + // positive format + nElem = (nDef >= 0 ? nDef : (nNeg >= 0 ? nNeg : 0)); + scanCurrFormatImpl( pFormatArr[nElem].Code, 0, nSign, nPar, nNum, nBlank, nSym ); + if (areChecksEnabled() && (nNum == -1 || nSym == -1)) + { + outputCheckMessage( appendLocaleInfo( u"LocaleDataWrapper::getCurrFormatsImpl: CurrPositiveFormat?" ) ); + } + if (nBlank == -1) + { + if ( nSym < nNum ) + nCurrPositiveFormat = 0; // $1 + else + nCurrPositiveFormat = 1; // 1$ + } + else + { + if ( nSym < nNum ) + nCurrPositiveFormat = 2; // $ 1 + else + nCurrPositiveFormat = 3; // 1 $ + } + + // negative format + if ( nNeg < 0 ) + nCurrNegativeFormat = nCurrFormatDefault; + else + { + const OUString& rCode = pFormatArr[nNeg].Code; + sal_Int32 nDelim = rCode.indexOf(';'); + scanCurrFormatImpl( rCode, nDelim+1, nSign, nPar, nNum, nBlank, nSym ); + if (areChecksEnabled() && (nNum == -1 || nSym == -1 || (nPar == -1 && nSign == -1))) + { + outputCheckMessage( appendLocaleInfo( u"LocaleDataWrapper::getCurrFormatsImpl: CurrNegativeFormat?" ) ); + } + // NOTE: one of nPar or nSign are allowed to be -1 + if (nBlank == -1) + { + if ( nSym < nNum ) + { + if ( -1 < nPar && nPar < nSym ) + nCurrNegativeFormat = 0; // ($1) + else if ( -1 < nSign && nSign < nSym ) + nCurrNegativeFormat = 1; // -$1 + else if ( nNum < nSign ) + nCurrNegativeFormat = 3; // $1- + else + nCurrNegativeFormat = 2; // $-1 + } + else + { + if ( -1 < nPar && nPar < nNum ) + nCurrNegativeFormat = 4; // (1$) + else if ( -1 < nSign && nSign < nNum ) + nCurrNegativeFormat = 5; // -1$ + else if ( nSym < nSign ) + nCurrNegativeFormat = 7; // 1$- + else + nCurrNegativeFormat = 6; // 1-$ + } + } + else + { + if ( nSym < nNum ) + { + if ( -1 < nPar && nPar < nSym ) + nCurrNegativeFormat = 14; // ($ 1) + else if ( -1 < nSign && nSign < nSym ) + nCurrNegativeFormat = 9; // -$ 1 + else if ( nNum < nSign ) + nCurrNegativeFormat = 12; // $ 1- + else + nCurrNegativeFormat = 11; // $ -1 + } + else + { + if ( -1 < nPar && nPar < nNum ) + nCurrNegativeFormat = 15; // (1 $) + else if ( -1 < nSign && nSign < nNum ) + nCurrNegativeFormat = 8; // -1 $ + else if ( nSym < nSign ) + nCurrNegativeFormat = 10; // 1 $- + else + nCurrNegativeFormat = 13; // 1- $ + } + } + } +} + +// --- date ----------------------------------------------------------- + +DateOrder LocaleDataWrapper::getDateOrder() const +{ + return nDateOrder; +} + +LongDateOrder LocaleDataWrapper::getLongDateOrder() const +{ + return nLongDateOrder; +} + +LongDateOrder LocaleDataWrapper::scanDateOrderImpl( std::u16string_view rCode ) const +{ + // Only some european versions were translated, the ones with different + // keyword combinations are: + // English DMY, German TMJ, Spanish DMA, French JMA, Italian GMA, + // Dutch DMJ, Finnish PKV + + // default is English keywords for every other language + size_t nDay = rCode.find('D'); + size_t nMonth = rCode.find('M'); + size_t nYear = rCode.find('Y'); + if (nDay == std::u16string_view::npos || nMonth == std::u16string_view::npos || nYear == std::u16string_view::npos) + { // This algorithm assumes that all three parts (DMY) are present + if (nMonth == std::u16string_view::npos) + { // only Finnish has something else than 'M' for month + nMonth = rCode.find('K'); + if (nMonth != std::u16string_view::npos) + { + nDay = rCode.find('P'); + nYear = rCode.find('V'); + } + } + else if (nDay == std::u16string_view::npos) + { // We have a month 'M' if we reach this branch. + // Possible languages containing 'M' but no 'D': + // German, French, Italian + nDay = rCode.find('T'); // German + if (nDay != std::u16string_view::npos) + nYear = rCode.find('J'); + else + { + nYear = rCode.find('A'); // French, Italian + if (nYear != std::u16string_view::npos) + { + nDay = rCode.find('J'); // French + if (nDay == std::u16string_view::npos) + nDay = rCode.find('G'); // Italian + } + } + } + else + { // We have a month 'M' and a day 'D'. + // Possible languages containing 'D' and 'M' but not 'Y': + // Spanish, Dutch + nYear = rCode.find('A'); // Spanish + if (nYear == std::u16string_view::npos) + nYear = rCode.find('J'); // Dutch + } + if (nDay == std::u16string_view::npos || nMonth == std::u16string_view::npos || nYear == std::u16string_view::npos) + { + if (areChecksEnabled()) + { + outputCheckMessage( appendLocaleInfo( u"LocaleDataWrapper::scanDateOrder: not all DMY present" ) ); + } + if (nDay == std::u16string_view::npos) + nDay = rCode.size(); + if (nMonth == std::u16string_view::npos) + nMonth = rCode.size(); + if (nYear == std::u16string_view::npos) + nYear = rCode.size(); + } + } + // compare with <= because each position may equal rCode.getLength() + if ( nDay <= nMonth && nMonth <= nYear ) + return LongDateOrder::DMY; // also if every position equals rCode.getLength() + else if ( nMonth <= nDay && nDay <= nYear ) + return LongDateOrder::MDY; + else if ( nYear <= nMonth && nMonth <= nDay ) + return LongDateOrder::YMD; + else if ( nYear <= nDay && nDay <= nMonth ) + return LongDateOrder::YDM; + else + { + if (areChecksEnabled()) + { + outputCheckMessage( appendLocaleInfo( u"LocaleDataWrapper::scanDateOrder: no magic applicable" ) ); + } + return LongDateOrder::DMY; + } +} + +static DateOrder getDateOrderFromLongDateOrder( LongDateOrder eLong ) +{ + switch (eLong) + { + case LongDateOrder::YMD: + return DateOrder::YMD; + break; + case LongDateOrder::DMY: + return DateOrder::DMY; + break; + case LongDateOrder::MDY: + return DateOrder::MDY; + break; + case LongDateOrder::YDM: + default: + assert(!"unhandled LongDateOrder to DateOrder"); + return DateOrder::DMY; + } +} + +void LocaleDataWrapper::loadDateOrders() +{ + css::uno::Reference< css::i18n::XNumberFormatCode > xNFC = i18n::NumberFormatMapper::create( m_xContext ); + uno::Sequence< NumberFormatCode > aFormatSeq = xNFC->getAllFormatCode( KNumberFormatUsage::DATE, maLanguageTag.getLocale() ); + sal_Int32 nCnt = aFormatSeq.getLength(); + if ( !nCnt ) + { // bad luck + if (areChecksEnabled()) + { + outputCheckMessage( appendLocaleInfo( u"LocaleDataWrapper::getDateOrdersImpl: no date formats" ) ); + } + nDateOrder = DateOrder::DMY; + nLongDateOrder = LongDateOrder::DMY; + return; + } + // find the edit (21), a default (medium preferred), + // a medium (default preferred), and a long (default preferred) + NumberFormatCode const * const pFormatArr = aFormatSeq.getArray(); + sal_Int32 nEdit, nDef, nMedium, nLong; + nEdit = nDef = nMedium = nLong = -1; + for ( sal_Int32 nElem = 0; nElem < nCnt; nElem++ ) + { + if ( nEdit == -1 && pFormatArr[nElem].Index == NumberFormatIndex::DATE_SYS_DDMMYYYY ) + nEdit = nElem; + if ( nDef == -1 && pFormatArr[nElem].Default ) + nDef = nElem; + switch ( pFormatArr[nElem].Type ) + { + case KNumberFormatType::MEDIUM : + { + if ( pFormatArr[nElem].Default ) + { + nDef = nElem; + nMedium = nElem; + } + else if ( nMedium == -1 ) + nMedium = nElem; + } + break; + case KNumberFormatType::LONG : + { + if ( pFormatArr[nElem].Default ) + nLong = nElem; + else if ( nLong == -1 ) + nLong = nElem; + } + break; + } + } + if ( nEdit == -1 ) + { + if (areChecksEnabled()) + { + outputCheckMessage( appendLocaleInfo( u"LocaleDataWrapper::getDateOrdersImpl: no edit" ) ); + } + if ( nDef == -1 ) + { + if (areChecksEnabled()) + { + outputCheckMessage( appendLocaleInfo( u"LocaleDataWrapper::getDateOrdersImpl: no default" ) ); + } + if ( nMedium != -1 ) + nDef = nMedium; + else if ( nLong != -1 ) + nDef = nLong; + else + nDef = 0; + } + nEdit = nDef; + } + LongDateOrder nDO = scanDateOrderImpl( pFormatArr[nEdit].Code ); + if ( pFormatArr[nEdit].Type == KNumberFormatType::LONG ) + { // normally this is not the case + nLongDateOrder = nDO; + nDateOrder = getDateOrderFromLongDateOrder(nDO); + } + else + { + // YDM should not occur in a short/medium date (i.e. no locale has + // that) and is nowhere handled. + nDateOrder = getDateOrderFromLongDateOrder(nDO); + if ( nLong == -1 ) + nLongDateOrder = nDO; + else + nLongDateOrder = scanDateOrderImpl( pFormatArr[nLong].Code ); + } +} + +// --- digit grouping ------------------------------------------------- + +void LocaleDataWrapper::loadDigitGrouping() +{ + /* TODO: This is a very simplified grouping setup that only serves its + * current purpose for Indian locales. A free-form flexible one would + * obtain grouping from locale data where it could be specified using, for + * example, codes like #,### and #,##,### that would generate the integer + * sequence. Needed additional API and a locale data element. + */ + + if (aGrouping.hasElements() && aGrouping[0]) + return; + + i18n::LanguageCountryInfo aLCInfo( getLanguageCountryInfo()); + if (aLCInfo.Country.equalsIgnoreAsciiCase("IN") || // India + aLCInfo.Country.equalsIgnoreAsciiCase("BT") ) // Bhutan + { + aGrouping = { 3, 2, 0 }; + } + else + { + aGrouping = { 3, 0, 0 }; + } +} + +const css::uno::Sequence< sal_Int32 >& LocaleDataWrapper::getDigitGrouping() const +{ + return aGrouping; +} + +// --- simple number formatting helpers ------------------------------- + +// The ImplAdd... methods are taken from class International and modified to +// suit the needs. + +static void ImplAddUNum( OUStringBuffer& rBuf, sal_uInt64 nNumber ) +{ + // fill temp buffer with digits + sal_Unicode aTempBuf[64]; + sal_Unicode* pTempBuf = aTempBuf; + do + { + *pTempBuf = static_cast<sal_Unicode>(nNumber % 10) + '0'; + pTempBuf++; + nNumber /= 10; + } + while ( nNumber ); + + // copy temp buffer to buffer passed + do + { + pTempBuf--; + rBuf.append(*pTempBuf); + } + while ( pTempBuf != aTempBuf ); +} + +static void ImplAddUNum( OUStringBuffer& rBuf, sal_uInt64 nNumber, int nMinLen ) +{ + // fill temp buffer with digits + sal_Unicode aTempBuf[64]; + sal_Unicode* pTempBuf = aTempBuf; + do + { + *pTempBuf = static_cast<sal_Unicode>(nNumber % 10) + '0'; + pTempBuf++; + nNumber /= 10; + nMinLen--; + } + while ( nNumber ); + + // fill with zeros up to the minimal length + while ( nMinLen > 0 ) + { + rBuf.append('0'); + nMinLen--; + } + + // copy temp buffer to real buffer + do + { + pTempBuf--; + rBuf.append(*pTempBuf); + } + while ( pTempBuf != aTempBuf ); +} + +static void ImplAddNum( OUStringBuffer& rBuf, sal_Int64 nNumber, int nMinLen ) +{ + if (nNumber < 0) + { + rBuf.append('-'); + nNumber = -nNumber; + } + return ImplAddUNum( rBuf, nNumber, nMinLen); +} + +static void ImplAdd2UNum( OUStringBuffer& rBuf, sal_uInt16 nNumber ) +{ + DBG_ASSERT( nNumber < 100, "ImplAdd2UNum() - Number >= 100" ); + + if ( nNumber < 10 ) + { + rBuf.append('0'); + rBuf.append(static_cast<char>(nNumber + '0')); + } + else + { + sal_uInt16 nTemp = nNumber % 10; + nNumber /= 10; + rBuf.append(static_cast<char>(nNumber + '0')); + rBuf.append(static_cast<char>(nTemp + '0')); + } +} + +static void ImplAdd9UNum( OUStringBuffer& rBuf, sal_uInt32 nNumber ) +{ + DBG_ASSERT( nNumber < 1000000000, "ImplAdd9UNum() - Number >= 1000000000" ); + + std::ostringstream ostr; + ostr.fill('0'); + ostr.width(9); + ostr << nNumber; + std::string aStr = ostr.str(); + rBuf.appendAscii(aStr.c_str(), aStr.size()); +} + +void LocaleDataWrapper::ImplAddFormatNum( OUStringBuffer& rBuf, + sal_Int64 nNumber, sal_uInt16 nDecimals, bool bUseThousandSep, + bool bTrailingZeros ) const +{ + OUStringBuffer aNumBuf(64); + sal_uInt16 nNumLen; + + // negative number + sal_uInt64 abs; + if ( nNumber < 0 ) + { + // Avoid overflow, map -2^63 -> 2^63 explicitly: + abs = nNumber == std::numeric_limits<sal_Int64>::min() + ? static_cast<sal_uInt64>(std::numeric_limits<sal_Int64>::min()) : nNumber * -1; + rBuf.append('-'); + } + else + { + abs = nNumber; + } + + // convert number + ImplAddUNum( aNumBuf, abs ); + nNumLen = static_cast<sal_uInt16>(aNumBuf.getLength()); + + if ( nNumLen <= nDecimals ) + { + // strip .0 in decimals? + if ( !nNumber && !bTrailingZeros ) + { + rBuf.append('0'); + } + else + { + // LeadingZero, insert 0 + if ( isNumLeadingZero() ) + { + rBuf.append('0'); + } + + // append decimal separator + rBuf.append( aLocaleDataItem.decimalSeparator ); + + // fill with zeros + sal_uInt16 i = 0; + while ( i < (nDecimals-nNumLen) ) + { + rBuf.append('0'); + i++; + } + + // append decimals + rBuf.append(aNumBuf); + } + } + else + { + const OUString& rThoSep = aLocaleDataItem.thousandSeparator; + + // copy number to buffer (excluding decimals) + sal_uInt16 nNumLen2 = nNumLen-nDecimals; + uno::Sequence< sal_Bool > aGroupPos; + if (bUseThousandSep) + aGroupPos = utl::DigitGroupingIterator::createForwardSequence( + nNumLen2, getDigitGrouping()); + sal_uInt16 i = 0; + for (; i < nNumLen2; ++i ) + { + rBuf.append(aNumBuf[i]); + + // add thousand separator? + if ( bUseThousandSep && aGroupPos[i] ) + rBuf.append( rThoSep ); + } + + // append decimals + if ( nDecimals ) + { + rBuf.append( aLocaleDataItem.decimalSeparator ); + + bool bNullEnd = true; + while ( i < nNumLen ) + { + if ( aNumBuf[i] != '0' ) + bNullEnd = false; + + rBuf.append(aNumBuf[i]); + i++; + } + + // strip .0 in decimals? + if ( bNullEnd && !bTrailingZeros ) + rBuf.setLength( rBuf.getLength() - (nDecimals + 1) ); + } + } +} + +// --- simple date and time formatting -------------------------------- + +OUString LocaleDataWrapper::getDate( const Date& rDate ) const +{ +//!TODO: leading zeros et al + OUStringBuffer aBuf(128); + sal_uInt16 nDay = rDate.GetDay(); + sal_uInt16 nMonth = rDate.GetMonth(); + sal_Int16 nYear = rDate.GetYear(); + sal_uInt16 nYearLen; + + if ( (true) /* IsDateCentury() */ ) + nYearLen = 4; + else + { + nYearLen = 2; + nYear %= 100; + } + + switch ( getDateOrder() ) + { + case DateOrder::DMY : + ImplAdd2UNum( aBuf, nDay ); + aBuf.append( aLocaleDataItem.dateSeparator ); + ImplAdd2UNum( aBuf, nMonth ); + aBuf.append( aLocaleDataItem.dateSeparator ); + ImplAddNum( aBuf, nYear, nYearLen ); + break; + case DateOrder::MDY : + ImplAdd2UNum( aBuf, nMonth ); + aBuf.append( aLocaleDataItem.dateSeparator ); + ImplAdd2UNum( aBuf, nDay ); + aBuf.append( aLocaleDataItem.dateSeparator ); + ImplAddNum( aBuf, nYear, nYearLen ); + break; + default: + ImplAddNum( aBuf, nYear, nYearLen ); + aBuf.append( aLocaleDataItem.dateSeparator ); + ImplAdd2UNum( aBuf, nMonth ); + aBuf.append( aLocaleDataItem.dateSeparator ); + ImplAdd2UNum( aBuf, nDay ); + } + + return aBuf.makeStringAndClear(); +} + +OUString LocaleDataWrapper::getTime( const tools::Time& rTime, bool bSec, bool b100Sec ) const +{ +//!TODO: leading zeros et al + OUStringBuffer aBuf(128); + sal_uInt16 nHour = rTime.GetHour(); + + nHour %= 24; + + ImplAdd2UNum( aBuf, nHour ); + aBuf.append( aLocaleDataItem.timeSeparator ); + ImplAdd2UNum( aBuf, rTime.GetMin() ); + if ( bSec ) + { + aBuf.append( aLocaleDataItem.timeSeparator ); + ImplAdd2UNum( aBuf, rTime.GetSec() ); + + if ( b100Sec ) + { + aBuf.append( aLocaleDataItem.time100SecSeparator ); + ImplAdd9UNum( aBuf, rTime.GetNanoSec() ); + } + } + + return aBuf.makeStringAndClear(); +} + +OUString LocaleDataWrapper::getDuration( const tools::Duration& rDuration, bool bSec, bool b100Sec ) const +{ + OUStringBuffer aBuf(128); + + if ( rDuration.IsNegative() ) + aBuf.append(' '); + + sal_Int64 nHours = static_cast<sal_Int64>(rDuration.GetDays()) * 24 + + (rDuration.IsNegative() ? + -static_cast<sal_Int64>(rDuration.GetTime().GetHour()) : + rDuration.GetTime().GetHour()); + if ( (true) /* IsTimeLeadingZero() */ ) + ImplAddNum( aBuf, nHours, 2 ); + else + ImplAddNum( aBuf, nHours, 1 ); + aBuf.append( aLocaleDataItem.timeSeparator ); + ImplAdd2UNum( aBuf, rDuration.GetTime().GetMin() ); + if ( bSec ) + { + aBuf.append( aLocaleDataItem.timeSeparator ); + ImplAdd2UNum( aBuf, rDuration.GetTime().GetSec() ); + + if ( b100Sec ) + { + aBuf.append( aLocaleDataItem.time100SecSeparator ); + ImplAdd9UNum( aBuf, rDuration.GetTime().GetNanoSec() ); + } + } + + return aBuf.makeStringAndClear(); +} + +// --- simple number formatting --------------------------------------- + +static size_t ImplGetNumberStringLengthGuess( const css::i18n::LocaleDataItem2& rLocaleDataItem, sal_uInt16 nDecimals ) +{ + // approximately 3.2 bits per digit + const size_t nDig = ((sizeof(sal_Int64) * 8) / 3) + 1; + // digits, separators (pessimized for insane "every digit may be grouped"), leading zero, sign + size_t nGuess = ((nDecimals < nDig) ? + (((nDig - nDecimals) * rLocaleDataItem.thousandSeparator.getLength()) + nDig) : + nDecimals) + rLocaleDataItem.decimalSeparator.getLength() + 3; + return nGuess; +} + +OUString LocaleDataWrapper::getNum( sal_Int64 nNumber, sal_uInt16 nDecimals, + bool bUseThousandSep, bool bTrailingZeros ) const +{ + // check if digits and separators will fit into fixed buffer or allocate + size_t nGuess = ImplGetNumberStringLengthGuess( aLocaleDataItem, nDecimals ); + OUStringBuffer aBuf(int(nGuess + 16)); + + ImplAddFormatNum( aBuf, nNumber, nDecimals, + bUseThousandSep, bTrailingZeros ); + + return aBuf.makeStringAndClear(); +} + +OUString LocaleDataWrapper::getCurr( sal_Int64 nNumber, sal_uInt16 nDecimals, + std::u16string_view rCurrencySymbol, bool bUseThousandSep ) const +{ + sal_Unicode cZeroChar = getCurrZeroChar(); + + // check if digits and separators will fit into fixed buffer or allocate + size_t nGuess = ImplGetNumberStringLengthGuess( aLocaleDataItem, nDecimals ); + OUStringBuffer aNumBuf(sal_Int32(nGuess + 16)); + + bool bNeg; + if ( nNumber < 0 ) + { + bNeg = true; + nNumber *= -1; + } + else + bNeg = false; + + // convert number + ImplAddFormatNum( aNumBuf, nNumber, nDecimals, + bUseThousandSep, true ); + const sal_Int32 nNumLen = aNumBuf.getLength(); + + // replace zeros with zero character + if ( (cZeroChar != '0') && nDecimals /* && IsNumTrailingZeros() */ ) + { + sal_uInt16 i; + bool bZero = true; + + sal_uInt16 nNumBufIndex = nNumLen-nDecimals; + i = 0; + do + { + if ( aNumBuf[nNumBufIndex] != '0' ) + { + bZero = false; + break; + } + + nNumBufIndex++; + i++; + } + while ( i < nDecimals ); + + if ( bZero ) + { + nNumBufIndex = nNumLen-nDecimals; + i = 0; + do + { + aNumBuf[nNumBufIndex] = cZeroChar; + nNumBufIndex++; + i++; + } + while ( i < nDecimals ); + } + } + + OUString aCur; + if ( !bNeg ) + { + switch( getCurrPositiveFormat() ) + { + case 0: + aCur = rCurrencySymbol + aNumBuf; + break; + case 1: + aCur = aNumBuf + rCurrencySymbol; + break; + case 2: + aCur = OUString::Concat(rCurrencySymbol) + " " + aNumBuf; + break; + case 3: + aCur = aNumBuf + " " + rCurrencySymbol; + break; + } + } + else + { + switch( getCurrNegativeFormat() ) + { + case 0: + aCur = OUString::Concat("(") + rCurrencySymbol + aNumBuf + ")"; + break; + case 1: + aCur = OUString::Concat("-") + rCurrencySymbol + aNumBuf; + break; + case 2: + aCur = OUString::Concat(rCurrencySymbol) + "-" + aNumBuf; + break; + case 3: + aCur = rCurrencySymbol + aNumBuf + "-"; + break; + case 4: + aCur = "(" + aNumBuf + rCurrencySymbol + ")"; + break; + case 5: + aCur = "-" + aNumBuf + rCurrencySymbol; + break; + case 6: + aCur = aNumBuf + "-" + rCurrencySymbol; + break; + case 7: + aCur = aNumBuf + rCurrencySymbol + "-"; + break; + case 8: + aCur = "-" + aNumBuf + " " + rCurrencySymbol; + break; + case 9: + aCur = OUString::Concat("-") + rCurrencySymbol + " " + aNumBuf; + break; + case 10: + aCur = aNumBuf + " " + rCurrencySymbol + "-"; + break; + case 11: + aCur = OUString::Concat(rCurrencySymbol) + " -" + aNumBuf; + break; + case 12: + aCur = OUString::Concat(rCurrencySymbol) + " " + aNumBuf + "-"; + break; + case 13: + aCur = aNumBuf + "- " + rCurrencySymbol; + break; + case 14: + aCur = OUString::Concat("(") + rCurrencySymbol + " " + aNumBuf + ")"; + break; + case 15: + aCur = "(" + aNumBuf + " " + rCurrencySymbol + ")"; + break; + } + } + + return aCur; +} + +// --- number parsing ------------------------------------------------- + +double LocaleDataWrapper::stringToDouble( std::u16string_view aString, bool bUseGroupSep, + rtl_math_ConversionStatus* pStatus, sal_Int32* pParseEnd ) const +{ + const sal_Unicode* pParseEndChar; + double fValue = stringToDouble(aString.data(), aString.data() + aString.size(), bUseGroupSep, pStatus, &pParseEndChar); + if (pParseEnd) + *pParseEnd = pParseEndChar - aString.data(); + return fValue; +} + +double LocaleDataWrapper::stringToDouble( const sal_Unicode* pBegin, const sal_Unicode* pEnd, bool bUseGroupSep, + rtl_math_ConversionStatus* pStatus, const sal_Unicode** ppParseEnd ) const +{ + const sal_Unicode cGroupSep = (bUseGroupSep ? aLocaleDataItem.thousandSeparator[0] : 0); + rtl_math_ConversionStatus eStatus = rtl_math_ConversionStatus_Ok; + const sal_Unicode* pParseEnd = nullptr; + double fValue = rtl_math_uStringToDouble( pBegin, pEnd, aLocaleDataItem.decimalSeparator[0], cGroupSep, &eStatus, &pParseEnd); + bool bTryAlt = (pParseEnd < pEnd && !aLocaleDataItem.decimalSeparatorAlternative.isEmpty() && + *pParseEnd == aLocaleDataItem.decimalSeparatorAlternative.toChar()); + // Try re-parsing with alternative if that was the reason to stop. + if (bTryAlt) + fValue = rtl_math_uStringToDouble( pBegin, pEnd, aLocaleDataItem.decimalSeparatorAlternative.toChar(), cGroupSep, &eStatus, &pParseEnd); + if (pStatus) + *pStatus = eStatus; + if (ppParseEnd) + *ppParseEnd = pParseEnd; + return fValue; +} + +// --- mixed ---------------------------------------------------------- + +LanguageTag LocaleDataWrapper::getLoadedLanguageTag() const +{ + LanguageCountryInfo aLCInfo = getLanguageCountryInfo(); + return LanguageTag( lang::Locale( aLCInfo.Language, aLCInfo.Country, aLCInfo.Variant )); +} + +OUString LocaleDataWrapper::appendLocaleInfo(std::u16string_view rDebugMsg) const +{ + LanguageTag aLoaded = getLoadedLanguageTag(); + return OUString::Concat(rDebugMsg) + "\n" + maLanguageTag.getBcp47() + " requested\n" + + aLoaded.getBcp47() + " loaded"; +} + +// static +void LocaleDataWrapper::outputCheckMessage( std::u16string_view rMsg ) +{ + outputCheckMessage(OUStringToOString(rMsg, RTL_TEXTENCODING_UTF8).getStr()); +} + +// static +void LocaleDataWrapper::outputCheckMessage( const char* pStr ) +{ + fprintf( stderr, "\n%s\n", pStr); + fflush( stderr); + SAL_WARN("unotools.i18n", pStr); +} + +// static +void LocaleDataWrapper::evaluateLocaleDataChecking() +{ + // Using the rtl_Instance template here wouldn't solve all threaded write + // accesses, since we want to assign the result to the static member + // variable and would need to dereference the pointer returned and assign + // the value unguarded. This is the same pattern manually coded. + sal_uInt8 nCheck = nLocaleDataChecking; + if (!nCheck) + { + ::osl::MutexGuard aGuard( ::osl::Mutex::getGlobalMutex()); + nCheck = nLocaleDataChecking; + if (!nCheck) + { +#ifdef DBG_UTIL + nCheck = 1; +#else + const char* pEnv = getenv( "OOO_ENABLE_LOCALE_DATA_CHECKS"); + if (pEnv && (pEnv[0] == 'Y' || pEnv[0] == 'y' || pEnv[0] == '1')) + nCheck = 1; + else + nCheck = 2; +#endif + OSL_DOUBLE_CHECKED_LOCKING_MEMORY_BARRIER(); + nLocaleDataChecking = nCheck; + } + } + else { + OSL_DOUBLE_CHECKED_LOCKING_MEMORY_BARRIER(); + } +} + +// --- XLocaleData3 ---------------------------------------------------------- + +css::uno::Sequence< css::i18n::Calendar2 > LocaleDataWrapper::getAllCalendars() const +{ + try + { + return xLD->getAllCalendars2( getMyLocale() ); + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "getAllCalendars" ); + } + return {}; +} + +// --- XLocaleData4 ---------------------------------------------------------- + +const css::uno::Sequence< OUString > & LocaleDataWrapper::getDateAcceptancePatterns() const +{ + return aDateAcceptancePatterns; +} + +// --- Override layer -------------------------------------------------------- + +void LocaleDataWrapper::loadDateAcceptancePatterns( + const std::vector<OUString> & rPatterns ) +{ + if (!aDateAcceptancePatterns.hasElements() || rPatterns.empty()) + { + try + { + aDateAcceptancePatterns = xLD->getDateAcceptancePatterns( maLanguageTag.getLocale() ); + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "setDateAcceptancePatterns" ); + } + if (rPatterns.empty()) + return; // just a reset + if (!aDateAcceptancePatterns.hasElements()) + { + aDateAcceptancePatterns = comphelper::containerToSequence(rPatterns); + return; + } + } + + // Earlier versions checked for presence of the full date pattern with + // aDateAcceptancePatterns[0] == rPatterns[0] and prepended that if not. + // This lead to confusion if the patterns were intentionally specified + // without, giving entirely a different DMY order, see tdf#150288. + // Not checking this and accepting the given patterns as is may result in + // the user shooting themself in the foot, but we can't have both. + aDateAcceptancePatterns = comphelper::containerToSequence(rPatterns); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/i18n/nativenumberwrapper.cxx b/unotools/source/i18n/nativenumberwrapper.cxx new file mode 100644 index 0000000000..882005ebf7 --- /dev/null +++ b/unotools/source/i18n/nativenumberwrapper.cxx @@ -0,0 +1,110 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <unotools/nativenumberwrapper.hxx> +#include <com/sun/star/i18n/NativeNumberSupplier2.hpp> +#include <comphelper/diagnose_ex.hxx> + +using namespace ::com::sun::star; + +NativeNumberWrapper::NativeNumberWrapper( + const uno::Reference< uno::XComponentContext > & rxContext + ) +{ + xNNS = i18n::NativeNumberSupplier2::create(rxContext); +} + +NativeNumberWrapper::~NativeNumberWrapper() +{ +} + +OUString +NativeNumberWrapper::getNativeNumberString( + const OUString& rNumberString, + const css::lang::Locale& rLocale, + sal_Int16 nNativeNumberMode) const +{ + try + { + if ( xNNS.is() ) + return xNNS->getNativeNumberString(rNumberString, rLocale, nNativeNumberMode); + } + catch ( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("unotools.i18n", "" ); + } + return OUString(); +} + +OUString +NativeNumberWrapper::getNativeNumberStringParams( + const OUString& rNumberString, + const css::lang::Locale& rLocale, + sal_Int16 nNativeNumberMode, + const OUString& rNativeNumberParams) const +{ + try + { + if ( xNNS.is() ) + return xNNS->getNativeNumberStringParams(rNumberString, rLocale, nNativeNumberMode, + rNativeNumberParams); + } + catch ( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("unotools.i18n", "" ); + } + return OUString(); +} + +i18n::NativeNumberXmlAttributes +NativeNumberWrapper::convertToXmlAttributes( + const css::lang::Locale& rLocale, + sal_Int16 nNativeNumberMode ) const +{ + try + { + if ( xNNS.is() ) + return xNNS->convertToXmlAttributes( rLocale, nNativeNumberMode ); + } + catch ( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("unotools.i18n", "" ); + } + return i18n::NativeNumberXmlAttributes(); +} + +sal_Int16 +NativeNumberWrapper::convertFromXmlAttributes( + const i18n::NativeNumberXmlAttributes& rAttr ) const +{ + try + { + if ( xNNS.is() ) + return xNNS->convertFromXmlAttributes( rAttr ); + } + catch ( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("unotools.i18n", "" ); + } + return 0; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/i18n/resmgr.cxx b/unotools/source/i18n/resmgr.cxx new file mode 100644 index 0000000000..2cf013d161 --- /dev/null +++ b/unotools/source/i18n/resmgr.cxx @@ -0,0 +1,321 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <boost/version.hpp> +#if BOOST_VERSION < 106700 +// Needed when #include <boost/locale.hpp> below includes Boost 1.65.1 +// workdir/UnpackedTarball/boost/boost/locale/format.hpp using "std::auto_ptr<data> d;", but must +// come very early here in case <memory> is already (indirectly) included earlier: +#include <config_libcxx.h> +#if HAVE_LIBCPP +#define _LIBCPP_ENABLE_CXX17_REMOVED_AUTO_PTR +#elif defined _MSC_VER +#define _HAS_AUTO_PTR_ETC 1 +#endif +#endif + +#include <sal/config.h> + +#include <cassert> + +#include <string.h> +#include <stdio.h> +#if defined UNX && !defined MACOSX && !defined IOS && !defined ANDROID && !defined EMSCRIPTEN +# include <libintl.h> +#endif + +#include <comphelper/lok.hxx> +#include <unotools/resmgr.hxx> +#include <osl/thread.h> +#include <osl/file.hxx> +#include <rtl/crc.h> +#include <rtl/bootstrap.hxx> +#include <i18nlangtag/languagetag.hxx> + +#include <boost/locale.hpp> +#include <boost/locale/gnu_gettext.hpp> + +#include <unordered_map> + +#ifdef ANDROID +#include <osl/detail/android-bootstrap.h> +#endif + +#ifdef EMSCRIPTEN +#include <osl/detail/emscripten-bootstrap.h> +#endif + +#if defined(_WIN32) && defined(DBG_UTIL) +#include <o3tl/char16_t2wchar_t.hxx> +#include <prewin.h> +#include <crtdbg.h> +#include <postwin.h> +#endif + +namespace +{ + OUString createFromUtf8(const char* data, size_t size) + { + OUString aTarget; + bool bSuccess = rtl_convertStringToUString(&aTarget.pData, + data, + size, + RTL_TEXTENCODING_UTF8, + RTL_TEXTTOUNICODE_FLAGS_UNDEFINED_ERROR|RTL_TEXTTOUNICODE_FLAGS_MBUNDEFINED_ERROR|RTL_TEXTTOUNICODE_FLAGS_INVALID_ERROR); + (void) bSuccess; + assert(bSuccess); + return aTarget; + } + + OString genKeyId(const OString& rGenerator) + { + sal_uInt32 nCRC = rtl_crc32(0, rGenerator.getStr(), rGenerator.getLength()); + // Use simple ASCII characters, exclude I, l, 1 and O, 0 to avoid confusing IDs + static const char sSymbols[] = + "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789"; + char sKeyId[6]; + for (short nKeyInd = 0; nKeyInd < 5; ++nKeyInd) + { + sKeyId[nKeyInd] = sSymbols[(nCRC & 63) % strlen(sSymbols)]; + nCRC >>= 6; + } + sKeyId[5] = '\0'; + return sKeyId; + } +} + +#if defined(_WIN32) && defined(DBG_UTIL) +static int IgnoringCrtReportHook(int reportType, wchar_t *message, int * /* returnValue */) +{ + OUString sType; + if (reportType == _CRT_WARN) + sType = "WARN"; + else if (reportType == _CRT_ERROR) + sType = "ERROR"; + else if (reportType == _CRT_ASSERT) + sType = "ASSERT"; + else + sType = "?(" + OUString::number(reportType) + ")"; + + SAL_WARN("unotools.i18n", "CRT Report Hook: " << sType << ": " << OUString(o3tl::toU(message))); + + return TRUE; +} +#endif + + +namespace Translate +{ + std::locale Create(std::string_view aPrefixName, const LanguageTag& rLocale) + { + static std::unordered_map<OString, std::locale> aCache; + OString sIdentifier = rLocale.getGlibcLocaleString(u".UTF-8").toUtf8(); + OString sUnique = sIdentifier + aPrefixName; + auto aFind = aCache.find(sUnique); + if (aFind != aCache.end()) + return aFind->second; + boost::locale::generator gen; +#if BOOST_VERSION < 108100 + gen.characters(boost::locale::char_facet); + gen.categories(boost::locale::message_facet | boost::locale::information_facet); +#else + gen.characters(boost::locale::char_facet_t::char_f); + gen.categories(boost::locale::category_t::message | boost::locale::category_t::information); +#endif +#if defined(ANDROID) || defined(EMSCRIPTEN) + OString sPath(OString(lo_get_app_data_dir()) + "/program/resource"); +#else + OUString uri("$BRAND_BASE_DIR/$BRAND_SHARE_RESOURCE_SUBDIR/"); + rtl::Bootstrap::expandMacros(uri); + OUString path; + osl::File::getSystemPathFromFileURL(uri, path); +#if defined _WIN32 + // add_messages_path is documented to treat path string in the *created* locale's encoding + // on Windows; creating an UTF-8 encoding, we're lucky to have Unicode path support here. + constexpr rtl_TextEncoding eEncoding = RTL_TEXTENCODING_UTF8; +#else + const rtl_TextEncoding eEncoding = osl_getThreadTextEncoding(); +#endif + OString sPath(OUStringToOString(path, eEncoding)); +#endif + gen.add_messages_path(std::string(sPath)); +#if defined UNX && !defined MACOSX && !defined IOS && !defined ANDROID && !defined EMSCRIPTEN + // allow gettext to find these .mo files e.g. so gtk dialogs can use them + bindtextdomain(aPrefixName.data(), sPath.getStr()); + // tdf#131069 gtk, and anything sane, always wants utf-8 strings as output + bind_textdomain_codeset(aPrefixName.data(), "UTF-8"); +#endif + gen.add_messages_domain(aPrefixName.data()); + +#if defined(_WIN32) && defined(DBG_UTIL) + // With a newer C++ debug runtime (in an --enable-dbgutil build), passing an invalid locale + // name causes an attempt to display an error dialog. Which does not even show up, at least + // for me, but instead the process (gengal, at least) just hangs. Which is far from ideal. + + // Passing a POSIX-style locale name to the std::locale constructor on Windows is a bit odd, + // but apparently in the normal C++ runtime it "just" causes an exception to be thrown, that + // boost catches (see the loadable(std::string name) in boost's + // libs\locale\src\std\std_backend.cpp), and then instead uses the Windows style locale name + // it knows how to construct. (Why does it even try the POSIX style name I can't + // understand.) + + // Actually it isn't just the locale name part "en_US" of a locale like "en_US.UTF-8" that + // is problematic, but also the encoding part, "UTF-8". The Microsoft C/C++ library does not + // support UTF-8 locales. The error message that our own report hook catches says: + // "f:\dd\vctools\crt\crtw32\stdcpp\xmbtowc.c(89) : Assertion failed: ploc->_Mbcurmax == 1 + // || ploc->_Mbcurmax == 2". Clearly in a UTF-8 locale (perhaps one that boost internally + // constructs?) the maximum bytes per character will be more than 2. + + // With a debug C++ runtime, we need to avoid the error dialog, and just ignore the error. + + struct CrtSetReportHook + { + int mnCrtSetReportHookSucceeded; + + CrtSetReportHook() + { + mnCrtSetReportHookSucceeded = _CrtSetReportHookW2(_CRT_RPTHOOK_INSTALL, IgnoringCrtReportHook); + } + + ~CrtSetReportHook() + { + if (mnCrtSetReportHookSucceeded >= 0) + _CrtSetReportHookW2(_CRT_RPTHOOK_REMOVE, IgnoringCrtReportHook); + } + } aHook; + +#endif + + std::locale aRet(gen(std::string(sIdentifier))); + + aCache[sUnique] = aRet; + return aRet; + } + + OUString get(TranslateId sContextAndId, const std::locale &loc) + { + assert(!strchr(sContextAndId.getId(), '\004') && "should be using nget, not get"); + + //if it's a key id locale, generate it here + if (std::use_facet<boost::locale::info>(loc).language() == "qtz") + { + OString sKeyId(genKeyId(OString::Concat(sContextAndId.mpContext) + "|" + std::string_view(sContextAndId.getId()))); + return OUString::fromUtf8(sKeyId) + u"\u2016" + createFromUtf8(sContextAndId.getId(), strlen(sContextAndId.getId())); + } + + //otherwise translate it + const std::string ret = boost::locale::pgettext(sContextAndId.mpContext, sContextAndId.getId(), loc); + OUString result(ExpandVariables(createFromUtf8(ret.data(), ret.size()))); + + if (comphelper::LibreOfficeKit::isActive()) + { + // If it is de-CH, change sharp s to double s. + if (std::use_facet<boost::locale::info>(loc).country() == "CH" && + std::use_facet<boost::locale::info>(loc).language() == "de") + result = result.replaceAll(OUString::fromUtf8("\xC3\x9F"), "ss"); + } + return result; + } + + OUString nget(TranslateNId aContextSingularPlural, int n, const std::locale &loc) + { + //if it's a key id locale, generate it here + if (std::use_facet<boost::locale::info>(loc).language() == "qtz") + { + OString sKeyId(genKeyId(OString::Concat(aContextSingularPlural.mpContext) + "|" + aContextSingularPlural.mpSingular)); + const char* pForm = n == 0 ? aContextSingularPlural.mpSingular : aContextSingularPlural.mpPlural; + return OUString::fromUtf8(sKeyId) + u"\u2016" + createFromUtf8(pForm, strlen(pForm)); + } + + //otherwise translate it + const std::string ret = boost::locale::npgettext(aContextSingularPlural.mpContext, aContextSingularPlural.mpSingular, aContextSingularPlural.mpPlural, n, loc); + OUString result(ExpandVariables(createFromUtf8(ret.data(), ret.size()))); + + if (comphelper::LibreOfficeKit::isActive()) + { + if (std::use_facet<boost::locale::info>(loc).country() == "CH" && + std::use_facet<boost::locale::info>(loc).language() == "de") + result = result.replaceAll(OUString::fromUtf8("\xC3\x9F"), "ss"); + } + return result; + } + + static ResHookProc pImplResHookProc = nullptr; + + OUString ExpandVariables(const OUString& rString) + { + if (pImplResHookProc) + return pImplResHookProc(rString); + return rString; + } + + void SetReadStringHook( ResHookProc pProc ) + { + pImplResHookProc = pProc; + } + + ResHookProc GetReadStringHook() + { + return pImplResHookProc; + } +} + +bool TranslateId::operator==(const TranslateId& other) const +{ + if (mpContext == nullptr || other.mpContext == nullptr) + { + if (mpContext != other.mpContext) + return false; + } + else if (strcmp(mpContext, other.mpContext) != 0) + return false; + + if (mpId == nullptr || other.mpId == nullptr) + { + return mpId == other.mpId; + } + return strcmp(getId(),other.getId()) == 0; +} + +bool TranslateNId::operator==(const TranslateNId& other) const +{ + if (mpContext == nullptr || other.mpContext == nullptr) + { + if (mpContext != other.mpContext) + return false; + } + else if (strcmp(mpContext, other.mpContext) != 0) + return false; + + if (mpSingular == nullptr || other.mpSingular == nullptr) + { + if (mpSingular != other.mpSingular) + return false; + } + else if (strcmp(mpSingular, other.mpSingular) != 0) + return false; + + if (mpPlural == nullptr || other.mpPlural == nullptr) + { + return mpPlural == other.mpPlural; + } + return strcmp(mpPlural,other.mpPlural) == 0; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/i18n/textsearch.cxx b/unotools/source/i18n/textsearch.cxx new file mode 100644 index 0000000000..f61aeaf1a2 --- /dev/null +++ b/unotools/source/i18n/textsearch.cxx @@ -0,0 +1,357 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <cstdlib> +#include <string_view> + +#include <i18nlangtag/languagetag.hxx> +#include <i18nutil/searchopt.hxx> +#include <i18nutil/transliteration.hxx> +#include <com/sun/star/util/TextSearch2.hpp> +#include <com/sun/star/util/SearchAlgorithms2.hpp> +#include <com/sun/star/util/SearchFlags.hpp> +#include <unotools/charclass.hxx> +#include <comphelper/processfactory.hxx> +#include <unotools/textsearch.hxx> +#include <rtl/ustrbuf.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <mutex> + +using namespace ::com::sun::star::util; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::lang; + +namespace utl +{ + +SearchParam::SearchParam( const OUString &rText, + SearchType eType, + bool bCaseSensitive, + sal_uInt32 cWildEscChar, + bool bWildMatchSel ) +{ + sSrchStr = rText; + m_eSrchType = eType; + + m_cWildEscChar = cWildEscChar; + + m_bCaseSense = bCaseSensitive; + m_bWildMatchSel = bWildMatchSel; +} + +SearchParam::SearchParam( const SearchParam& rParam ) +{ + sSrchStr = rParam.sSrchStr; + m_eSrchType = rParam.m_eSrchType; + + m_cWildEscChar = rParam.m_cWildEscChar; + + m_bCaseSense = rParam.m_bCaseSense; + m_bWildMatchSel = rParam.m_bWildMatchSel; +} + +SearchParam::~SearchParam() {} + +static bool lcl_Equals( const i18nutil::SearchOptions2& rSO1, const i18nutil::SearchOptions2& rSO2 ) +{ + return + rSO1.AlgorithmType2 == rSO2.AlgorithmType2 && + rSO1.WildcardEscapeCharacter == rSO2.WildcardEscapeCharacter && + rSO1.searchFlag == rSO2.searchFlag && + rSO1.searchString == rSO2.searchString && + rSO1.replaceString == rSO2.replaceString && + rSO1.changedChars == rSO2.changedChars && + rSO1.deletedChars == rSO2.deletedChars && + rSO1.insertedChars == rSO2.insertedChars && + rSO1.Locale.Language == rSO2.Locale.Language && + rSO1.Locale.Country == rSO2.Locale.Country && + rSO1.Locale.Variant == rSO2.Locale.Variant && + rSO1.transliterateFlags == rSO2.transliterateFlags; +} + +namespace +{ + struct CachedTextSearch + { + std::mutex mutex; + i18nutil::SearchOptions2 Options; + css::uno::Reference< css::util::XTextSearch2 > xTextSearch; + }; +} + +Reference<XTextSearch2> TextSearch::getXTextSearch( const i18nutil::SearchOptions2& rPara ) +{ + static CachedTextSearch theCachedTextSearch; + + std::scoped_lock aGuard(theCachedTextSearch.mutex); + + if ( lcl_Equals(theCachedTextSearch.Options, rPara) ) + return theCachedTextSearch.xTextSearch; + + Reference< XComponentContext > xContext = ::comphelper::getProcessComponentContext(); + theCachedTextSearch.xTextSearch.set( ::TextSearch2::create(xContext) ); + theCachedTextSearch.xTextSearch->setOptions2( rPara.toUnoSearchOptions2() ); + theCachedTextSearch.Options = rPara; + + return theCachedTextSearch.xTextSearch; +} + +TextSearch::TextSearch(const SearchParam & rParam, LanguageType eLang ) +{ + if( LANGUAGE_NONE == eLang ) + eLang = LANGUAGE_SYSTEM; + css::lang::Locale aLocale( LanguageTag::convertToLocale( eLang ) ); + + Init( rParam, aLocale); +} + +TextSearch::TextSearch(const SearchParam & rParam, const CharClass& rCClass ) +{ + Init( rParam, rCClass.getLanguageTag().getLocale() ); +} + +TextSearch::TextSearch( const i18nutil::SearchOptions2& rPara ) +{ + xTextSearch = getXTextSearch( rPara ); +} + +void TextSearch::Init( const SearchParam & rParam, + const css::lang::Locale& rLocale ) +{ + // convert SearchParam to the UNO SearchOptions2 + i18nutil::SearchOptions2 aSOpt; + + switch( rParam.GetSrchType() ) + { + case SearchParam::SearchType::Wildcard: + aSOpt.AlgorithmType2 = SearchAlgorithms2::WILDCARD; + aSOpt.WildcardEscapeCharacter = rParam.GetWildEscChar(); + if (rParam.IsWildMatchSel()) + aSOpt.searchFlag |= SearchFlags::WILD_MATCH_SELECTION; + break; + + case SearchParam::SearchType::Regexp: + aSOpt.AlgorithmType2 = SearchAlgorithms2::REGEXP; + break; + + case SearchParam::SearchType::Normal: + aSOpt.AlgorithmType2 = SearchAlgorithms2::ABSOLUTE; + break; + + default: + for (;;) std::abort(); + } + aSOpt.searchString = rParam.GetSrchStr(); + aSOpt.replaceString = ""; + aSOpt.Locale = rLocale; + aSOpt.transliterateFlags = TransliterationFlags::NONE; + if( !rParam.IsCaseSensitive() ) + { + aSOpt.searchFlag |= SearchFlags::ALL_IGNORE_CASE; + aSOpt.transliterateFlags |= TransliterationFlags::IGNORE_CASE; + } + + xTextSearch = getXTextSearch( aSOpt ); +} + +void TextSearch::SetLocale( const i18nutil::SearchOptions2& rOptions, + const css::lang::Locale& rLocale ) +{ + i18nutil::SearchOptions2 aSOpt( rOptions ); + aSOpt.Locale = rLocale; + + xTextSearch = getXTextSearch( aSOpt ); +} + +TextSearch::~TextSearch() +{ +} + +/* + * General search methods. These methods will call the respective + * methods, such as ordinary string searching or regular expression + * matching, using the method pointer. + */ +bool TextSearch::SearchForward( const OUString &rStr, + sal_Int32* pStart, sal_Int32* pEnd, + css::util::SearchResult* pRes) +{ + bool bRet = false; + try + { + if( xTextSearch.is() ) + { + SearchResult aRet( xTextSearch->searchForward( rStr, *pStart, *pEnd )); + if( aRet.subRegExpressions > 0 ) + { + bRet = true; + // the XTextsearch returns in startOffset the higher position + // and the endposition is always exclusive. + // The caller of this function will have in startPos the + // lower pos. and end + *pStart = aRet.startOffset[ 0 ]; + *pEnd = aRet.endOffset[ 0 ]; + if( pRes ) + *pRes = aRet; + } + } + } + catch ( Exception& ) + { + TOOLS_WARN_EXCEPTION("unotools.i18n", "" ); + } + return bRet; +} + +bool TextSearch::searchForward( const OUString &rStr ) +{ + sal_Int32 pStart = 0; + sal_Int32 pEnd = rStr.getLength(); + + bool bResult = SearchForward(rStr, &pStart, &pEnd); + + return bResult; +} + +bool TextSearch::SearchBackward( const OUString & rStr, sal_Int32* pStart, + sal_Int32* pEnd, SearchResult* pRes ) +{ + bool bRet = false; + try + { + if( xTextSearch.is() ) + { + SearchResult aRet( xTextSearch->searchBackward( rStr, *pStart, *pEnd )); + if( aRet.subRegExpressions ) + { + bRet = true; + // the XTextsearch returns in startOffset the higher position + // and the endposition is always exclusive. + // The caller of this function will have in startPos the + // lower pos. and end + *pEnd = aRet.startOffset[ 0 ]; + *pStart = aRet.endOffset[ 0 ]; + if( pRes ) + *pRes = aRet; + } + } + } + catch ( Exception& ) + { + TOOLS_WARN_EXCEPTION("unotools.i18n", "" ); + } + return bRet; +} + +void TextSearch::ReplaceBackReferences( OUString& rReplaceStr, std::u16string_view rStr, const SearchResult& rResult ) const +{ + if( rResult.subRegExpressions <= 0 ) + return; + + sal_Unicode sFndChar; + sal_Int32 i; + OUStringBuffer sBuff(rReplaceStr.getLength()*4); + for(i = 0; i < rReplaceStr.getLength(); i++) + { + if( rReplaceStr[i] == '&') + { + sal_Int32 nStart = rResult.startOffset[0]; + sal_Int32 nLength = rResult.endOffset[0] - rResult.startOffset[0]; + sBuff.append(rStr.substr(nStart, nLength)); + } + else if((i < rReplaceStr.getLength() - 1) && rReplaceStr[i] == '$') + { + sFndChar = rReplaceStr[ i + 1 ]; + switch(sFndChar) + { // placeholder for a backward reference? + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + int j = sFndChar - '0'; // index + if(j < rResult.subRegExpressions) + { + sal_Int32 nSttReg = rResult.startOffset[j]; + sal_Int32 nRegLen = rResult.endOffset[j]; + if (nSttReg < 0 || nRegLen < 0) // A "not found" optional capture + { + nSttReg = nRegLen = 0; // Copy empty string + } + else if (nRegLen >= nSttReg) + { + nRegLen = nRegLen - nSttReg; + } + else + { + nRegLen = nSttReg - nRegLen; + nSttReg = rResult.endOffset[j]; + } + // Copy reference from found string + sBuff.append(rStr.substr(nSttReg, nRegLen)); + } + i += 1; + } + break; + default: + sBuff.append(OUStringChar(rReplaceStr[i]) + OUStringChar(rReplaceStr[i+1])); + i += 1; + break; + } + } + else if((i < rReplaceStr.getLength() - 1) && rReplaceStr[i] == '\\') + { + sFndChar = rReplaceStr[ i+1 ]; + switch(sFndChar) + { + case '\\': + case '&': + case '$': + sBuff.append(sFndChar); + i+=1; + break; + case 't': + sBuff.append('\t'); + i += 1; + break; + default: + sBuff.append(OUStringChar(rReplaceStr[i]) + OUStringChar(rReplaceStr[i+1])); + i += 1; + break; + } + } + else + { + sBuff.append(rReplaceStr[i]); + } + } + rReplaceStr = sBuff.makeStringAndClear(); +} + +} // namespace utl + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/i18n/transliterationwrapper.cxx b/unotools/source/i18n/transliterationwrapper.cxx new file mode 100644 index 0000000000..a8e4baedf9 --- /dev/null +++ b/unotools/source/i18n/transliterationwrapper.cxx @@ -0,0 +1,231 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <unotools/transliterationwrapper.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <i18nutil/transliteration.hxx> +#include <comphelper/diagnose_ex.hxx> + +#include <com/sun/star/i18n/Transliteration.hpp> + +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::i18n; +using namespace ::com::sun::star::uno; +using namespace ::utl; + +TransliterationWrapper::TransliterationWrapper( + const Reference< XComponentContext > & rxContext, + TransliterationFlags nTyp ) + : xTrans( Transliteration::create(rxContext) ), + aLanguageTag( LANGUAGE_SYSTEM ), nType( nTyp ), bFirstCall( true ) +{ +} + +TransliterationWrapper::~TransliterationWrapper() +{ +} + +OUString TransliterationWrapper::transliterate(const OUString& rStr, LanguageType nLang, + sal_Int32 nStart, sal_Int32 nLen, + Sequence <sal_Int32>* pOffset ) +{ + OUString sRet; + if( xTrans.is() ) + { + try + { + loadModuleIfNeeded( nLang ); + + if ( pOffset ) + sRet = xTrans->transliterate( rStr, nStart, nLen, *pOffset ); + else + sRet = xTrans->transliterateString2String( rStr, nStart, nLen); + } + catch( Exception& ) + { + TOOLS_WARN_EXCEPTION("unotools.i18n", "" ); + } + } + return sRet; +} + +OUString TransliterationWrapper::transliterate( const OUString& rStr, + sal_Int32 nStart, sal_Int32 nLen ) const +{ + OUString sRet( rStr ); + if( xTrans.is() ) + { + try + { + sRet = xTrans->transliterateString2String( rStr, nStart, nLen); + } + catch( Exception& ) + { + TOOLS_WARN_EXCEPTION("unotools.i18n", "" ); + } + } + return sRet; +} + +bool TransliterationWrapper::needLanguageForTheMode() const +{ + return TransliterationFlags::UPPERCASE_LOWERCASE == nType || + TransliterationFlags::LOWERCASE_UPPERCASE == nType || + TransliterationFlags::IGNORE_CASE == nType || + TransliterationFlags::SENTENCE_CASE == nType || + TransliterationFlags::TITLE_CASE == nType || + TransliterationFlags::TOGGLE_CASE == nType; +} + +void TransliterationWrapper::setLanguageLocaleImpl( LanguageType nLang ) +{ + if( LANGUAGE_NONE == nLang ) + nLang = LANGUAGE_SYSTEM; + aLanguageTag.reset( nLang); +} + +void TransliterationWrapper::loadModuleIfNeeded( LanguageType nLang ) +{ + bool bLoad = bFirstCall; + bFirstCall = false; + + if( nType == TransliterationFlags::SENTENCE_CASE ) + { + if( bLoad ) + loadModuleByImplName("SENTENCE_CASE", nLang); + } + else if( nType == TransliterationFlags::TITLE_CASE ) + { + if( bLoad ) + loadModuleByImplName("TITLE_CASE", nLang); + } + else if( nType == TransliterationFlags::TOGGLE_CASE ) + { + if( bLoad ) + loadModuleByImplName("TOGGLE_CASE", nLang); + } + else + { + if( aLanguageTag.getLanguageType() != nLang ) + { + setLanguageLocaleImpl( nLang ); + if( !bLoad ) + bLoad = needLanguageForTheMode(); + } + if( bLoad ) + loadModuleImpl(); + } +} + +void TransliterationWrapper::loadModuleImpl() const +{ + if ( bFirstCall ) + const_cast<TransliterationWrapper*>(this)->setLanguageLocaleImpl( LANGUAGE_SYSTEM ); + + try + { + if ( xTrans.is() ) + xTrans->loadModule( static_cast<TransliterationModules>(nType), aLanguageTag.getLocale() ); + } + catch ( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "loadModuleImpl" ); + } + + bFirstCall = false; +} + +void TransliterationWrapper::loadModuleByImplName(const OUString& rModuleName, + LanguageType nLang ) +{ + try + { + setLanguageLocaleImpl( nLang ); + css::lang::Locale aLocale( aLanguageTag.getLocale()); + // Reset LanguageTag, so the next call to loadModuleIfNeeded() forces + // new settings. + aLanguageTag.reset( LANGUAGE_DONTKNOW); + if ( xTrans.is() ) + xTrans->loadModuleByImplName( rModuleName, aLocale ); + } + catch ( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "loadModuleByImplName" ); + } + + bFirstCall = false; +} + +bool TransliterationWrapper::equals( + const OUString& rStr1, sal_Int32 nPos1, sal_Int32 nCount1, sal_Int32& nMatch1, + const OUString& rStr2, sal_Int32 nPos2, sal_Int32 nCount2, sal_Int32& nMatch2 ) const +{ + try + { + if( bFirstCall ) + loadModuleImpl(); + if ( xTrans.is() ) + return xTrans->equals( rStr1, nPos1, nCount1, nMatch1, rStr2, nPos2, nCount2, nMatch2 ); + } + catch ( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "equals" ); + } + return false; +} + +sal_Int32 TransliterationWrapper::compareString( const OUString& rStr1, const OUString& rStr2 ) const +{ + try + { + if( bFirstCall ) + loadModuleImpl(); + if ( xTrans.is() ) + return xTrans->compareString( rStr1, rStr2 ); + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "compareString" ); + } + return 0; +} + +// --- helpers -------------------------------------------------------- + +bool TransliterationWrapper::isEqual( const OUString& rStr1, const OUString& rStr2 ) const +{ + sal_Int32 nMatch1(0), nMatch2(0); + bool bMatch = equals( + rStr1, 0, rStr1.getLength(), nMatch1, + rStr2, 0, rStr2.getLength(), nMatch2 ); + return bMatch; +} + +bool TransliterationWrapper::isMatch( const OUString& rStr1, const OUString& rStr2 ) const +{ + sal_Int32 nMatch1(0), nMatch2(0); + equals( + rStr1, 0, rStr1.getLength(), nMatch1, + rStr2, 0, rStr2.getLength(), nMatch2 ); + return (nMatch1 <= nMatch2) && (nMatch1 == rStr1.getLength()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/misc/ServiceDocumenter.cxx b/unotools/source/misc/ServiceDocumenter.cxx new file mode 100644 index 0000000000..0bf1ce9fa5 --- /dev/null +++ b/unotools/source/misc/ServiceDocumenter.cxx @@ -0,0 +1,89 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +#include "ServiceDocumenter.hxx" +#include <com/sun/star/system/SystemShellExecuteFlags.hpp> +#include <com/sun/star/system/XSystemShellExecute.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <cppuhelper/supportsservice.hxx> + +using namespace com::sun::star; +using uno::Reference; +using lang::XServiceInfo; +using lang::XTypeProvider; + +void unotools::misc::ServiceDocumenter::showCoreDocs(const Reference<XServiceInfo>& xService) +{ + if(!xService.is()) + return; + auto xMSF(m_xContext->getServiceManager()); + Reference<system::XSystemShellExecute> xShell(xMSF->createInstanceWithContext("com.sun.star.system.SystemShellExecute", m_xContext), uno::UNO_QUERY); + xShell->execute( + m_sCoreBaseUrl + xService->getImplementationName() + ".html", "", + css::system::SystemShellExecuteFlags::URIS_ONLY); +} + +void unotools::misc::ServiceDocumenter::showInterfaceDocs(const Reference<XTypeProvider>& xTypeProvider) +{ + if(!xTypeProvider.is()) + return; + auto xMSF(m_xContext->getServiceManager()); + Reference<system::XSystemShellExecute> xShell(xMSF->createInstanceWithContext("com.sun.star.system.SystemShellExecute", m_xContext), uno::UNO_QUERY); + const css::uno::Sequence<css::uno::Type> aTypes = xTypeProvider->getTypes(); + for(const auto& aType : aTypes) + { + auto sUrl = aType.getTypeName(); + sal_Int32 nIdx = 0; + while(nIdx != -1) + sUrl = sUrl.replaceFirst(".", "_1_1", &nIdx); + xShell->execute( + m_sServiceBaseUrl + "/interface" + sUrl + ".html", "", + css::system::SystemShellExecuteFlags::URIS_ONLY); + } +} + +void unotools::misc::ServiceDocumenter::showServiceDocs(const Reference<XServiceInfo>& xService) +{ + if(!xService.is()) + return; + auto xMSF(m_xContext->getServiceManager()); + Reference<system::XSystemShellExecute> xShell(xMSF->createInstanceWithContext("com.sun.star.system.SystemShellExecute", m_xContext), uno::UNO_QUERY); + const css::uno::Sequence<OUString> aServiceNames = xService->getSupportedServiceNames(); + for(const auto& sService : aServiceNames) + { + auto sUrl = sService; + sal_Int32 nIdx = 0; + while(nIdx != -1) + sUrl = sUrl.replaceFirst(".", "_1_1", &nIdx); + xShell->execute( + m_sServiceBaseUrl + "/service" + sUrl + ".html", "", + css::system::SystemShellExecuteFlags::URIS_ONLY); + } +} + +// XServiceInfo +sal_Bool unotools::misc::ServiceDocumenter::supportsService(const OUString& sServiceName) +{ + return cppu::supportsService(this, sServiceName); +} +OUString unotools::misc::ServiceDocumenter::getImplementationName() +{ + return "com.sun.star.comp.unotools.misc.ServiceDocumenter"; +} +css::uno::Sequence< OUString > unotools::misc::ServiceDocumenter::getSupportedServiceNames() +{ + return { "com.sun.star.script.ServiceDocumenter" }; +} + + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +unotools_ServiceDocument_get_implementation( + css::uno::XComponentContext* context , css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new unotools::misc::ServiceDocumenter(context)); +} diff --git a/unotools/source/misc/ServiceDocumenter.hxx b/unotools/source/misc/ServiceDocumenter.hxx new file mode 100644 index 0000000000..0c94b5ac7e --- /dev/null +++ b/unotools/source/misc/ServiceDocumenter.hxx @@ -0,0 +1,57 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +#pragma once + +#include <cppuhelper/implbase.hxx> +#include <com/sun/star/script/XServiceDocumenter.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <utility> + +namespace com::sun::star::uno { class XComponentContext; } + +namespace unotools::misc { + +class ServiceDocumenter : public ::cppu::WeakImplHelper< + css::script::XServiceDocumenter, css::lang::XServiceInfo> +{ + public: + ServiceDocumenter(css::uno::Reference< css::uno::XComponentContext> xContext) + : m_xContext(std::move(xContext)) + , m_sCoreBaseUrl("http://example.com") + , m_sServiceBaseUrl("https://api.libreoffice.org/docs/idl/ref") + {}; + + // XServiceInfo + virtual sal_Bool SAL_CALL supportsService(const OUString& sServiceName) override; + virtual OUString SAL_CALL getImplementationName() override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override; + + // XServiceDocumenter + virtual OUString SAL_CALL getCoreBaseUrl() override + { return m_sCoreBaseUrl; }; + virtual void SAL_CALL setCoreBaseUrl( const OUString& sCoreBaseUrl ) override + { m_sCoreBaseUrl = sCoreBaseUrl; }; + virtual OUString SAL_CALL getServiceBaseUrl() override + { return m_sServiceBaseUrl; }; + virtual void SAL_CALL setServiceBaseUrl( const OUString& sServiceBaseUrl ) override + { m_sServiceBaseUrl = sServiceBaseUrl; }; + virtual void SAL_CALL showServiceDocs( const ::css::uno::Reference< ::css::lang::XServiceInfo >& xService) override; + virtual void SAL_CALL showInterfaceDocs( const ::css::uno::Reference< ::css::lang::XTypeProvider >& xTypeProvider ) override; + virtual void SAL_CALL showCoreDocs( const ::css::uno::Reference< ::css::lang::XServiceInfo >& xService) override; + protected: + virtual ~ServiceDocumenter() override + {}; + private: + css::uno::Reference< css::uno::XComponentContext> m_xContext; + OUString m_sCoreBaseUrl; + OUString m_sServiceBaseUrl; +}; + +} +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/misc/ZipPackageHelper.cxx b/unotools/source/misc/ZipPackageHelper.cxx new file mode 100644 index 0000000000..8bbd751318 --- /dev/null +++ b/unotools/source/misc/ZipPackageHelper.cxx @@ -0,0 +1,173 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <com/sun/star/io/XActiveDataSink.hpp> +#include <com/sun/star/beans/NamedValue.hpp> +#include <com/sun/star/container/XNamed.hpp> +#include <com/sun/star/container/XChild.hpp> +#include <com/sun/star/container/XNameContainer.hpp> +#include <com/sun/star/util/XChangesBatch.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/container/XHierarchicalNameAccess.hpp> +#include <com/sun/star/lang/XSingleServiceFactory.hpp> + +#include <unotools/ZipPackageHelper.hxx> +#include <comphelper/storagehelper.hxx> +#include <osl/file.hxx> +#include <unotools/streamwrap.hxx> +#include <tools/stream.hxx> +#include <tools/urlobj.hxx> + +#include <rtl/uri.hxx> + +namespace com::sun::star::io { class XInputStream; } + +using namespace utl; +using namespace osl; +using namespace comphelper; +using namespace com::sun::star; +using namespace com::sun::star::lang; +using namespace com::sun::star::uno; +using namespace com::sun::star::util; +using namespace com::sun::star::container; +using namespace com::sun::star::beans; +using namespace com::sun::star::io; + +using ::rtl::Uri; + +ZipPackageHelper::ZipPackageHelper( + const Reference< XComponentContext >& rxContext, + const OUString& sPackageURL) +: mxContext( rxContext ) +{ + // create the package zip file + Sequence< Any > aArguments{ + Any(sPackageURL), + // let ZipPackage be used + Any(beans::NamedValue("StorageFormat", Any(ZIP_STORAGE_FORMAT_STRING))) + }; + + Reference< XHierarchicalNameAccess > xHNameAccess( + mxContext->getServiceManager()->createInstanceWithArgumentsAndContext( + "com.sun.star.packages.comp.ZipPackage", + aArguments, mxContext ), UNO_QUERY); + mxHNameAccess = xHNameAccess; + + if( !mxHNameAccess.is() ) + return; + + Reference<XSingleServiceFactory> xFactory(mxHNameAccess, UNO_QUERY); + mxFactory = xFactory; + + // get root zip folder + mxHNameAccess->getByHierarchicalName( "/" ) >>= mxRootFolder; +} + +static OUString encodeZipUri( const OUString& rURI ) +{ + return Uri::encode( rURI, rtl_UriCharClassUric, rtl_UriEncodeCheckEscapes, RTL_TEXTENCODING_UTF8 ); +} + +Reference< XInterface >& ZipPackageHelper::getRootFolder() +{ + return mxRootFolder; +} + +Reference< XInterface > ZipPackageHelper::addFolder( Reference< XInterface > const & xRootFolder, + const OUString& rName ) +{ + if ( rName == ".." || rName == "." ) + throw lang::IllegalArgumentException(); + + Reference< XInterface > xFolder( mxFactory->createInstanceWithArguments({ Any(true) } )); + Reference< XNamed > xNamed( xFolder, UNO_QUERY ); + Reference< XChild > xChild( xFolder, UNO_QUERY ); + + if( xNamed.is() && xChild.is() ) + { + OUString aName( encodeZipUri( rName ) ); + xNamed->setName( aName ); + xChild->setParent( xRootFolder ); + } + + return xFolder; +} + +void ZipPackageHelper::addFolderWithContent( Reference< XInterface > const & xRootFolder, const OUString& rDirURL ) +{ + if (rDirURL.isEmpty()) + return; + + osl::Directory aDirectory(rDirURL); + + if (aDirectory.open() != osl::FileBase::E_None) + return; + + osl::DirectoryItem aDirectoryItem; + + while (osl::FileBase::E_None == aDirectory.getNextItem(aDirectoryItem)) + { + osl::FileStatus aFileStatus(osl_FileStatus_Mask_Type | osl_FileStatus_Mask_FileURL | osl_FileStatus_Mask_FileName); + + if (osl::FileBase::E_None == aDirectoryItem.getFileStatus(aFileStatus)) + { + if (aFileStatus.isDirectory()) + { + const OUString aFileName(aFileStatus.getFileName()); + + if (!aFileName.isEmpty()) + { + Reference<XInterface> folder(addFolder(xRootFolder, aFileName)); + addFolderWithContent(folder, aFileStatus.getFileURL()); + } + } + else if (aFileStatus.isRegular()) + { + addFile(xRootFolder, aFileStatus.getFileURL()); + } + } + } +} + +void ZipPackageHelper::addFile( css::uno::Reference< css::uno::XInterface > const & xRootFolder, + const OUString& rSourceFileURL ) +{ + INetURLObject aURL( rSourceFileURL ); + OUString aName( aURL.getName() ); + + SvFileStream* pStream = new SvFileStream(rSourceFileURL, StreamMode::READ ); + Reference< XInputStream > xInput( new utl::OSeekableInputStreamWrapper( pStream, true ) ); + Reference< XActiveDataSink > xSink( mxFactory->createInstance(), UNO_QUERY ); + assert(xSink); // this should never fail + if( !xSink.is() ) + return; + + Reference< XNameContainer > xNameContainer(xRootFolder, UNO_QUERY ); + xNameContainer->insertByName(encodeZipUri( aName ), Any(xSink)); + xSink->setInputStream( xInput ); +} + +void ZipPackageHelper::savePackage() +{ + Reference< XChangesBatch > xBatch( mxHNameAccess, UNO_QUERY ); + if( xBatch.is() ) + xBatch->commitChanges(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/misc/closeveto.cxx b/unotools/source/misc/closeveto.cxx new file mode 100644 index 0000000000..cb5ee7face --- /dev/null +++ b/unotools/source/misc/closeveto.cxx @@ -0,0 +1,147 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <unotools/closeveto.hxx> + +#include <com/sun/star/util/CloseVetoException.hpp> +#include <com/sun/star/util/XCloseable.hpp> + +#include <cppuhelper/implbase.hxx> +#include <rtl/ref.hxx> +#include <comphelper/diagnose_ex.hxx> + +namespace utl +{ + + using ::com::sun::star::uno::Reference; + using ::com::sun::star::uno::XInterface; + using ::com::sun::star::uno::UNO_QUERY; + using ::com::sun::star::uno::Exception; + using ::com::sun::star::util::XCloseable; + using ::com::sun::star::util::XCloseListener; + using ::com::sun::star::util::CloseVetoException; + using ::com::sun::star::lang::EventObject; + + //= CloseListener_Impl + + typedef ::cppu::WeakImplHelper < XCloseListener + > CloseListener_Base; + + namespace { + + class CloseListener_Impl : public CloseListener_Base + { + public: + explicit CloseListener_Impl(bool const bHasOwnership) + : m_bHasOwnership(bHasOwnership) + { + } + + // XCloseListener + virtual void SAL_CALL queryClosing( const EventObject& Source, sal_Bool GetsOwnership ) override; + virtual void SAL_CALL notifyClosing( const EventObject& Source ) override; + + // XEventListener + virtual void SAL_CALL disposing( const EventObject& Source) override; + + bool hasOwnership() const { return m_bHasOwnership; } + + protected: + virtual ~CloseListener_Impl() override + { + } + + private: + bool m_bHasOwnership; + }; + + } + + void SAL_CALL CloseListener_Impl::queryClosing( const EventObject&, sal_Bool i_deliverOwnership ) + { + if ( !m_bHasOwnership ) + m_bHasOwnership = i_deliverOwnership; + + throw CloseVetoException(); + } + + void SAL_CALL CloseListener_Impl::notifyClosing( const EventObject& ) {} + + void SAL_CALL CloseListener_Impl::disposing( const EventObject& ) {} + + //= CloseVeto_Data + + struct CloseVeto_Data + { + Reference< XCloseable > xCloseable; + ::rtl::Reference< CloseListener_Impl > pListener; + }; + + //= operations + + namespace + { + + void lcl_init( CloseVeto_Data& i_data, const Reference< XInterface >& i_closeable, + bool const hasOwnership) + { + i_data.xCloseable.set( i_closeable, UNO_QUERY ); + ENSURE_OR_RETURN_VOID( i_data.xCloseable.is(), "CloseVeto: the component is not closeable!" ); + + i_data.pListener = new CloseListener_Impl(hasOwnership); + i_data.xCloseable->addCloseListener( i_data.pListener ); + } + + void lcl_deinit( CloseVeto_Data const & i_data ) + { + if ( !i_data.xCloseable.is() ) + return; + + i_data.xCloseable->removeCloseListener( i_data.pListener ); + if ( i_data.pListener->hasOwnership() ) + { + try + { + i_data.xCloseable->close( true ); + } + catch( const CloseVetoException& ) { } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("unotools"); + } + } + } + } + + //= CloseVeto + CloseVeto::CloseVeto(const Reference< XInterface >& i_closeable, + bool const hasOwnership) + : m_xData(new CloseVeto_Data) + { + lcl_init(*m_xData, i_closeable, hasOwnership); + } + + CloseVeto::~CloseVeto() + { + lcl_deinit(*m_xData); + } + +} // namespace utl + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/misc/datetime.cxx b/unotools/source/misc/datetime.cxx new file mode 100644 index 0000000000..e2cc1f6d11 --- /dev/null +++ b/unotools/source/misc/datetime.cxx @@ -0,0 +1,536 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <unotools/datetime.hxx> +#include <unotools/localedatawrapper.hxx> +#include <unotools/syslocale.hxx> +#include <tools/date.hxx> +#include <tools/time.hxx> +#include <tools/datetime.hxx> +#include <rtl/ustrbuf.hxx> +#include <rtl/math.hxx> +#include <osl/diagnose.h> +#include <comphelper/string.hxx> +#include <o3tl/string_view.hxx> +#include <cstddef> +#include <sstream> + +namespace +{ + bool checkAllNumber(std::u16string_view rString) + { + sal_Int32 nPos = 0; + sal_Int32 nLen = rString.size(); + + // skip white space + while( nPos < nLen && ' ' == rString[nPos] ) + nPos++; + + if( nPos < nLen && '-' == rString[nPos] ) + nPos++; + + // get number + while( nPos < nLen && + '0' <= rString[nPos] && + '9' >= rString[nPos] ) + { + nPos++; + } + + return nPos == nLen; + } + + /** convert string to number with optional min and max values */ + bool convertNumber32(sal_Int32& rValue, + std::u16string_view rString, + sal_Int32 /*nMin*/ = -1, sal_Int32 /*nMax*/ = -1) + { + if (!checkAllNumber(rString)) + { + rValue = 0; + return false; + } + + rValue = o3tl::toInt32(rString); + return true; + } + + bool convertNumber64(sal_Int64& rValue, + std::u16string_view rString, + sal_Int64 /*nMin*/ = -1, sal_Int64 /*nMax*/ = -1) + { + if (!checkAllNumber(rString)) + { + rValue = 0; + return false; + } + + rValue = o3tl::toInt64(rString); + return true; + } + + // although the standard calls for fixed-length (zero-padded) tokens + // (in their integer part), we are here liberal and allow shorter tokens + // (when there are separators, else it is ambiguous). + // Note that: + // the token separator is OPTIONAL + // empty string is a valid token! (to recognise hh or hhmm or hh:mm formats) + // returns: success / failure + // in case of failure, no reference argument is changed + // arguments: + // i_str: string to extract token from + // index: index in i_str where to start tokenizing + // after return, start of *next* token (if any) + // if this was the last token, then the value is UNDEFINED + // o_strInt: output; integer part of token + // o_bFraction: output; was there a fractional part? + // o_strFrac: output; fractional part of token + bool impl_getISO8601TimeToken(std::u16string_view i_str, std::size_t &nPos, OUString &resInt, bool &bFraction, OUString &resFrac) + { + bFraction = false; + // all tokens are of length 2 + const std::size_t nEndPos = nPos + 2; + const sal_Unicode c0 = '0'; + const sal_Unicode c9 = '9'; + const sal_Unicode sep = ':'; + for (;nPos < nEndPos && nPos < i_str.size(); ++nPos) + { + const sal_Unicode c = i_str[nPos]; + if (c == sep) + return true; + if (c < c0 || c > c9) + return false; + resInt += OUStringChar(c); + } + if (nPos == 0) + return false; + if (nPos == i_str.size() || i_str[nPos] == sep) + return true; + if (i_str[nPos] == ',' || i_str[nPos] == '.') + { + bFraction = true; + ++nPos; + for (; nPos < i_str.size(); ++nPos) + { + const sal_Unicode c = i_str[nPos]; + if (c == 'Z' || c == '+' || c == '-') + { + --nPos; // we don't want to skip the tz separator + return true; + } + if (c == sep) + // fractional part allowed only in *last* token + return false; + if (c < c0 || c > c9) + return false; + resFrac += OUStringChar(c); + } + OSL_ENSURE(nPos == i_str.size(), "impl_getISO8601TimeToken internal error; expected to be at end of string"); + return true; + } + if (i_str[nPos] == 'Z' || i_str[nPos] == '+' || i_str[nPos] == '-') + { + --nPos; // we don't want to skip the tz separator + return true; + } + else + return false; + } + bool getISO8601TimeToken(std::u16string_view i_str, std::size_t &io_index, OUString &o_strInt, bool &o_bFraction, OUString &o_strFrac) + { + OUString resInt; + OUString resFrac; + bool bFraction = false; + std::size_t index = io_index; + if(!impl_getISO8601TimeToken(i_str, index, resInt, bFraction, resFrac)) + return false; + else + { + io_index = index+1; + o_strInt = resInt; + o_strFrac = resFrac; + o_bFraction = bFraction; + return true; + } + } + bool getISO8601TimeZoneToken(std::u16string_view i_str, std::size_t &io_index, OUString &o_strInt) + { + const sal_Unicode c0 = '0'; + const sal_Unicode c9 = '9'; + const sal_Unicode sep = ':'; + if (i_str[io_index] == 'Z') // UTC timezone indicator + { + ++io_index; + o_strInt = "Z"; + return true; + } + else if (i_str[io_index] == '+' || i_str[io_index] == '-') // other timezones indicator + { + ++io_index; + o_strInt.clear(); + for (; io_index < i_str.size(); ++io_index) + { + const sal_Unicode c = i_str[io_index]; + if ((c < c0 || c > c9) && c != sep) + return false; + o_strInt += OUStringChar(c); + } + return true; + } + else + return false; + } +} + +namespace utl +{ +const LocaleDataWrapper& GetLocaleData() +{ + static SvtSysLocale ourSysLocale; + return ourSysLocale.GetLocaleData(); +} + +DateTime GetDateTime(const css::util::DateTime& _rDT) { return DateTime(_rDT); } + +OUString GetDateTimeString(const css::util::DateTime& _rDT) +{ + // String with date and time information (#i20172#) + DateTime aDT(GetDateTime(_rDT)); + const LocaleDataWrapper& rLoDa = GetLocaleData(); + + return rLoDa.getDate(aDT) + " " + rLoDa.getTime(aDT); +} + +OUString GetDateTimeString(sal_Int32 _nDate, sal_Int32 _nTime) +{ + const LocaleDataWrapper& rLoDa = GetLocaleData(); + + Date aDate(_nDate); + tools::Time aTime(_nTime * tools::Time::nanoPerCenti); + return rLoDa.getDate(aDate) + ", " + rLoDa.getTime(aTime); +} + +OUString GetDateString(const css::util::DateTime& _rDT) +{ + return GetLocaleData().getDate(GetDateTime(_rDT)); +} + +void typeConvert(const Date& _rDate, css::util::Date& _rOut) +{ + _rOut.Day = _rDate.GetDay(); + _rOut.Month = _rDate.GetMonth(); + _rOut.Year = _rDate.GetYear(); +} + +void typeConvert(const css::util::Date& _rDate, Date& _rOut) +{ + _rOut = Date(_rDate.Day, _rDate.Month, _rDate.Year); +} + +void typeConvert(const DateTime& _rDateTime, css::util::DateTime& _rOut) +{ + _rOut.Year = _rDateTime.GetYear(); + _rOut.Month = _rDateTime.GetMonth(); + _rOut.Day = _rDateTime.GetDay(); + _rOut.Hours = _rDateTime.GetHour(); + _rOut.Minutes = _rDateTime.GetMin(); + _rOut.Seconds = _rDateTime.GetSec(); + _rOut.NanoSeconds = _rDateTime.GetNanoSec(); +} + +void typeConvert(const css::util::DateTime& _rDateTime, DateTime& _rOut) +{ + Date aDate(_rDateTime.Day, _rDateTime.Month, _rDateTime.Year); + tools::Time aTime(_rDateTime.Hours, _rDateTime.Minutes, _rDateTime.Seconds, _rDateTime.NanoSeconds); + _rOut = DateTime(aDate, aTime); +} + +OUString toISO8601(const css::util::DateTime& rDateTime) +{ + OUStringBuffer rBuffer(32); + rBuffer.append(OUString::number(static_cast<sal_Int32>(rDateTime.Year)) + "-"); + if( rDateTime.Month < 10 ) + rBuffer.append('0'); + rBuffer.append(OUString::number(static_cast<sal_Int32>(rDateTime.Month)) + "-"); + if( rDateTime.Day < 10 ) + rBuffer.append('0'); + rBuffer.append(static_cast<sal_Int32>(rDateTime.Day)); + + if( rDateTime.NanoSeconds != 0 || + rDateTime.Seconds != 0 || + rDateTime.Minutes != 0 || + rDateTime.Hours != 0 ) + { + rBuffer.append('T'); + if( rDateTime.Hours < 10 ) + rBuffer.append('0'); + rBuffer.append(OUString::number(static_cast<sal_Int32>(rDateTime.Hours)) + ":"); + if( rDateTime.Minutes < 10 ) + rBuffer.append('0'); + rBuffer.append(OUString::number(static_cast<sal_Int32>(rDateTime.Minutes)) + ":"); + if( rDateTime.Seconds < 10 ) + rBuffer.append('0'); + rBuffer.append(static_cast<sal_Int32>(rDateTime.Seconds)); + if ( rDateTime.NanoSeconds > 0) + { + OSL_ENSURE(rDateTime.NanoSeconds < 1000000000,"NanoSeconds cannot be more than 999 999 999"); + rBuffer.append(','); + std::ostringstream ostr; + ostr.fill('0'); + ostr.width(9); + ostr << rDateTime.NanoSeconds; + rBuffer.appendAscii(ostr.str().c_str()); + } + } + return rBuffer.makeStringAndClear(); +} + +/** convert ISO8601 DateTime String to util::DateTime */ +bool ISO8601parseDateTime(std::u16string_view rString, css::util::DateTime& rDateTime) +{ + bool bSuccess = true; + + std::u16string_view aDateStr, aTimeStr; + css::util::Date aDate; + css::util::Time aTime; + size_t nPos = rString.find( 'T' ); + if ( nPos != std::u16string_view::npos ) + { + aDateStr = rString.substr( 0, nPos ); + aTimeStr = rString.substr( nPos + 1 ); + } + else + aDateStr = rString; // no separator: only date part + + bSuccess = ISO8601parseDate(aDateStr, aDate); + + if ( bSuccess && !aTimeStr.empty() ) // time is optional + { + bSuccess = ISO8601parseTime(aTimeStr, aTime); + } + + if (bSuccess) + { + rDateTime = css::util::DateTime(aTime.NanoSeconds, aTime.Seconds, aTime.Minutes, aTime.Hours, + aDate.Day, aDate.Month, aDate.Year, false); + } + + return bSuccess; +} + +/** convert ISO8601 Date String to util::Date */ +// TODO: supports only calendar dates YYYY-MM-DD +// MISSING: calendar dates YYYYMMDD YYYY-MM +// year, week date, ordinal date +bool ISO8601parseDate(std::u16string_view aDateStr, css::util::Date& rDate) +{ + const sal_Int32 nDateTokens {comphelper::string::getTokenCount(aDateStr, '-')}; + + if (nDateTokens<1 || nDateTokens>3) + return false; + + sal_Int32 nYear = 1899; + sal_Int32 nMonth = 12; + sal_Int32 nDay = 30; + + sal_Int32 nIdx {0}; + auto strCurrentToken = o3tl::getToken(aDateStr, 0, '-', nIdx ); + if ( !convertNumber32( nYear, strCurrentToken, 0, 9999 ) ) + return false; + if ( nDateTokens >= 2 ) + { + strCurrentToken = o3tl::getToken(aDateStr, 0, '-', nIdx ); + if (strCurrentToken.size() > 2) + return false; + if ( !convertNumber32( nMonth, strCurrentToken, 0, 12 ) ) + return false; + } + if ( nDateTokens >= 3 ) + { + strCurrentToken = o3tl::getToken(aDateStr, 0, '-', nIdx ); + if (strCurrentToken.size() > 2) + return false; + if ( !convertNumber32( nDay, strCurrentToken, 0, 31 ) ) + return false; + } + + rDate.Year = static_cast<sal_uInt16>(nYear); + rDate.Month = static_cast<sal_uInt16>(nMonth); + rDate.Day = static_cast<sal_uInt16>(nDay); + + return true; +} + +/** convert ISO8601 Time String to util::Time */ +bool ISO8601parseTime(std::u16string_view aTimeStr, css::util::Time& rTime) +{ + sal_Int32 nHour = 0; + sal_Int32 nMin = 0; + sal_Int32 nSec = 0; + sal_Int32 nNanoSec = 0; + + std::size_t n = 0; + OUString tokInt; + OUString tokFrac; + OUString tokTz; + bool bFrac = false; + // hours + bool bSuccess = getISO8601TimeToken(aTimeStr, n, tokInt, bFrac, tokFrac); + if (!bSuccess) + return false; + + if ( bFrac && n < aTimeStr.size()) + { + // is it junk or the timezone? + bSuccess = getISO8601TimeZoneToken(aTimeStr, n, tokTz); + if (!bSuccess) + return false; + } + bSuccess = convertNumber32( nHour, tokInt, 0, 23 ); + if (!bSuccess) + return false; + + if (bFrac) + { + sal_Int64 fracNumerator; + bSuccess = convertNumber64(fracNumerator, tokFrac); + if ( bSuccess ) + { + double frac = static_cast<double>(fracNumerator) / pow(static_cast<double>(10), static_cast<double>(tokFrac.getLength())); + // minutes + OSL_ENSURE(frac < 1 && frac >= 0, "ISO8601parse internal error frac hours (of hours) not between 0 and 1"); + frac *= 60; + nMin = floor(frac); + frac -= nMin; + // seconds + OSL_ENSURE(frac < 1 && frac >= 0, "ISO8601parse internal error frac minutes (of hours) not between 0 and 1"); + frac *= 60; + nSec = floor(frac); + frac -= nSec; + // nanoseconds + OSL_ENSURE(frac < 1 && frac >= 0, "ISO8601parse internal error frac seconds (of hours) not between 0 and 1"); + frac *= 1000000000; + nNanoSec = ::rtl::math::round(frac); + } + goto end; + } + if(n >= aTimeStr.size()) + goto end; + + // minutes + bSuccess = getISO8601TimeToken(aTimeStr, n, tokInt, bFrac, tokFrac); + if (!bSuccess) + return false; + if ( bFrac && n < aTimeStr.size()) + { + // is it junk or the timezone? + bSuccess = getISO8601TimeZoneToken(aTimeStr, n, tokTz); + if (!bSuccess) + return false; + } + bSuccess = convertNumber32( nMin, tokInt, 0, 59 ); + if (!bSuccess) + return false; + if (bFrac) + { + sal_Int64 fracNumerator; + bSuccess = convertNumber64(fracNumerator, tokFrac); + if ( bSuccess ) + { + double frac = static_cast<double>(fracNumerator) / pow(static_cast<double>(10), static_cast<double>(tokFrac.getLength())); + // seconds + OSL_ENSURE(frac < 1 && frac >= 0, "ISO8601parse internal error frac minutes (of minutes) not between 0 and 1"); + frac *= 60; + nSec = floor(frac); + frac -= nSec; + // nanoseconds + OSL_ENSURE(frac < 1 && frac >= 0, "ISO8601parse internal error frac seconds (of minutes) not between 0 and 1"); + frac *= 1000000000; + nNanoSec = ::rtl::math::round(frac); + } + goto end; + } + if(n >= aTimeStr.size()) + goto end; + + // seconds + bSuccess = getISO8601TimeToken(aTimeStr, n, tokInt, bFrac, tokFrac); + if (!bSuccess) + return false; + if (n < aTimeStr.size()) + { + // is it junk or the timezone? + bSuccess = getISO8601TimeZoneToken(aTimeStr, n, tokTz); + if (!bSuccess) + return false; + } + // max 60 for leap seconds + bSuccess = convertNumber32( nSec, tokInt, 0, 60 ); + if (!bSuccess) + return false; + if (bFrac) + { + sal_Int64 fracNumerator; + bSuccess = convertNumber64(fracNumerator, tokFrac); + if ( bSuccess ) + { + double frac = static_cast<double>(fracNumerator) / pow(static_cast<double>(10), static_cast<double>(tokFrac.getLength())); + // nanoseconds + OSL_ENSURE(frac < 1 && frac >= 0, "ISO8601parse internal error frac seconds (of seconds) not between 0 and 1"); + frac *= 1000000000; + nNanoSec = ::rtl::math::round(frac); + } + goto end; + } + + end: + if (bSuccess) + { + // normalise time + const int secondsOverflow = (nSec == 60) ? 61 : 60; + if (nNanoSec == 1000000000) + { + nNanoSec = 0; + ++nSec; + } + if(nSec == secondsOverflow) + { + nSec = 0; + ++nMin; + } + if(nMin == 60) + { + nMin = 0; + ++nHour; + } + if(!tokTz.isEmpty()) + rTime.IsUTC = (tokTz == "Z"); + + rTime.Hours = static_cast<sal_uInt16>(nHour); + rTime.Minutes = static_cast<sal_uInt16>(nMin); + rTime.Seconds = static_cast<sal_uInt16>(nSec); + rTime.NanoSeconds = nNanoSec; + } + + return bSuccess; +} + +} // namespace utl + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/misc/defaultencoding.cxx b/unotools/source/misc/defaultencoding.cxx new file mode 100644 index 0000000000..552f3b8c40 --- /dev/null +++ b/unotools/source/misc/defaultencoding.cxx @@ -0,0 +1,33 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <sal/config.h> + +#include <unotools/configmgr.hxx> +#include <unotools/defaultencoding.hxx> +#include <officecfg/Office/Linguistic.hxx> +#include <officecfg/Setup.hxx> +#include <officecfg/System.hxx> + +OUString utl_getLocaleForGlobalDefaultEncoding() +{ + if (utl::ConfigManager::IsFuzzing()) + return "en-US"; + // First try document default language + OUString result(officecfg::Office::Linguistic::General::DefaultLocale::get()); + // Fallback to LO locale + if (result.isEmpty()) + result = officecfg::Setup::L10N::ooSetupSystemLocale::get(); + // Fallback to system locale + if (result.isEmpty()) + result = officecfg::System::L10N::Locale::get(); + return result; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/misc/desktopterminationobserver.cxx b/unotools/source/misc/desktopterminationobserver.cxx new file mode 100644 index 0000000000..2eeb44a92d --- /dev/null +++ b/unotools/source/misc/desktopterminationobserver.cxx @@ -0,0 +1,188 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <unotools/desktopterminationobserver.hxx> + +#include <com/sun/star/frame/TerminationVetoException.hpp> +#include <com/sun/star/frame/XTerminateListener.hpp> +#include <com/sun/star/frame/Desktop.hpp> +#include <cppuhelper/implbase.hxx> +#include <comphelper/processfactory.hxx> +#include <osl/diagnose.h> +#include <comphelper/diagnose_ex.hxx> + +#include <vector> + +namespace utl +{ + + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::lang; + using namespace ::com::sun::star::frame; + + namespace + { + + typedef ::std::vector< ITerminationListener* > Listeners; + + struct ListenerAdminData + { + Listeners aListeners; + bool bAlreadyTerminated; + bool bCreatedAdapter; + + ListenerAdminData() : bAlreadyTerminated( false ), bCreatedAdapter( false ) { } + }; + + ListenerAdminData& getListenerAdminData() + { + static ListenerAdminData s_aData; + return s_aData; + } + + //= OObserverImpl + + class OObserverImpl : public ::cppu::WeakImplHelper< XTerminateListener > + { + public: + static void ensureObservation(); + + private: + OObserverImpl(); + virtual ~OObserverImpl() override; + + // XTerminateListener + virtual void SAL_CALL queryTermination( const EventObject& Event ) override; + virtual void SAL_CALL notifyTermination( const EventObject& Event ) override; + + // XEventListener + virtual void SAL_CALL disposing( const css::lang::EventObject& Source ) override; + }; + + OObserverImpl::OObserverImpl() + { + } + + OObserverImpl::~OObserverImpl() + { + } + + void OObserverImpl::ensureObservation() + { + { + if ( getListenerAdminData().bCreatedAdapter ) + return; + ::osl::MutexGuard aGuard( ::osl::Mutex::getGlobalMutex() ); + if ( getListenerAdminData().bCreatedAdapter ) + return; + + getListenerAdminData().bCreatedAdapter = true; + } + + try + { + Reference< XDesktop2 > xDesktop = Desktop::create( ::comphelper::getProcessComponentContext() ); + xDesktop->addTerminateListener( new OObserverImpl ); + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "unotools", "OObserverImpl::ensureObservation" ); + } + } + + void SAL_CALL OObserverImpl::queryTermination( const EventObject& /*Event*/ ) + { + Listeners aToNotify; + { + ::osl::MutexGuard aGuard( ::osl::Mutex::getGlobalMutex() ); + aToNotify = getListenerAdminData().aListeners; + } + + for (auto const& listener : aToNotify) + { + if ( !listener->queryTermination() ) + throw TerminationVetoException(); + } + } + + void SAL_CALL OObserverImpl::notifyTermination( const EventObject& /*Event*/ ) + { + // get the listeners + Listeners aToNotify; + { + ::osl::MutexGuard aGuard( ::osl::Mutex::getGlobalMutex() ); + OSL_ENSURE( !getListenerAdminData().bAlreadyTerminated, "OObserverImpl::notifyTermination: terminated twice?" ); + aToNotify = getListenerAdminData().aListeners; + getListenerAdminData().bAlreadyTerminated = true; + } + + // notify the listeners + for (auto const& listener : aToNotify) + { + listener->notifyTermination(); + } + + // clear the listener container + { + ::osl::MutexGuard aGuard( ::osl::Mutex::getGlobalMutex() ); + getListenerAdminData().aListeners.clear(); + } + } + + void SAL_CALL OObserverImpl::disposing( const EventObject& /*Event*/ ) + { +#if OSL_DEBUG_LEVEL > 0 + ::osl::MutexGuard aGuard( ::osl::Mutex::getGlobalMutex() ); + OSL_ENSURE( getListenerAdminData().bAlreadyTerminated, "OObserverImpl::disposing: disposing without terminated?" ); +#endif + // not interested in + } + } + + //= DesktopTerminationObserver + + void DesktopTerminationObserver::registerTerminationListener( ITerminationListener* _pListener ) + { + if ( !_pListener ) + return; + + { + ::osl::MutexGuard aGuard( ::osl::Mutex::getGlobalMutex() ); + if ( getListenerAdminData().bAlreadyTerminated ) + { + _pListener->notifyTermination(); + return; + } + + getListenerAdminData().aListeners.push_back( _pListener ); + } + + OObserverImpl::ensureObservation(); + } + + void DesktopTerminationObserver::revokeTerminationListener( ITerminationListener const * _pListener ) + { + ::osl::MutexGuard aGuard( ::osl::Mutex::getGlobalMutex() ); + Listeners& rListeners = getListenerAdminData().aListeners; + std::erase(rListeners, _pListener); + } + +} // namespace utl + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/misc/eventlisteneradapter.cxx b/unotools/source/misc/eventlisteneradapter.cxx new file mode 100644 index 0000000000..b899db5c07 --- /dev/null +++ b/unotools/source/misc/eventlisteneradapter.cxx @@ -0,0 +1,155 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <vector> + +#include <com/sun/star/lang/XComponent.hpp> +#include <unotools/eventlisteneradapter.hxx> +#include <osl/diagnose.h> +#include <cppuhelper/implbase.hxx> +#include <rtl/ref.hxx> + +namespace utl +{ + + using namespace ::com::sun::star::uno; + using namespace ::com::sun::star::lang; + + //= OEventListenerImpl + + class OEventListenerImpl : public ::cppu::WeakImplHelper< XEventListener > + { + protected: + OEventListenerAdapter* m_pAdapter; + Reference< XEventListener > m_xKeepMeAlive; + // imagine an implementation of XComponent which holds it's listeners with a weak reference ... + // would be very bad if we don't hold ourself + Reference< XComponent > m_xComponent; + + public: + OEventListenerImpl( OEventListenerAdapter* _pAdapter, const Reference< XComponent >& _rxComp ); + + void dispose(); + const Reference< XComponent >& getComponent() const { return m_xComponent; } + + protected: + virtual void SAL_CALL disposing( const EventObject& _rSource ) override; + }; + + OEventListenerImpl::OEventListenerImpl( OEventListenerAdapter* _pAdapter, const Reference< XComponent >& _rxComp ) + :m_pAdapter(_pAdapter) + { + OSL_ENSURE(m_pAdapter, "OEventListenerImpl::OEventListenerImpl: invalid adapter!"); + // no checks of _rxComp !! + // (OEventListenerAdapter is responsible for this) + + // just in case addEventListener throws an exception ... don't initialize m_xKeepMeAlive before this + // is done + Reference< XEventListener > xMeMyselfAndI = this; + _rxComp->addEventListener(xMeMyselfAndI); + + m_xComponent = _rxComp; + m_xKeepMeAlive = xMeMyselfAndI; + } + + void OEventListenerImpl::dispose() + { + if (m_xComponent.is()) + { + if (m_xKeepMeAlive.is()) + m_xComponent->removeEventListener(m_xKeepMeAlive); + m_xComponent.clear(); + m_xKeepMeAlive.clear(); + } + } + + void SAL_CALL OEventListenerImpl::disposing( const EventObject& _rSource ) + { + Reference< XEventListener > xDeleteUponLeaving = m_xKeepMeAlive; + m_xKeepMeAlive.clear(); + + m_pAdapter->_disposing(_rSource); + } + + //= OEventListenerAdapterImpl + + struct OEventListenerAdapterImpl + { + public: + std::vector< rtl::Reference<OEventListenerImpl> > aListeners; + }; + + //= OEventListenerAdapter + + OEventListenerAdapter::OEventListenerAdapter() + :m_pImpl(new OEventListenerAdapterImpl) + { + } + + OEventListenerAdapter::~OEventListenerAdapter() + { + stopAllComponentListening( ); + } + + void OEventListenerAdapter::stopComponentListening( const css::uno::Reference< css::lang::XComponent >& _rxComp ) + { + if ( m_pImpl->aListeners.empty() ) + return; + + auto it = m_pImpl->aListeners.begin(); + do + { + rtl::Reference<OEventListenerImpl>& pListenerImpl = *it; + if ((pListenerImpl->getComponent().get() == _rxComp.get()) || (pListenerImpl->getComponent() == _rxComp)) + { + pListenerImpl->dispose(); + it = m_pImpl->aListeners.erase( it ); + } + else + ++it; + } + while ( it != m_pImpl->aListeners.end() ); + } + + void OEventListenerAdapter::stopAllComponentListening( ) + { + for ( const auto & i : m_pImpl->aListeners ) + { + i->dispose(); + } + m_pImpl->aListeners.clear(); + } + + void OEventListenerAdapter::startComponentListening( const Reference< XComponent >& _rxComp ) + { + if (!_rxComp.is()) + { + OSL_FAIL("OEventListenerAdapter::startComponentListening: invalid component!"); + return; + } + + rtl::Reference<OEventListenerImpl> pListenerImpl = new OEventListenerImpl(this, _rxComp); + m_pImpl->aListeners.emplace_back(pListenerImpl); + } + +} // namespace utl + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/misc/fontcvt.cxx b/unotools/source/misc/fontcvt.cxx new file mode 100644 index 0000000000..5abda6fdf9 --- /dev/null +++ b/unotools/source/misc/fontcvt.cxx @@ -0,0 +1,1432 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <rtl/ustrbuf.hxx> +#include <sal/log.hxx> +#include <unotools/fontcvt.hxx> +#include <unotools/fontdefs.hxx> + +#include <cstddef> +#include <map> +#include <vector> + +//These conversion tables were designed for StarSymbol. OpenSymbol +//originally didn't have the same code points as StarSymbol, and +//then gained some extra code points, but there are still potentially +//various holes in OpenSymbol which were filled by StarSymbol, i.e. +//destination mapping points which are empty in OpenSymbol + +const sal_Unicode aStarBatsTab[224] = +{ + // F020 + 0x0020, 0x263a, 0x25cf, 0x274d, + 0x25a0, 0x25a1, 0xE000, 0x2751, + 0x2752, 0xE001, 0xE002, 0xE003, + 0x2756, 0xE004, 0xE005, 0x27a2, + // F030 + 0xE006, 0x2794, 0x2713, 0x2612, + 0x2611, 0x27b2, 0x261b, 0x270d, + 0x270e, 0xE007, 0x2714, 0xE008, + 0xE009, 0xE00A, 0x274f, 0x2750, + // F040 + 0xE00B, 0xE00C, 0xE00D, 0xE00E, + 0x2722, 0x2723, 0x2724, 0x2725, + 0x2733, 0x2734, 0x2735, 0x2736, + 0x2737, 0x2738, 0x2739, 0x2717, + // F050 + 0x2718, 0x2719, 0x271a, 0x271b, + 0x271c, 0x272b, 0x272c, 0x272d, + 0x272e, 0x272f, 0x2730, 0, + 0xE00F, 0x278a, 0x278b, 0x278c, + // F060 + 0x278d, 0x278e, 0x278f, 0x2790, + 0x2791, 0x2792, 0x2793, 0xE010, + 0x2780, 0x2781, 0x2782, 0x2783, + 0x2784, 0x2785, 0x2786, 0x2787, + // F070 + 0x2788, 0x2789, 0xE011, 0xE012, + 0x260e, 0xE013, 0xE014, 0xE015, + 0xE016, 0xE017, 0xE018, 0xE019, + 0xE01A, 0x261e, 0xE01B, 0, + // F080 + 0x20ac, 0, 0x201a, 0x0192, + 0x201e, 0x2026, 0x2020, 0x2021, + 0xE01c, 0x2030, 0x0160, 0x2039, + 0x0152, 0, 0x017d, 0, + // F090 + 0, 0x2018, 0x2019, 0x201c, + 0x201d, 0x2022, 0x2013, 0x2014, + 0xE01d, 0x2122, 0x0161, 0x203a, + 0x0153, 0, 0x017e, 0x0178, + // F0A0 + 0, 0x21e7, 0x21e8, 0x21e9, + 0x21e6, 0xE01e, 0xE01f, 0x00a7, + 0xE020, 0xE021, 0xE022, 0x00ab, + 0xE023, 0x2639, 0xE024, 0xE025, + // F0B0 + 0xE026, 0xE027, 0xE028, 0x21e5, + 0x21e4, 0x2192, 0x2193, 0x2190, + 0x2191, 0xE029, 0xE02a, 0x00bb, + 0xE02b, 0xE02c, 0xE02d, 0xE02e, + // F0C0 + 0xE02f, 0xE030, 0xE031, 0xE032, + 0x25be, 0x25b4, 0x25bf, 0x25b5, + 0xE033, 0xE034, 0xE035, 0x2702, + 0x2708, 0x2721, 0x273f, 0x2744, + // F0D0 + 0x25d7, 0x2759, 0xE036, 0xE037, + 0x2762, 0x2663, 0x2665, 0x2660, + 0x2194, 0x2195, 0x2798, 0x279a, + 0x27b8, 0, 0x00b6, 0, + // F0E0 + 0x00a2, 0x00a4, 0x00a5, 0xE038, + 0x20a1, 0x20a2, 0x20a3, 0x20a4, + 0x20a9, 0x20ab, 0x20a8, 0xE039, + 0, 0, 0, 0, + // F0F0 + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0xE03a +}; + +const sal_Unicode aStarMathTab[224] = +{ + // F020 + 0x0020, 0x0021, 0x0022, 0x0023, + 0xE080, 0x0025, 0x0026, 0x221e, + 0x0028, 0x0029, 0x002a, 0x002b, + 0x002c, 0x002d, 0x002e, 0x002f, + // F030 + 0x2224, 0x21d2, 0x21d0, 0x21d4, + 0xE081, 0xE082, 0x00b0, 0, + 0, 0, 0x003a, 0x003b, + 0x003c, 0x003d, 0x003e, 0x00bf, + // F040 + 0x2260, 0xE083, 0x2212, 0x2217, + 0x00d7, 0x22c5, 0x00f7, 0x00b1, + 0x2213, 0x2295, 0x2296, 0x2297, + 0x2298, 0x2299, 0x222a, 0x2229, + // F050 + 0xE084, 0xE085, 0x2264, 0x2265, + 0xE086, 0xE087, 0x226a, 0x226b, + 0x007e, 0x2243, 0x2248, 0x225d, + 0x2261, 0x221d, 0x2202, 0x2282, + // F060 + 0x2283, 0x2286, 0x2287, 0x2284, + 0x2285, 0x2288, 0x2289, 0x2208, + 0x2209, 0xE089, 0x2203, 0x220d, + 0x2135, 0x2111, 0x211c, 0x2118, + // F070 + 0xE08a, 0x2221, 0x2222, 0x2223, + 0x2225, 0x22a5, 0xE08b, 0x22ef, + 0x22ee, 0x22f0, 0x22f1, 0x22b6, + 0x22b7, 0xE08c, 0x2216, 0x00ac, + // F080 + 0x222b, 0x222c, 0x222d, 0x222e, + 0x222f, 0x2230, 0x221a, 0xE08d, + 0xE08e, 0xE08f, 0x2210, 0x220f, + 0x2211, 0x2207, 0x2200, 0xE090, + // F090 + 0xE091, 0xE092, 0xE093, 0xE094, + 0xE095, 0xE096, 0xE097, 0xE098, + 0x02d9, 0x00a8, 0xE09b, 0x02da, + 0x2227, 0x2228, 0x220b, 0x2205, + // F0A0 + 0x007b, 0x007d, 0xe09e, 0xe09f, + 0x2329, 0x232a, 0x005b, 0x005d, + 0xE0a0, 0x22be, 0xE0a1, 0x2259, + 0x2254, 0x2255, 0x21b3, 0x2197, + // F0B0 + 0x2198, 0x2245, 0x301a, 0x301b, + 0x2373, 0xE0a5, 0xE0a6, 0x22a4, + 0x2112, 0x2130, 0x2131, 0, + 0xE0a7, 0xE0a8, 0xE0a9, 0xE0aa, + // F0C0 + 0x2308, 0x230a, 0x2309, 0x230b, + 0, 0xe0ab, 0xe0ac, 0xe0ad, + 0xe0ae, 0xe0af, 0xe0b0, 0xe0b1, + 0xe0b2, 0xe0b3, 0xe0b4, 0xe0b5, + // F0D0 + 0xe0b6, 0xe0b7, 0xe0b8, 0xe0b9, + 0xe0ba, 0xe0bb, 0xe0bc, 0xe0bd, + 0xe0be, 0xe0bf, 0xe0c0, 0xe0c1, + 0xe0c2, 0xe0c3, 0xe0c4, 0xe0c5, + // F0E0 + 0xe0c6, 0xe0c7, 0xe0c8, 0xe0c9, + 0xe0ca, 0xe0cb, 0xe0cc, 0xe0cd, + 0xe0ce, 0xe0cf, 0xe0d0, 0xe0d1, + 0x03f1, 0xe0d3, 0xe0d4, 0xe0d5, + // F0F0 + 0x2113, 0xe0d6, 0x2107, 0x2127, + 0x210a, 0xe0d9, 0x210f, 0x019b, + 0xe0db, 0xe0dc, 0xe0dd, 0x2115, + 0x2124, 0x211a, 0x211d, 0x2102 +}; + +const sal_Unicode aWingDingsTab[224] = +{ + // F020 + 0x0020, 0xe400, 0xe401, 0xe402, + 0xe403, 0xe404, 0xe405, 0xe406, + 0xe407, 0xe408, 0xe409, 0xe40a, + 0xe40b, 0xe40c, 0xe40d, 0xe40e, + // F030 + 0xe40f, 0xe410, 0xe411, 0xe412, + 0xe413, 0xe414, 0xe415, 0xe416, + 0xe417, 0xe418, 0xe419, 0xe41a, + 0xe41b, 0xe41c, 0x2707, 0xe41d, + // F040 + 0xe41e, 0xe41f, 0xe420, 0xe421, + 0x261c, 0xe423, 0x261d, 0x261f, + 0x261f, 0xe424, 0xe425, 0xe426, + 0xe427, 0xe428, 0xe429, 0xe42a, + // F050 + 0xe42b, 0xe42c, 0x263c, 0xe42d, + 0xe42e, 0xe42f, 0xe430, 0xe431, + 0xe432, 0xe433, 0x262a, 0x262f, + 0x0950, 0xe434, 0x2648, 0x2649, + // F060 + 0x264a, 0x264b, 0x264c, 0x264d, + 0x264e, 0x264f, 0x2650, 0x2651, + 0x2652, 0x2653, 0xe435, 0xe436, + 0xe437, 0xe438, 0xe439, 0xe43a, + // F070 + 0xe43b, 0xe43c, 0xe43d, 0xe43e, + 0xe43f, 0xe440, 0xe441, 0xe442, + 0xe443, 0x2353, 0x2318, 0xe444, + 0xe445, 0xe446, 0xe447, 0, + // F080 + 0xe448, 0xe449, 0xe44a, 0xe44b, + 0xe44c, 0xe44d, 0xe44e, 0xe44f, + 0xe450, 0xe451, 0xe452, 0xe453, + 0xe454, 0xe455, 0xe456, 0xe457, + // F090 + 0xe458, 0xe459, 0xe45a, 0xe45b, + 0xe45c, 0xe45d, 0xe45e, 0xe45f, + 0xe460, 0xe461, 0xe462, 0xe463, + 0xe464, 0xe465, 0xe466, 0xe467, + // F0a0 + 0xe468, 0xe469, 0xe46a, 0xe46b, + 0xe46c, 0xe46d, 0xe46e, 0xe46f, + 0xe470, 0xe471, 0xe472, 0xe473, + 0xe474, 0xe475, 0xe476, 0xe477, + // F0b0 + 0xe478, 0xe479, 0xe47a, 0xe47b, + 0xe47c, 0xe47d, 0xe47e, 0xe47f, + 0xe480, 0xe481, 0xe482, 0xe483, + 0xe484, 0xe485, 0xe486, 0xe487, + // F0c0 + 0xe488, 0xe489, 0xe48a, 0xe48b, + 0xe48c, 0xe48d, 0xe48e, 0xe48f, + 0xe490, 0xe491, 0xe492, 0xe493, + 0xe494, 0xe495, 0xe496, 0xe497, + // F0d0 + 0xe498, 0xe499, 0xe49a, 0xe49b, + 0xe49c, 0x232b, 0x2326, 0xe49d, + 0xe49e, 0xe49f, 0xe4a0, 0xe4a1, + 0xe4a2, 0xe4a3, 0xe4a4, 0xe4a5, + // F0e0 + 0xe4a6, 0xe4a7, 0xe4a8, 0xe4a9, + 0xe4aa, 0xe4ab, 0xe4ac, 0xe4ad, + 0xe4ae, 0xe4af, 0xe4b0, 0xe4b1, + 0xe4b2, 0xe4b3, 0xe4b4, 0xe4b5, + // F0f0 + 0xe4b6, 0xe4b7, 0xe4b8, 0xe4b9, + 0xe4ba, 0xe4bb, 0xe4bc, 0xe4bd, + 0xe4be, 0xe4bf, 0xe4c0, 0xe4c1, + 0xe4c2, 0xe4c3, 0xe4c4, 0xe4c5 +}; + +const sal_Unicode aWingDings2Tab[224] = +{ + // F020 + 0x0020, 0xe500, 0xe501, 0xe502, + 0xe503, 0xe504, 0xe505, 0xe506, + 0xe507, 0xe508, 0xe509, 0xe50a, + 0xe50b, 0xe50c, 0xe50d, 0xe50e, + // F030 + 0xe50f, 0xe510, 0xe511, 0xe512, + 0xe513, 0xe514, 0xe515, 0xe516, + 0xe517, 0xe518, 0xe519, 0xe51a, + 0xe51b, 0xe51c, 0xe51d, 0xe51e, + // F040 + 0xe51f, 0xe520, 0xe521, 0xe522, + 0xe523, 0xe524, 0xe525, 0xe526, + 0xe527, 0xe528, 0xe529, 0xe52a, + 0xe52b, 0xe52c, 0xe52d, 0xe52e, + // F050 + 0xe52f, 0xe530, 0xe531, 0xe532, + 0xe533, 0xe534, 0xe535, 0xe536, + 0xe537, 0x203D, 0x203D, 0x203D, + 0xe53b, 0xe53c, 0xe53d, 0xe53e, + // F060 + 0xe53f, 0xe540, 0xe541, 0xe542, + 0xe543, 0xe544, 0xe545, 0xe546, + 0xe547, 0x24EA, 0x2460, 0x2461, + 0x2462, 0x2463, 0x2464, 0x2465, + // F070 + 0x2466, 0x2467, 0x2468, 0x2469, + 0xE453, 0x278A, 0x278B, 0x278C, + 0x278D, 0x278E, 0x278F, 0x2790, + 0x2791, 0x2792, 0x2793, 0, + // F080 + 0x2609, 0x25cb, 0x263d, 0x263e, + 0xe55d, 0xe55e, 0xe55f, 0xe560, + 0xe561, 0xe562, 0xe563, 0xe564, + 0xe565, 0xe566, 0xe567, 0xe568, + // F090 + 0xe569, 0xe56a, 0xe56b, 0xe56c, + 0xe56d, 0xe56e, 0xe56f, 0xe570, + 0xe571, 0xe572, 0xe573, 0xe574, + 0xe575, 0, 0, 0xe578, + // F0a0 + 0xe579, 0xe57a, 0xe57b, 0xe57c, + 0, 0, 0, 0, + 0, 0, 0, 0xe584, + 0xe585, 0, 0xe586, 0, + // F0b0 + 0, 0, 0, 0, + 0xe58d, 0xe58e, 0xe58f, 0xe590, + 0, 0, 0xe593, 0xe594, + 0, 0, 0, 0xe587, + // F0c0 + 0xe599, 0xe59a, 0xe59b, 0xe59c, + 0xe59d, 0xe59e, 0xe59f, 0xe5a0, + 0xe5a1, 0xe5a2, 0xe5a3, 0xe5a4, + 0xe5a5, 0xe5a6, 0xe5a7, 0xe5a8, + // F0d0 + 0xe5a9, 0xe5aa, 0xe5ab, 0xe5ac, + 0xe5ad, 0xe5ae, 0xe5af, 0xe5b0, + 0xe5b1, 0xe5b2, 0xe5b3, 0xe5b4, + 0xe5b5, 0xe5b6, 0xe5b7, 0xe5b8, + // F0e0 + 0xe5b9, 0xe5ba, 0xe5bb, 0xe5bc, + 0xe5bd, 0xe5be, 0xe5bf, 0xe5c0, + 0xe5c1, 0xe5c2, 0xe5c3, 0xe5c4, + 0xe5c5, 0xe5c6, 0xe5c7, 0xe5c8, + // F0f0 + 0xe5c9, 0, 0xe5cb, 0xe477, + 0xe5cd, 0xe5ce, 0xe5cf, 0xe5d0, + 0x203b, 0x2042, 0, 0, + 0, 0, 0, 0 +}; + +const sal_Unicode aWingDings3Tab[224] = +{ + // F020 + 0x0020, 0xe600, 0xe601, 0xe602, + 0xe603, 0x2196, 0xe604, 0x2199, + 0xe605, 0xe606, 0xe607, 0xe608, + 0xe609, 0xe60a, 0xe60b, 0x21de, + // F030 + 0x21df, 0xe60c, 0xe60d, 0xe60e, + 0x21e2, 0x21e1, 0x21e3, 0x21af, + 0x21b5, 0xe60f, 0xe610, 0xe611, + 0xe612, 0xe613, 0xe614, 0xe615, + // F040 + 0xe616, 0xe617, 0xe618, 0xe619, + 0x21c4, 0x21c5, 0xe61a, 0xe61b, + 0x21c7, 0x21c9, 0x21c8, 0x21ca, + 0x21b6, 0x21b7, 0xe61c, 0xe61d, + // F050 + 0x21bb, 0x21ba, 0xe61e, 0x2324, + 0x2303, 0x2325, 0x2334, 0xe61f, + 0x21ea, 0xe620, 0xe621, 0xe622, + 0xe623, 0xe624, 0xe625, 0xe626, + // F060 + 0xe627, 0xe628, 0xe629, 0xe62a, + 0xe62b, 0xe62c, 0xe62d, 0xe62e, + 0xe62f, 0xe630, 0xe631, 0xe632, + 0xe633, 0xe634, 0xe635, 0xe636, + // F070 + 0xe637, 0xe638, 0x25b3, 0x25bd, + 0x25c0, 0x25b6, 0x25c1, 0x25b7, + 0x25e3, 0xe639, 0x25e4, 0x25e5, + 0x25c2, 0x25b8, 0xe63a, 0, + // F080 + 0xe63b, 0xe63c, 0xe63d, 0xe63e, + 0xe63f, 0xe640, 0xe641, 0xe642, + 0xe643, 0xe644, 0xe645, 0xe646, + 0xe647, 0xe648, 0xe649, 0xe64a, + // F090 + 0xe64b, 0xe64c, 0xe64d, 0xe64e, + 0xe64f, 0xe650, 0xe651, 0xe652, + 0xe653, 0xe654, 0xe655, 0xe656, + 0xe657, 0xe658, 0xe659, 0xe65a, + // F0a0 + 0xe65b, 0xe65c, 0xe65d, 0xe65e, + 0xe65f, 0xe660, 0xe661, 0xe662, + 0xe663, 0xe664, 0xe665, 0xe666, + 0xe667, 0xe668, 0xe669, 0xe66a, + // F0b0 + 0xe66b, 0xe66c, 0xe66d, 0xe66e, + 0xe66f, 0xe670, 0xe671, 0xe672, + 0xe673, 0xe674, 0xe675, 0xe676, + 0xe677, 0xe678, 0xe679, 0xe67a, + // F0c0 + 0xe67b, 0xe67c, 0xe67d, 0xe67e, + 0xe67f, 0xe680, 0xe681, 0xe682, + 0xe683, 0xe684, 0xe685, 0xe686, + 0xe687, 0xe688, 0xe689, 0xe68a, + // F0d0 + 0xe68b, 0xe68c, 0xe68d, 0xe68e, + 0xe68f, 0xe690, 0xe691, 0xe692, + 0xe693, 0xe694, 0xe695, 0xe696, + 0xe697, 0xe698, 0xe699, 0xe69a, + // F0e0 + 0xe69b, 0xe69c, 0xe69d, 0xe69e, + 0xe69f, 0xe6a0, 0xe6a1, 0xe6a2, + 0xe6a3, 0xe6a4, 0xe6a5, 0xe6a6, + 0xe6a7, 0xe6a8, 0xe6a9, 0xe6aa, + // F0f0 + 0xe6ab, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0 +}; + +const sal_Unicode aWebDingsTab[224] = +{ + // F020 + 0x0020, 0xe300, 0xe301, 0xe302, + 0xe303, 0xe304, 0xe305, 0xe306, + 0xe307, 0xe308, 0xe309, 0xe30a, + 0xe30b, 0xe30c, 0xe30d, 0xe30e, + // F030 + 0xe30f, 0xe310, 0xe311, 0xe312, + 0xe313, 0xe314, 0xe315, 0xe316, + 0xe317, 0xe318, 0xe319, 0xe31a, + 0xe31b, 0xe31c, 0xe31d, 0xe31e, + // F040 + 0xe31f, 0xe320, 0xe321, 0xe322, + 0xe323, 0xe324, 0xe325, 0xe326, + 0xe327, 0xe328, 0xe329, 0xe32a, + 0xe32b, 0xe32c, 0xe32d, 0xe32e, + // F050 + 0xe32f, 0xe330, 0xe331, 0xe332, + 0xe333, 0xe334, 0xe335, 0xe336, + 0xe337, 0xe338, 0xe339, 0xe33a, + 0xe33b, 0xe33c, 0xe33d, 0xe33e, + // F060 + 0xe33f, 0xe340, 0xe341, 0xe342, + 0xe343, 0xe344, 0xe345, 0xe346, + 0xe347, 0xe348, 0xe349, 0xe34a, + 0xe34b, 0xe34c, 0xe34d, 0xe34e, + // F070 + 0xe34f, 0xe350, 0xe351, 0xe352, + 0xe353, 0xe354, 0xe355, 0xe356, + 0xe357, 0xe358, 0xe359, 0xe35a, + 0xe35b, 0xe35c, 0xe35d, 0, + // F080 + 0xe35e, 0xe35f, 0xe360, 0xe361, + 0xe362, 0xe363, 0xe364, 0xe365, + 0xe366, 0xe367, 0xe368, 0xe369, + 0xe36a, 0xe36b, 0xe36c, 0xe36d, + // F090 + 0xe36e, 0xe36f, 0xe370, 0xe371, + 0xe372, 0xe373, 0xe374, 0xe375, + 0xe376, 0xe377, 0xe378, 0xe379, + 0xe37a, 0xe37b, 0xe37c, 0xe37d, + // F0a0 + 0xe37e, 0xe37f, 0xe380, 0xe381, + 0xe382, 0xe383, 0xe384, 0xe385, + 0xe386, 0xe387, 0xe388, 0xe389, + 0xe38a, 0xe38b, 0xe38c, 0xe38d, + // F0b0 + 0xe38e, 0xe38f, 0xe390, 0xe391, + 0xe392, 0xe393, 0xe394, 0xe395, + 0xe396, 0xe397, 0xe398, 0xe399, + 0xe39a, 0xe39b, 0xe39c, 0xe39d, + // F0c0 + 0xe39e, 0xe39f, 0xe3a0, 0xe3a1, + 0xe3a2, 0xe3a3, 0xe3a4, 0xe3a5, + 0xe3a6, 0xe3a7, 0xe3a8, 0xe3a9, + 0xe3aa, 0xe3ab, 0xe3ac, 0xe3ad, + // F0d0 + 0xe3ae, 0xe3af, 0xe3b0, 0xe3b1, + 0xe3b2, 0xe3b3, 0xe3b4, 0xe3b5, + 0xe3b6, 0xe3b7, 0xe3b8, 0xe3b9, + 0xe3ba, 0xe3bb, 0xe3bc, 0xe3bd, + // F0e0 + 0xe3be, 0xe3bf, 0xe3c0, 0xe3c1, + 0xe3c2, 0xe3c3, 0xe3c4, 0xe3c5, + 0xe3c6, 0xe3c7, 0xe3c8, 0xe3c9, + 0xe3ca, 0xe3cb, 0xe3cd, 0xe3ce, + // F0f0 + 0xe3cf, 0xe3d0, 0xe3d1, 0xe3d2, + 0xe3d3, 0xe3d4, 0xe3d5, 0xe3d6, + 0xe3d7, 0xe3d8, 0xe3d9, 0xe3da, + 0xe3db, 0xe3dc, 0xe3dd, 0xe3de +}; + +// See http://www.iana.org/assignments/character-sets/character-sets.xml +// See ftp://ftp.unicode.org/Public/MAPPINGS/VENDORS/ADOBE/symbol.txt + +const sal_Unicode aAdobeSymbolTab[224] = +{ +//TODO: + // F020 + 0x0020, 0xe100, 0xe101, 0xe102, + 0xe103, 0xe104, 0xe16a, 0xe105, + 0xe106, 0xe107, 0xe108, 0xe109, + 0xe10a, 0xe10b, 0xe10c, 0xe10d, + // F030 + 0x0030, 0x0031, 0x0032, 0x0033, + 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0xe10e, 0xe10f, + 0xe110, 0xe111, 0xe112, 0x003f, + // F040 + 0xe113, 0x0391, 0x0392, 0x03a7, + 0x0394, 0x0395, 0x03a6, 0x0393, + 0x0397, 0x0399, 0x03d1, 0x039a, + 0x039b, 0x039c, 0x039d, 0x039f, + // F050 + 0x03a0, 0x0398, 0x03a1, 0x03a3, + 0x03a4, 0x03a5, 0x03c2, 0x03a9, + 0x039e, 0x03a8, 0x0396, 0xe114, + 0x2234, 0xe115, 0xe116, 0x005f, + // F060 + 0x00af, 0x03b1, 0x03b2, 0x03c7, + 0x03b4, 0x03b5, 0x03d5, 0x03b3, + 0x03b7, 0x03b9, 0x03c6, 0x03ba, + 0x03bb, 0x03bc, 0x03bd, 0x03bf, + // F070 + 0x03c0, 0x03b8, 0x03c1, 0x03c3, + 0x03c4, 0x03c5, 0x03d6, 0x03c9, + 0x03be, 0x03c8, 0x03b6, 0xe117, + 0x007c, 0xe118, 0xe119, 0, + // F080 + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + // F090 + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + // F0a0 + 0xe11a, 0x03d2, 0x2032, 0xe11b, + 0x2215, 0xe11c, 0xe11d, 0xe11e, + 0x2666, 0xe11f, 0xe120, 0xe121, + 0xe122, 0xe123, 0xe124, 0xe125, + // F0b0 + 0xe126, 0xe127, 0x2033, 0xe128, + 0xe129, 0xe12a, 0xe12b, 0xe12c, + 0xe12d, 0xe12e, 0xe12f, 0xe130, + 0xe131, 0x2502, 0x2500, 0x21b2, + // F0c0 + 0xe132, 0xe133, 0xe134, 0xe135, + 0xe136, 0xe137, 0xe138, 0xe139, + 0xe13a, 0xe13b, 0xe13c, 0xe13d, + 0xe13e, 0xe13f, 0xe140, 0xe141, + // F0d0 + 0x2220, 0xe142, 0x00ae, 0x00a9, + 0xe143, 0xe144, 0xe145, 0xe146, + 0xe147, 0xe148, 0xe149, 0xe14a, + 0xe14b, 0x21d1, 0xe14c, 0x21d3, + // F0e0 + 0x25ca, 0xe14d, 0xe14e, 0xe14f, + 0xe150, 0xe151, 0xf8eb, 0xf8ec, + 0xf8ed, 0xf8ee, 0xf8ef, 0xf8f0, + 0xf8f1, 0xf8f2, 0xf8f3, 0xf8f4, + // F0f0 + 0, 0x232a, 0x222b, 0x2320, + 0xf8f5, 0x2321, 0xf8f6, 0xf8f7, + 0xf8f8, 0xf8f9, 0xf8fa, 0xf8fb, + 0xf8fc, 0xf8fd, 0xf8fe, 0, +}; + +const sal_Unicode aMonotypeSortsTab[224] = +{ + // F020 + 0x0020, 0x2701, 0xe200, 0x2703, + 0x2704, 0xe201, 0x2706, 0xe202, + 0xe203, 0xe203, 0xe204, 0xe205, + 0x270c, 0xe206, 0xe207, 0xe208, + // F030 + 0x2710, 0x2711, 0x2712, 0xe209, + 0xe20a, 0x2715, 0x2716, 0xe20b, + 0xe20c, 0xe20d, 0xe20e, 0xe20f, + 0xe210, 0x271d, 0x271e, 0x271f, + // F040 + 0x2720, 0xe211, 0xe212, 0xe213, + 0xe214, 0xe215, 0x2726, 0x2727, + 0x2605, 0x2729, 0x272a, 0xe216, + 0xe217, 0xe218, 0xe219, 0xe21a, + // F050 + 0xe21b, 0xe21c, 0x2732, 0xe21d, + 0xe21e, 0xe21f, 0xe220, 0xe221, + 0xe222, 0xe223, 0x273a, 0x273b, + 0x273c, 0x273d, 0x273e, 0xe224, + // F060 + 0x2740, 0x2741, 0x2742, 0x2743, + 0xe225, 0x2745, 0x2746, 0x2747, + 0x2748, 0x2749, 0x274a, 0x274b, + 0xe226, 0xe227, 0xe228, 0xe229, + // F070 + 0xe22a, 0xe22b, 0xe22c, 0x25b2, + 0x25bc, 0xe22d, 0xe22e, 0xe22f, + 0x2758, 0xe230, 0x275a, 0x275b, + 0x275c, 0x275d, 0x275e, 0, + // F080 + 0xe231, 0xe232, 0xe233, 0xe234, + 0xe235, 0xe236, 0xe237, 0xe238, + 0xe239, 0xe23a, 0xe23b, 0xe23c, + 0xe23d, 0xe23e, 0, 0, + // F090 + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + // F0a0 + 0, 0xe23f, 0xe240, 0x2763, + 0x2764, 0x2765, 0x2766, 0x2767, + 0xe241, 0xe242, 0xe243, 0xe244, + 0x2460, 0x2461, 0x2462, 0x2463, + // F0b0 + 0x2464, 0x2465, 0x2466, 0x2467, + 0x2468, 0x2469, 0x2776, 0x2777, + 0x2778, 0x2779, 0x277a, 0x277b, + 0x277c, 0x277d, 0x277e, 0x277f, + // F0c0 + 0xe245, 0xe246, 0xe247, 0xe248, + 0xe249, 0xe24a, 0xe24b, 0xe24c, + 0xe24d, 0xe24e, 0xe24f, 0xe250, + 0xe251, 0xe252, 0xe253, 0xe254, + // F0d0 + 0xe255, 0xe256, 0xe257, 0xe258, + 0xe259, 0xe25a, 0xe25b, 0xe25c, + 0xe25d, 0x2799, 0xe25e, 0x279b, + 0x279c, 0x279d, 0x279e, 0x279f, + // F0e0 + 0x27a0, 0x27a1, 0xe25f, 0x27a3, + 0x27a4, 0x27a5, 0x27a6, 0x27a7, + 0x27a8, 0x27a9, 0x27aa, 0x27ab, + 0x27ac, 0x27ad, 0x27ae, 0x27af, + // F0f0 + 0, 0x27b1, 0xe260, 0x27b3, + 0x27b4, 0x27b5, 0x27b6, 0x27b7, + 0xe261, 0x27b9, 0x27ba, 0x27bb, + 0x27bc, 0x27bd, 0x27be, 0, +}; + +const sal_Unicode aMTExtraTab[224] = +{ + // F020 + 0x0020, 0, 0, 0x0300, + 0x0302, 0x0303, 0x0307, 0, + 0x2323, 0x2322, 0, 0, + 0, 0, 0, 0, + // F030 + 0, 0xEC00, 0xEC01, 0xEC02, + 0xEC03, 0xEC0B, 0xEC04, 0xEC05, + 0xEC06, 0, 0x223C, 0x2243, + 0x22B2, 0x226A, 0x22B3, 0x226B, + // F040 + 0x225C, 0x2259, 0x2250, 0x2210, + 0x019B, 0xEC0E, 0xEC0F, 0xEC10, + 0xEC11, 0x2229, 0x2127, 0x2026, + 0x22EF, 0x22EE, 0x22F0, 0x22F1, + // F050 + 0x2225, 0x2235, 0x2221, 0x2222, + 0, 0x222A, 0x25B3, 0x25A1, + 0x25AD, 0x25B1, 0x2197, 0x2199, + 0xEB05, 0x2198, 0x2196, 0xEB06, + // F060 + 0x2035, 0x21A6, 0x2195, 0x21D5, + 0x25CB, 0x2299, 0x227B, 0xE98F, + 0x210F, 0xEE00, 0xEE01, 0, + 0x2113, 0x2213, 0xFFFD, 0x2218, + // F070 + 0x227A, 0xEB1A, 0x20D7, 0x20D6, + 0x20E1, 0xEB00, 0x20D1, 0x20D0, + 0xEB19, 0, 0, 0xFE38, + 0xEC0C, 0xFE37, 0xEC0D, 0, + // F080 + 0x21C4, 0xEB01, 0xEB02, 0x21CC, + 0xEB03, 0xEB04, 0x21C0, 0x21BD, + 0xF8E7, 0, 0, 0, + 0, 0, 0, 0, + // F090 + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + // F0a0 + 0, 0x211D, 0x2124, 0x2102, + 0x211A, 0x2115, 0, 0x301A, + 0x301B, 0xEC22, 0xEC23, 0xEC24, + 0xEC25, 0xEC26, 0xEC27, 0, + // F0b0 + 0xEE04, 0xEE05, 0xEE06, 0, + 0, 0xEE07, 0xEE08, 0xEE09, + 0, 0, 0xEE0A, 0xEE0B, + 0xEE0C, 0, 0, 0, + // F0c0 + 0xEE0D, 0xEE0E, 0xEE0F, 0xEE10, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + // F0d0 + 0, 0xEE11, 0xEE12, 0xEE13, + 0, 0xEE15, 0xEE16, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + // F0e0 + 0, 0, 0, 0, + 0, 0, 0, 0xF8FF, + 0x0160, 0x00DD, 0x00DE, 0x00D0, + 0x0161, 0x00FD, 0x00FE, 0x00F0, + // F0f0 + 0xFB01, 0xFB02, 0x0131, 0x00B9, + 0x00B2, 0x00B3, 0x00BD, 0x00BC, + 0x00BE, 0x2044, 0x00A6, 0x02DD, + 0x02D8, 0x02C7, 0x02DA, 0x02DB, +}; + +const sal_Unicode aAdobeSymbolToAppleSymbolTab[224] = +{ + // F020 + 0x0020, 0x0021, 0x2200, 0x0023, + 0x2203, 0x0025, 0x0026, 0x220D, + 0x0028, 0x0029, 0x2217, 0x002B, + 0x002C, 0x2212, 0x002E, 0x002F, + // F030 + 0x0030, 0x0031, 0x0032, 0x0033, + 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003A, 0x003B, + 0x003C, 0x003D, 0x003E, 0x003F, + // F040 + 0x2245, 0x0391, 0x0392, 0x03A7, + 0x0394, 0x0395, 0x03A6, 0x0393, + 0x0397, 0x0399, 0x03D1, 0x039A, + 0x039B, 0x039C, 0x039D, 0x039F, + // F050 + 0x03A0, 0x0398, 0x03A1, 0x03A3, + 0x03A4, 0x03A5, 0x03C2, 0x03A9, + 0x039E, 0x03A8, 0x0396, 0x005B, + 0x2234, 0x005D, 0x22A5, 0x005F, + // F060 + 0xF8E5, 0x03B1, 0x03B2, 0x03C7, + 0x03B4, 0x03B5, 0x03C6, 0x03B3, + 0x03B7, 0x03B9, 0x03D5, 0x03BA, + 0x03BB, 0x03BC, 0x03BD, 0x03BF, + // F070 + 0x03C0, 0x03B8, 0x03C1, 0x03C3, + 0x03C4, 0x03C5, 0x03D6, 0x03C9, + 0x03BE, 0x03C8, 0x03B6, 0x007B, + 0x007C, 0x007D, 0x223C, 0x007F, + // F080 + 0x0080, 0x0081, 0x0082, 0x0083, + 0x0084, 0x0085, 0x0086, 0x0087, + 0x0088, 0x0089, 0x008A, 0x008B, + 0x008C, 0x008D, 0x008E, 0x008F, + // F090 + 0x0090, 0x0091, 0x0092, 0x0093, + 0x0094, 0x0095, 0x0096, 0x0097, + 0x0098, 0x0099, 0x009A, 0x009B, + 0x009C, 0x009D, 0x009E, 0x009F, + // F0a0 + 0x20AC, 0x03D2, 0x2032, 0x2264, + 0x2044, 0x221E, 0x0192, 0x2663, + 0x2666, 0x2665, 0x2660, 0x2194, + 0x2190, 0x2191, 0x2192, 0x2193, + // F0b0 + 0x00B0, 0x00B1, 0x2033, 0x2065, + 0x00D7, 0x221D, 0x2202, 0x2022, + 0x00F7, 0x2260, 0x2261, 0x2248, + 0x2026, 0x23D0, 0x23AF, 0x21B5, + // F0c0 + 0x2135, 0x2111, 0x211C, 0x2118, + 0x2297, 0x2295, 0x2205, 0x2229, + 0x222A, 0x2283, 0x2287, 0x2284, + 0x2282, 0x2286, 0x2208, 0x2209, + // F0d0 + 0x2220, 0x2207, 0x00AE, 0x00A9, + 0x2122, 0x220F, 0x221A, 0x22C5, + 0x00AC, 0x2227, 0x2228, 0x21D4, + 0x21D0, 0x21D1, 0x21D2, 0x21D3, + // F0e0 + 0x25CA, 0x3008, 0x00AE, 0x00A9, + 0x2122, 0x2211, 0x239B, 0x239C, + 0x239D, 0x23A1, 0x23A2, 0x23A3, + 0x23A7, 0x23A8, 0x23A9, 0x23AA, + // F0f0 + 0xF8FF, 0x3009, 0x222B, 0x2320, + 0x23AE, 0x2321, 0x239E, 0x239F, + 0x23A0, 0x23A4, 0x23A5, 0x23A6, + 0x23AB, 0x23AC, 0x23AD, 0x00FF +}; + +static sal_Unicode ImplStarSymbolToStarBats( sal_Unicode c ) +{ + switch ( c ) + { + case 0x00A2: c = 0xF0E0; break; + case 0x00A4: c = 0xF0E1; break; + case 0x00A5: c = 0xF0E2; break; + case 0x00A7: c = 0xF0A7; break; + case 0x00AB: c = 0xF0AB; break; + case 0x00B6: c = 0xF0DE; break; + case 0x00BB: c = 0xF0BB; break; + + case 0x0152: c = 0xF08C; break; + case 0x0153: c = 0xF09C; break; + case 0x0160: c = 0xF08A; break; + case 0x0161: c = 0xF09A; break; + case 0x0178: c = 0xF09F; break; + case 0x017D: c = 0xF08E; break; + case 0x017E: c = 0xF09E; break; + + case 0x0192: c = 0xF083; break; + case 0x02C6: c = 0xF088; break; + case 0x02DC: c = 0xF098; break; + case 0x2013: c = 0xF096; break; + case 0x2014: c = 0xF097; break; + case 0x2018: c = 0xF091; break; + + case 0x2019: c = 0xF092; break; + case 0x201A: c = 0xF082; break; + case 0x201C: c = 0xF093; break; + case 0x201D: c = 0xF094; break; + case 0x201E: c = 0xF084; break; + case 0x2020: c = 0xF086; break; + + case 0x2021: c = 0xF087; break; + case 0x2022: c = 0xF095; break; + case 0x2026: c = 0xF085; break; + case 0x2030: c = 0xF089; break; + case 0x2039: c = 0xF08B; break; + case 0x203A: c = 0xF09B; break; + + case 0x20A1: c = 0xF0E4; break; + case 0x20A2: c = 0xF0E5; break; + case 0x20A3: c = 0xF0E6; break; + case 0x20A4: c = 0xF0E7; break; + case 0x20A8: c = 0xF0EA; break; + case 0x20A9: c = 0xF0E8; break; + case 0x20AB: c = 0xF0E9; break; + case 0x20AC: c = 0xF080; break; + + case 0x2122: c = 0xF099; break; + case 0x2190: c = 0xF0B7; break; + case 0x2191: c = 0xF0B8; break; + case 0x2192: c = 0xF0B5; break; + case 0x2193: c = 0xF0B6; break; + case 0x2194: c = 0xF0D8; break; + case 0x2195: c = 0xF0D9; break; + + case 0x21E4: c = 0xF0B4; break; + case 0x21E5: c = 0xF0B3; break; + case 0x21E6: c = 0xF0A4; break; + case 0x21E7: c = 0xF0B2; break; + case 0x21E8: c = 0xF0AF; break; + case 0x21E9: c = 0xF0A3; break; + + case 0x25A0: c = 0xF024; break; + case 0x25A1: c = 0xF025; break; + case 0x25B4: c = 0xF0C5; break; + case 0x25B5: c = 0xF0C7; break; + case 0x25BE: c = 0xF0C4; break; + case 0x25BF: c = 0xF0C6; break; + case 0x25C6: c = 0xF043; break; + case 0x25CF: c = 0xF022; break; + case 0x25D7: c = 0xF0D0; break; + + case 0x260E: c = 0xF074; break; + case 0x2611: c = 0xF034; break; + case 0x2612: c = 0xF033; break; + case 0x261B: c = 0xF036; break; + case 0x261E: c = 0xF07D; break; + case 0x2639: c = 0xF0AD; break; + case 0x263A: c = 0xF021; break; + + case 0x2702: c = 0xF0CB; break; + case 0x2708: c = 0xF0CC; break; + case 0x270D: c = 0xF07E; break; + case 0x270E: c = 0xF038; break; + + case 0x2713: c = 0xF039; break; + case 0x2714: c = 0xF03A; break; + case 0x2717: c = 0xF04F; break; + case 0x2718: c = 0xF050; break; + case 0x2719: c = 0xF051; break; + case 0x271A: c = 0xF052; break; + case 0x271B: c = 0xF053; break; + case 0x271C: c = 0xF054; break; + + case 0x2721: c = 0xF0CD; break; + case 0x2722: c = 0xF044; break; + case 0x2723: c = 0xF045; break; + case 0x2724: c = 0xF046; break; + case 0x2725: c = 0xF047; break; + case 0x272B: c = 0xF055; break; + case 0x272C: c = 0xF056; break; + case 0x272D: c = 0xF057; break; + case 0x272E: c = 0xF058; break; + case 0x272F: c = 0xF059; break; + + case 0x2730: c = 0xF05A; break; + case 0x2733: c = 0xF048; break; + case 0x2734: c = 0xF049; break; + case 0x2735: c = 0xF04A; break; + case 0x2736: c = 0xF04B; break; + case 0x2737: c = 0xF04C; break; + case 0x2738: c = 0xF04D; break; + case 0x2739: c = 0xF04E; break; + case 0x273F: c = 0xF0CE; break; + + case 0x2744: c = 0xF0CF; break; + case 0x274D: c = 0xF023; break; + case 0x274F: c = 0xF03E; break; + case 0x2750: c = 0xF03F; break; + case 0x2751: c = 0xF027; break; + case 0x2752: c = 0xF028; break; + case 0x2756: c = 0xF02C; break; + case 0x2759: c = 0xF0D1; break; + case 0x2762: c = 0xF0D4; break; + + case 0x2780: c = 0xF068; break; + case 0x2781: c = 0xF069; break; + case 0x2782: c = 0xF06A; break; + case 0x2783: c = 0xF06B; break; + case 0x2784: c = 0xF06C; break; + case 0x2785: c = 0xF06D; break; + case 0x2786: c = 0xF06E; break; + case 0x2787: c = 0xF06F; break; + case 0x2788: c = 0xF070; break; + case 0x2789: c = 0xF071; break; + case 0x278A: c = 0xF05D; break; + case 0x278B: c = 0xF05E; break; + case 0x278C: c = 0xF05F; break; + case 0x278D: c = 0xF060; break; + case 0x278E: c = 0xF061; break; + case 0x278F: c = 0xF062; break; + + case 0x2790: c = 0xF063; break; + case 0x2791: c = 0xF064; break; + case 0x2792: c = 0xF065; break; + case 0x2793: c = 0xF066; break; + case 0x2794: c = 0xF031; break; + case 0x2798: c = 0xF0DA; break; + case 0x279A: c = 0xF0DB; break; + + case 0x27A2: c = 0xF02F; break; + case 0x27B2: c = 0xF035; break; + case 0x27B8: c = 0xF0DC; break; + + case 0xE000: c = 0xF000+38; break; + case 0xE001: c = 0xF000+41; break; + case 0xE002: c = 0xF000+42; break; + case 0xE003: c = 0xF000+43; break; + case 0xE004: c = 0xF000+45; break; + case 0xE005: c = 0xF000+46; break; + case 0xE006: c = 0xF000+48; break; + case 0xE007: c = 0xF000+57; break; + + case 0xE008: c = 0xF000+59; break; + case 0xE009: c = 0xF000+60; break; + case 0xE00a: c = 0xF000+61; break; + case 0xE00b: c = 0xF000+64; break; + case 0xE00c: c = 0xF000+65; break; + case 0xE00d: c = 0xF000+66; break; + case 0xE00e: c = 0xF000+67; break; + case 0xE00f: c = 0xF000+92; break; + + case 0xE010: c = 0xF000+103; break; + case 0xE011: c = 0xF000+114; break; + case 0xE012: c = 0xF000+115; break; + case 0xE013: c = 0xF000+117; break; + case 0xE014: c = 0xF000+118; break; + case 0xE015: c = 0xF000+119; break; + case 0xE016: c = 0xF000+120; break; + case 0xE017: c = 0xF000+121; break; + + case 0xE018: c = 0xF000+122; break; + case 0xE019: c = 0xF000+123; break; + case 0xE01a: c = 0xF000+124; break; + case 0xE01b: c = 0xF000+126; break; + case 0xE01c: c = 0xF000+136; break; + case 0xE01d: c = 0xF000+155; break; + case 0xE01e: c = 0xF000+165; break; + case 0xE01f: c = 0xF000+166; break; + + case 0xE020: c = 0xF000+168; break; + case 0xE021: c = 0xF000+169; break; + case 0xE022: c = 0xF000+170; break; + case 0xE023: c = 0xF000+172; break; + case 0xE024: c = 0xF000+174; break; + case 0xE025: c = 0xF000+175; break; + case 0xE026: c = 0xF000+176; break; + case 0xE027: c = 0xF000+177; break; + + case 0xE028: c = 0xF000+178; break; + case 0xE029: c = 0xF000+185; break; + case 0xE02a: c = 0xF000+186; break; + case 0xE02b: c = 0xF000+188; break; + case 0xE02c: c = 0xF000+189; break; + case 0xE02d: c = 0xF000+190; break; + case 0xE02e: c = 0xF000+191; break; + case 0xE02f: c = 0xF000+192; break; + + case 0xE030: c = 0xF000+193; break; + case 0xE031: c = 0xF000+194; break; + case 0xE032: c = 0xF000+195; break; + case 0xE033: c = 0xF000+200; break; + case 0xE034: c = 0xF000+201; break; + case 0xE035: c = 0xF000+202; break; + case 0xE036: c = 0xF000+210; break; + case 0xE037: c = 0xF000+211; break; + + case 0xE038: c = 0xF000+227; break; + case 0xE039: c = 0xF000+235; break; + case 0xE03a: c = 0xF000+255; break; + + default: c = 0; break; + } + + return c; +} + +namespace { + +enum SymbolFont +{ + Symbol=1, Wingdings=2, MonotypeSorts=4, Webdings=8, Wingdings2=16, + Wingdings3=32, MTExtra=64, TimesNewRoman=128 +}; + +} + +const char * const aSymbolNames[] = +{ + "Symbol", "Wingdings", "Monotype Sorts", "Webdings", "Wingdings 2", + "Wingdings 3", "MT Extra", "Times New Roman" +}; + +namespace { + +struct SymbolEntry +{ + sal_uInt8 cIndex; + enum SymbolFont eFont; +}; + +class StarSymbolToMSMultiFontImpl : public StarSymbolToMSMultiFont +{ +private: + ::std::multimap<sal_Unicode, SymbolEntry> maMagicMap; +public: + explicit StarSymbolToMSMultiFontImpl(); + OUString ConvertChar(sal_Unicode &rChar) override; +}; + +struct ExtraTable { sal_Unicode cStar; sal_uInt8 cMS;}; + +} + +ExtraTable const aWingDingsExtraTab[] = +{ + {0x25cf, 0x6C}, {0x2714, 0xFC}, {0x2717, 0xFB}, {0x2794, 0xE8}, + {0x27a2, 0xD8}, {0xe000, 0x6F}, {0xe001, 0x73}, {0xe002, 0x74}, + {0xe003, 0x75}, {0xe004, 0x77}, {0xe005, 0xA6}, {0xe006, 0xE0}, + {0xe007, 0xFC}, {0xe008, 0x6C}, {0xe009, 0x6D}, {0xe00a, 0x6E}, + {0xe00b, 0x72}, {0xe00c, 0x75}, {0xe00d, 0x76}, {0xe00e, 0x74}, + {0xe00f, 0x8B}, {0xe010, 0x80}, {0xe011, 0x2B}, {0xe012, 0x3A}, + {0xe013, 0x5D}, {0xe014, 0x29}, {0xe015, 0x3A}, {0xe016, 0x3C}, + {0xe017, 0x38}, {0xe018, 0x3A}, {0xe019, 0x2A}, {0xe01a, 0x2B}, + {0xe01b, 0x3F}, {0xe01c, 0x9F}, {0xe01d, 0x80}, {0xe01e, 0x8B}, + {0xe023, 0x4A}, {0xe025, 0xF0}, {0xe026, 0xF2}, {0xe027, 0xEF}, + {0xe028, 0xF1}, {0xe029, 0x52}, {0xe02a, 0x29}, {0xe02b, 0xE0}, + {0xe02c, 0xE2}, {0xe02d, 0xDF}, {0xe02e, 0xE1}, {0xe02f, 0xAC}, + {0xe030, 0xAD}, {0xe031, 0xAE}, {0xe032, 0x7C}, {0xe033, 0x43}, + {0xe034, 0x4D}, {0xe0aa, 0x71}, {0xe422, 0x44} +}; + +ExtraTable const aSymbolExtraTab2[] = +{ + {0x0020, 0x20}, {0x00A0, 0x20}, {0x0021, 0x21}, {0x2200, 0x22}, + {0x0023, 0x23}, {0x2203, 0x24}, {0x0025, 0x25}, {0x0026, 0x26}, + {0x220B, 0x27}, {0x0028, 0x28}, {0x0029, 0x29}, {0x2217, 0x2A}, + {0x002B, 0x2B}, {0x002C, 0x2C}, {0x2212, 0x2D}, {0x002E, 0x2E}, + {0x002F, 0x2F}, {0x003A, 0x3A}, {0x003B, 0x3B}, {0x003C, 0x3C}, + {0x003D, 0x3D}, {0x003E, 0x3E}, {0x2245, 0x40}, {0x2206, 0x44}, + {0x2126, 0x57}, {0x005B, 0x5B}, {0x005D, 0x5D}, {0x22A5, 0x5E}, + {0x03C6, 0x66}, {0x03D5, 0x6A}, {0x00B5, 0x6D}, {0x007B, 0x7B}, + {0x007C, 0x7C}, {0x007D, 0x7D}, {0x223C, 0x7E}, {0x20AC, 0xA0}, + {0x2032, 0xA2}, {0x2264, 0xA3}, {0x2044, 0xA4}, {0x221E, 0xA5}, + {0x0192, 0xA6}, {0x2663, 0xA7}, {0x2665, 0xA9}, {0x2660, 0xAA}, + {0x2194, 0xAB}, {0x2190, 0xAC}, {0x2191, 0xAD}, {0x2192, 0xAE}, + {0x2193, 0xAF}, {0x00B0, 0xB0}, {0x00B1, 0xB1}, {0x2265, 0xB3}, + {0x00D7, 0xB4}, {0x221D, 0xB5}, {0x2202, 0xB6}, {0x2022, 0xB7}, + {0x00F7, 0xB8}, {0x2260, 0xB9}, {0x2261, 0xBA}, {0x2248, 0xBB}, + {0x2026, 0xBC}, {0x21B5, 0xBF}, {0x2135, 0xC0}, {0x2111, 0xC1}, + {0x211C, 0xC2}, {0x2118, 0xC3}, {0x2297, 0xC4}, {0x2295, 0xC5}, + {0x2205, 0xC6}, {0x2229, 0xC7}, {0x222A, 0xC8}, {0x2283, 0xC9}, + {0x2287, 0xCA}, {0x2284, 0xCB}, {0x2282, 0xCC}, {0x2286, 0xCD}, + {0x2208, 0xCE}, {0x2209, 0xCF}, {0x2207, 0xD1}, {0x220F, 0xD5}, + {0x221A, 0xD6}, {0x22C5, 0xD7}, {0x00AC, 0xD8}, {0x2227, 0xD9}, + {0x2228, 0xDA}, {0x21D4, 0xDB}, {0x21D0, 0xDC}, {0x21D2, 0xDE}, + {0x2329, 0xE1}, {0x2211, 0xE5}, {0x232A, 0xF1}, {0x222B, 0xF2}, + {0x2320, 0xF3}, {0x2321, 0xF5}, {0x2013, 0x2D} +}; + +ExtraTable const aSymbolExtraTab[] = +{ + {0xe021, 0xD3}, {0xe024, 0xD2}, {0xe035, 0x20}, {0xe036, 0x28}, + {0xe037, 0x29}, {0xe039, 0x20}, {0xe083, 0x2B}, {0xe084, 0x3C}, + {0xe085, 0x3E}, {0xe086, 0xA3}, {0xe087, 0xB3}, {0xe089, 0xCE}, + {0xe08a, 0xA6}, {0xe08c, 0xAE}, {0xe08d, 0xD6}, {0xe08e, 0xD6}, + {0xe08f, 0xD6}, {0xe094, 0xA2}, {0xe09e, 0x28}, {0xe09f, 0x29}, + {0xe0a0, 0xD0}, {0xe0a6, 0xA2}, {0xe0a7, 0x7C}, {0xe0a8, 0x2F}, + {0xe0ab, 0x7C}, {0xe0ac, 0x47}, {0xe0ad, 0x44}, {0xe0ae, 0x51}, + {0xe0af, 0x4C}, {0xe0b0, 0x58}, {0xe0b1, 0x50}, {0xe0b2, 0x53}, + {0xe0b3, 0x55}, {0xe0b4, 0x46}, {0xe0b5, 0x59}, {0xe0b6, 0x57}, + {0xe0b7, 0x61}, {0xe0b8, 0x62}, {0xe0b9, 0x67}, {0xe0ba, 0x64}, + {0xe0bb, 0x65}, {0xe0bc, 0x7A}, {0xe0bd, 0x68}, {0xe0be, 0x71}, + {0xe0bf, 0x69}, {0xe0c0, 0x6B}, {0xe0c1, 0x6C}, {0xe0c2, 0x6D}, + {0xe0c3, 0x6E}, {0xe0c4, 0x78}, {0xe0c5, 0x6F}, {0xe0c6, 0x70}, + {0xe0c7, 0x72}, {0xe0c8, 0x73}, {0xe0c9, 0x74}, {0xe0ca, 0x75}, + {0xe0cb, 0x66}, {0xe0cc, 0x63}, {0xe0cd, 0x79}, {0xe0ce, 0x77}, + {0xe0cf, 0x65}, {0xe0d0, 0x4A}, {0xe0d1, 0x76}, {0xe0d3, 0x56}, + {0xe0d4, 0x6A}, {0xe0d5, 0xB6}, {0xe0d6, 0x69}, {0xe0db, 0xAC}, + {0xe0dc, 0xAD}, {0xe0dd, 0xAF} +}; + +ExtraTable const aTNRExtraTab[] = +{ + {0xe021, 0xA9}, + {0xe022, 0x40}, + {0xe024, 0xAE}, + {0xe035, 0x20}, + {0xe036, '('}, + {0xe037, ')'}, + {0xe039, 0x20}, + {0xe03a, 0x80}, + {0xe080, 0x89}, + {0xe083, '+'}, + {0xe084, '<'}, + {0xe085, '>'}, + {0xe0a9, '\\'} +}; + +StarSymbolToMSMultiFontImpl::StarSymbolToMSMultiFontImpl() +{ + struct ConvertTable + { + enum SymbolFont meFont; + const sal_Unicode* pTab; + }; + + //In order of preference + static const ConvertTable aConservativeTable[] = + { + {Symbol, aAdobeSymbolTab}, + {Wingdings, aWingDingsTab}, + {MonotypeSorts, aMonotypeSortsTab}, + {Webdings, aWebDingsTab}, + {Wingdings2, aWingDings2Tab}, + {Wingdings3, aWingDings3Tab}, + {MTExtra, aMTExtraTab} + }; + + struct ExtendedConvertTable + { + enum SymbolFont meFont; + const ExtraTable *mpTable; + size_t mnSize; + ExtendedConvertTable(SymbolFont eFont, const ExtraTable *pTable, + size_t nSize) + : meFont(eFont), mpTable(pTable), mnSize(nSize) {} + }; + + //Reverse map from a given starsymbol char to exact matches in ms symbol + //fonts. + for (auto const & r: aConservativeTable) + { + SymbolEntry aEntry; + aEntry.eFont = r.meFont; + for (aEntry.cIndex = 0xFF; aEntry.cIndex >= 0x20; --aEntry.cIndex) + { + if (sal_Unicode cChar = r.pTab[aEntry.cIndex-0x20]) + maMagicMap.emplace(cChar, aEntry); + } + } + + //In order of preference + static const ExtendedConvertTable aAggressiveTable[] = + { + ExtendedConvertTable(Symbol, aSymbolExtraTab2, + sizeof(aSymbolExtraTab2)), + ExtendedConvertTable(Symbol, aSymbolExtraTab, + sizeof(aSymbolExtraTab)), + ExtendedConvertTable(Wingdings, aWingDingsExtraTab, + sizeof(aWingDingsExtraTab)), + ExtendedConvertTable(TimesNewRoman, aTNRExtraTab, + sizeof(aTNRExtraTab)) + }; + + //Allow extra conversions that are not perfect, but "good enough" + for (auto const & r: aAggressiveTable) + { + SymbolEntry aEntry; + aEntry.eFont = r.meFont; + for (int j = r.mnSize / sizeof(r.mpTable[0]) - 1; j >=0; --j) + { + aEntry.cIndex = r.mpTable[j].cMS; + maMagicMap.emplace(r.mpTable[j].cStar, aEntry); + } + } +} + +static const char *SymbolFontToString(int nResult) +{ + const char * const *ppName = aSymbolNames; + int nI = Symbol; + while (nI <= nResult) + { + if (!(nI & nResult)) + nI = nI << 1; + else + break; + ppName++; + } + return *ppName; +} + +OUString StarSymbolToMSMultiFontImpl::ConvertChar(sal_Unicode &rChar) +{ + OUString sRet; + + ::std::multimap<sal_Unicode, SymbolEntry>::const_iterator aResult = + maMagicMap.find(rChar); + + if (aResult != maMagicMap.end()) + { + const SymbolEntry &rEntry = (*aResult).second; + const char* pc = SymbolFontToString(rEntry.eFont); + sRet = OUString(pc, strlen(pc), RTL_TEXTENCODING_ASCII_US); + rChar = rEntry.cIndex; + } + + return sRet; +} + +StarSymbolToMSMultiFont *CreateStarSymbolToMSMultiFont() +{ + return new StarSymbolToMSMultiFontImpl; +} + +sal_Unicode ConvertChar::RecodeChar( sal_Unicode cChar ) const +{ + sal_Unicode cRetVal = 0; + if( mpCvtFunc ) + { + // use a conversion function for recoding + cRetVal = mpCvtFunc( cChar ); + } + else + { + // use a conversion table for recoding + sal_Unicode cIndex = cChar; + // allow symbol aliasing + if( cIndex & 0xFF00 ) + cIndex -= 0xF000; + // recode the symbol + if( cIndex>=0x0020 && cIndex<=0x00FF ) + { + cRetVal = mpCvtTab[ cIndex - 0x0020 ]; + + if (!cRetVal && mpSubsFontName) + { + if ( IsOpenSymbol( OUString::createFromAscii(mpSubsFontName) ) ) + { + cRetVal = 0xE12C; + SAL_WARN( "unotools.misc", "Forcing a bullet substitution from 0x" << + OString::number(cChar, 16) << " to 0x" << + OString::number(cRetVal, 16)); + } + } + } + } + + return cRetVal ? cRetVal : cChar; +} + +// recode the string assuming the character codes are symbol codes +// from a traditional symbol font (i.e. U+F020..U+F0FF) +void ConvertChar::RecodeString( OUString& rStr, sal_Int32 nIndex, sal_Int32 nLen ) const +{ + sal_Int32 nLastIndex = nIndex + nLen; + OUStringBuffer aTmpStr(rStr); + + if( nLastIndex > aTmpStr.getLength() ) + nLastIndex = aTmpStr.getLength(); + + for(; nIndex < nLastIndex; ++nIndex ) + { + sal_Unicode cOrig = rStr[ nIndex ]; + // only recode symbols and their U+00xx aliases + if( ((cOrig < 0x0020) || (cOrig > 0x00FF)) + && ((cOrig < 0xF020) || (cOrig > 0xF0FF)) ) + continue; + + // recode a symbol + sal_Unicode cNew = RecodeChar( cOrig ); + if( cOrig != cNew ) + aTmpStr[ nIndex ] = cNew; + } + rStr = aTmpStr.makeStringAndClear(); +} + +namespace { + +struct RecodeTable { const char* pOrgName; ConvertChar aCvt;}; + +} + +const RecodeTable aStarSymbolRecodeTable[] = +{ + // the first two entries must be StarMath and StarBats; do not reorder! + // reason: see CreateFontToSubsFontConverter method + {"starbats", {aStarBatsTab, "StarSymbol", nullptr}}, + {"starmath", {aStarMathTab, "StarSymbol", nullptr}}, + + {"symbol", {aAdobeSymbolTab, "StarSymbol", nullptr}}, + {"standardsymbols", {aAdobeSymbolTab, "StarSymbol", nullptr}}, + {"standardsymbolsl",{aAdobeSymbolTab, "StarSymbol", nullptr}}, + + {"monotypesorts", {aMonotypeSortsTab, "StarSymbol", nullptr}}, +// {"monotypesorts2", {aMonotypeSorts2Tab, "StarSymbol", NULL}} + {"zapfdingbats", {aMonotypeSortsTab, "StarSymbol", nullptr}}, //ZapfDingbats=MonotypeSorts-X? + {"itczapfdingbats", {aMonotypeSortsTab, "StarSymbol", nullptr}}, + {"dingbats", {aMonotypeSortsTab, "StarSymbol", nullptr}}, + + {"webdings", {aWebDingsTab, "StarSymbol", nullptr}}, + {"wingdings", {aWingDingsTab, "StarSymbol", nullptr}}, + {"wingdings2", {aWingDings2Tab, "StarSymbol", nullptr}}, + {"wingdings3", {aWingDings3Tab, "StarSymbol", nullptr}}, + {"mtextra", {aMTExtraTab, "StarSymbol", nullptr}} +}; + +const RecodeTable aAppleSymbolRecodeTable[] = { + {"symbol", {aAdobeSymbolToAppleSymbolTab, "AppleSymbol", nullptr}} +}; + +static ConvertChar aImplStarSymbolCvt = { nullptr, "StarBats", ImplStarSymbolToStarBats }; + +const ConvertChar* ConvertChar::GetRecodeData( std::u16string_view rOrgFontName, std::u16string_view rMapFontName ) +{ + const ConvertChar* pCvt = nullptr; + + // clean up and lowercase font name + OUString aOrgName( GetEnglishSearchFontName( rOrgFontName ) ); + OUString aMapName( GetEnglishSearchFontName( rMapFontName ) ); + + if( aMapName == "starsymbol" + || aMapName == "opensymbol" ) + { + for (auto const & r: aStarSymbolRecodeTable) + { + if( aOrgName.equalsAscii( r.pOrgName ) ) + { + pCvt = &r.aCvt; + break; + } + } + } + //It's plausible that it's better to implement this + //as an additional encoding alongside the existing + //adobe-symbol to unicode conversion in rtl instead + else if( aMapName == "applesymbol" ) + { + for (auto const & r: aAppleSymbolRecodeTable) + { + if( aOrgName.equalsAscii( r.pOrgName ) ) + { + pCvt = &r.aCvt; + break; + } + } + } + else if( aMapName == "starbats" ) + { + if( aOrgName == "starsymbol" ) + pCvt = &aImplStarSymbolCvt; + else if( aOrgName == "opensymbol" ) + pCvt = &aImplStarSymbolCvt; + } + + return pCvt; +} + +FontToSubsFontConverter CreateFontToSubsFontConverter( std::u16string_view rOrgName, FontToSubsFontFlags nFlags ) +{ + const ConvertChar* pCvt = nullptr; + + OUString aName = GetEnglishSearchFontName( rOrgName ); + + if ( nFlags == FontToSubsFontFlags::IMPORT ) + { + const std::size_t nEntries = 2; // only StarMath+StarBats + for( std::size_t i = 0; i < nEntries; ++i ) + { + const RecodeTable& r = aStarSymbolRecodeTable[i]; + if( aName.equalsAscii( r.pOrgName ) ) + { + pCvt = &r.aCvt; + break; + } + } + } + else + { + // TODO: only StarMath+StarBats + if( aName == "starsymbol" ) + pCvt = &aImplStarSymbolCvt; + else if( aName == "opensymbol" ) + pCvt = &aImplStarSymbolCvt; + } + + return const_cast<ConvertChar *>(pCvt); +} + +sal_Unicode ConvertFontToSubsFontChar( + FontToSubsFontConverter hConverter, sal_Unicode cChar ) +{ + if ( hConverter ) + return static_cast<ConvertChar*>(hConverter)->RecodeChar( cChar ); + else + return cChar; +} + +OUString GetFontToSubsFontName( FontToSubsFontConverter hConverter ) +{ + if ( !hConverter ) + return OUString(); + + const char* pName = static_cast<ConvertChar*>(hConverter)->mpSubsFontName; + return OUString::createFromAscii( pName ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/misc/fontdefs.cxx b/unotools/source/misc/fontdefs.cxx new file mode 100644 index 0000000000..43b9e05543 --- /dev/null +++ b/unotools/source/misc/fontdefs.cxx @@ -0,0 +1,417 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <o3tl/safeint.hxx> +#include <o3tl/string_view.hxx> +#include <unotools/fontdefs.hxx> +#include <unotools/fontcfg.hxx> +#include <rtl/ustrbuf.hxx> + +#include <string_view> +#include <unordered_map> + +OUString StripScriptFromName(const OUString& _aName) +{ + // I worry that someone will have a font which *does* have + // e.g. "Greek" legitimately at the end of its name :-( + const char*const suffixes[] = { " baltic", + " ce", + " cyr", + " greek", + " tur", + " (arabic)", + " (hebrew)", + " (thai)", + " (vietnamese)" + }; + + OUString aName = _aName; + // These can be crazily piled up, e.g. Times New Roman CYR Greek + bool bFinished = false; + while (!bFinished) + { + bFinished = true; + for (const char* suffix : suffixes) + { + size_t nLen = strlen(suffix); + if (aName.endsWithIgnoreAsciiCaseAsciiL(suffix, nLen)) + { + bFinished = false; + aName = aName.copy(0, aName.getLength() - nLen); + } + } + } + return aName; +} + +//return true if the character is stripped from the string +static bool toOnlyLowerAsciiOrStrip(sal_Unicode c, OUStringBuffer &rName, sal_Int32 nIndex, sal_Int32& rLen) +{ + // not lowercase Ascii + if (c < 'a' || c > 'z') + { + // To Lowercase-Ascii + if ( (c >= 'A') && (c <= 'Z') ) + { + c += 'a' - 'A'; + rName[nIndex] = c; + } + else if( ((c < '0') || (c > '9')) && (c != ';') && (c != '(') && (c != ')') ) // not 0-9, semicolon, or brackets + { + // Remove white spaces and special characters + rName.remove(nIndex, 1); + rLen--; + return true; + } + } + return false; +} + +OUString GetEnglishSearchFontName(std::u16string_view rInName) +{ + OUStringBuffer rName(rInName); + bool bNeedTranslation = false; + sal_Int32 nLen = rName.getLength(); + + // Remove trailing whitespaces + sal_Int32 i = nLen; + while ( i && (rName[ i-1 ] < 32) ) + i--; + if ( i != nLen ) + rName.truncate(i); + + nLen = rName.getLength(); + + // remove all whitespaces and converts to lower case ASCII + // TODO: better transliteration to ASCII e.g. all digits + i = 0; + while ( i < nLen ) + { + sal_Unicode c = rName[ i ]; + if ( c > 127 ) + { + // Translate to Lowercase-ASCII + // FullWidth-ASCII to half ASCII + if ( (c >= 0xFF00) && (c <= 0xFF5E) ) + { + c -= 0xFF00-0x0020; + rName[ i ] = c; + if (toOnlyLowerAsciiOrStrip(c, rName, i, nLen)) + continue; + } + else + { + // Only Fontnames with None-Ascii-Characters must be translated + bNeedTranslation = true; + } + } + else if (toOnlyLowerAsciiOrStrip(c, rName, i, nLen)) + continue; + + i++; + } + OUString rNameStr = rName.makeStringAndClear(); + // translate normalized localized name to its normalized English ASCII name + if( bNeedTranslation ) + { + typedef std::unordered_map<OUString, OUString> FontNameDictionary; + static FontNameDictionary const aDictionary = { + {u"\uBC14\uD0D5"_ustr, "batang"}, + {u"\uBC14\uD0D5\uCCB4"_ustr, "batangche"}, + {u"\uAD81\uC11C"_ustr, "gungshu"}, + {u"\uAD81\uC11C\uCCB4"_ustr, "gungshuche"}, + {u"\uAD74\uB9BC"_ustr, "gulim"}, + {u"\uAD74\uB9BC\uCCB4"_ustr, "gulimche"}, + {u"\uB3CB\uC6C0"_ustr, "dotum"}, + {u"\uB3CB\uC6C0\uCCB4"_ustr, "dotumche"}, + {u"\u5B8B\u4F53"_ustr, "simsun"}, + {u"\u65B0\u5B8B\u4F53"_ustr, "nsimsun"}, + {u"\u9ED1\u4F53"_ustr, "simhei"}, + {u"\u6977\u4F53"_ustr, "simkai"}, + {u"\u4E2D\u6613\u5B8B\u4F53"_ustr, "zycjksun"}, + {u"\u4E2D\u6613\u9ED1\u4F53"_ustr, "zycjkhei"}, + {u"\u4E2D\u6613\u6977\u4F53"_ustr, "zycjkkai"}, + {u"\u65B9\u6B63\u9ED1\u4F53"_ustr, "fzhei"}, + {u"\u65B9\u6B63\u6977\u4F53"_ustr, "fzkai"}, + {u"\u65B9\u6B63\u5B8B\u4E00"_ustr, "fzsong"}, + {u"\u65B9\u6B63\u4E66\u5B8B"_ustr, "fzshusong"}, + {u"\u65B9\u6B63\u4EFF\u5B8B"_ustr, "fzfangsong"}, + // Attention: this fonts includes the wrong encoding vector - so we double the names with correct and wrong encoding + // First one is the GB-Encoding (we think the correct one), second is the big5 encoded name + {u"m\u7B80\u9ED1"_ustr, "mhei"}, + {u"m\u6F60\u7AAA"_ustr, "mhei"}, + {u"m\u7B80\u6977\u566C"_ustr, "mkai"}, + {u"m\u6F60\u7FF1\u628E"_ustr, "mkai"}, + {u"m\u7B80\u5B8B"_ustr, "msong"}, + {u"m\u6F60\u51BC"_ustr, "msong"}, + {u"m\u7B80\u592B\u5B8B"_ustr, "cfangsong"}, + {u"m\u6F60\u6E98\u51BC"_ustr, "cfangsong"}, + {u"\u7D30\u660E\u9AD4"_ustr, "mingliu"}, + {u"\u65B0\u7D30\u660E\u9AD4"_ustr, "pmingliu"}, + {u"\u6865"_ustr, "hei"}, + {u"\u6B61"_ustr, "kai"}, + {u"\u6D69\u6E67"_ustr, "ming"}, + {u"ms\u30B4\u30B7\u30C3\u30AF"_ustr, "msgothic"}, + {u"msp\u30B4\u30B7\u30C3\u30AF"_ustr, "mspgothic"}, + {u"ms\u660E\u671D"_ustr, "msmincho"}, + {u"msp\u660E\u671D"_ustr, "mspmincho"}, + {u"\u5FAE\u8EDF\u6B63\u9ED1\u9AD4"_ustr, "microsoftjhenghei"}, + {u"\u5FAE\u8F6F\u96C5\u9ED1"_ustr, "microsoftyahei"}, + {u"\u30e1\u30a4\u30ea\u30aa"_ustr, "meiryo"}, + {u"hg\u660E\u671Dl"_ustr, "hgminchol"}, + {u"hg\u30B4\u30B7\u30C3\u30AFb"_ustr, "hggothicb"}, + {u"hgp\u660E\u671Dl"_ustr, "hgpminchol"}, + {u"hgp\u30B4\u30B7\u30C3\u30AFb"_ustr, "hgpgothicb"}, + {u"hg\u660E\u671Dlsun"_ustr, "hgmincholsun"}, + {u"hg\u30B4\u30B7\u30C3\u30AFbsun"_ustr, "hggothicbsun"}, + {u"hgp\u660E\u671Dlsun"_ustr, "hgpmincholsun"}, + {u"hgp\u30B4\u30B7\u30C3\u30AFbsun"_ustr, "hgpgothicbsun"}, + {u"hg\u5E73\u6210\u660E\u671D\u4F53"_ustr, "hgheiseimin"}, + {u"hg\u5E73\u6210\u660E\u671D\u4F53w3x12"_ustr, "hgheiseimin"}, + {u"ipa\u660E\u671D"_ustr, "ipamincho"}, + {u"ipap\u660E\u671D"_ustr, "ipapmincho"}, + {u"ipa\u30B4\u30B7\u30C3\u30AF"_ustr, "ipagothic"}, + {u"ipap\u30B4\u30B7\u30C3\u30AF"_ustr, "ipapgothic"}, + {u"ipaui\u30B4\u30B7\u30C3\u30AF"_ustr, "ipauigothic"}, + {u"takao\u660E\u671D"_ustr, "takaomincho"}, + {u"takaop\u660E\u671D"_ustr, "takaopmincho"}, + {u"takao\u30B4\u30B7\u30C3\u30AF"_ustr, "takaogothic"}, + {u"takaop\u30B4\u30B7\u30C3\u30AF"_ustr, "takaopgothic"}, + {u"\u3055\u3056\u306A\u307F\u660E\u671D"_ustr, "sazanamimincho"}, + {u"\u3055\u3056\u306A\u307F\u30B4\u30B7\u30C3\u30AF"_ustr, "sazanamigothic"}, + {u"\u6771\u98A8\u660E\u671D"_ustr, "kochimincho"}, + {u"\u6771\u98A8\u30B4\u30B7\u30C3\u30AF"_ustr, "kochigothic"}, + {u"\uC36C\uB3CB\uC6C0"_ustr, "sundotum"}, + {u"\uC36C\uAD74\uB9BC"_ustr, "sungulim"}, + {u"\uC36C\uBC14\uD0D5"_ustr, "sunbatang"}, + {u"\uBC31\uBB35\uB3CB\uC6C0"_ustr, "baekmukdotum"}, + {u"\uBC31\uBB35\uAD74\uB9BC"_ustr, "baekmukgulim"}, + {u"\uBC31\uBB35\uBC14\uD0D5"_ustr, "baekmukbatang"}, + {u"\u65B9\u6B63\u9ED1\u4F53"_ustr, "fzheiti"}, + {u"\u65B9\u6B63\u9ED1\u9AD4"_ustr, "fzheiti"}, + {u"\u65B9\u6B63\u6977\u4F53"_ustr, "fzkaiti"}, + {u"\u65B9\u6B63\u6977\u9AD4"_ustr, "fzkaitib"}, + {u"\u65B9\u6B63\u660E\u9AD4"_ustr, "fzmingtib"}, + {u"\u65B9\u6B63\u5B8B\u4F53"_ustr, "fzsongti"}, + {u"hy\uACAC\uBA85\uC870"_ustr, "hymyeongjoextra"}, + {u"hy\uC2E0\uBA85\uC870"_ustr, "hysinmyeongjomedium"}, + {u"hy\uC911\uACE0\uB515"_ustr, "hygothicmedium"}, + {u"hy\uADF8\uB798\uD53Dm"_ustr, "hygraphicmedium"}, + {u"hy\uADF8\uB798\uD53D"_ustr, "hygraphic"}, + {u"\uC0C8\uAD74\uB9BC"_ustr, "newgulim"}, + {u"\uC36C\uAD81\uC11C"_ustr, "sungungseo"}, + {u"hy\uAD81\uC11Cb"_ustr, "hygungsobold"}, + {u"hy\uAD81\uC11C"_ustr, "hygungso"}, + {u"\uC36C\uD5E4\uB4DC\uB77C\uC778"_ustr, "sunheadline"}, + {u"hy\uD5E4\uB4DC\uB77C\uC778m"_ustr, "hyheadlinemedium"}, + {u"hy\uD5E4\uB4DC\uB77C\uC778"_ustr, "hyheadline"}, + {u"\uD734\uBA3C\uC61B\uCCB4"_ustr, "yetr"}, + {u"hy\uACAC\uACE0\uB515"_ustr, "hygothicextra"}, + {u"\uC36C\uBAA9\uD310"_ustr, "sunmokpan"}, + {u"\uC36C\uC5FD\uC11C"_ustr, "sunyeopseo"}, + {u"\uC36C\uBC31\uC1A1"_ustr, "sunbaeksong"}, + {u"hy\uC5FD\uC11Cl"_ustr, "hypostlight"}, + {u"hy\uC5FD\uC11C"_ustr, "hypost"}, + {u"\uD734\uBA3C\uB9E4\uC9C1\uCCB4"_ustr, "magicr"}, + {u"\uC36C\uD06C\uB9AC\uC2A4\uD0C8"_ustr, "suncrystal"}, + {u"\uC36C\uC0D8\uBB3C"_ustr, "sunsaemmul"}, + {u"hy\uC595\uC740\uC0D8\uBB3Cm"_ustr, "hyshortsamulmedium"}, + {u"hy\uC595\uC740\uC0D8\uBB3C"_ustr, "hyshortsamul"}, + {u"\uD55C\uCEF4\uBC14\uD0D5"_ustr, "haansoftbatang"}, + {u"\uD55C\uCEF4\uB3CB\uC6C0"_ustr, "haansoftdotum"}, + {u"\uD55C\uC591\uD574\uC11C"_ustr, "hyhaeseo"}, + {u"md\uC194\uCCB4"_ustr, "mdsol"}, + {u"md\uAC1C\uC131\uCCB4"_ustr, "mdgaesung"}, + {u"md\uC544\uD2B8\uCCB4"_ustr, "mdart"}, + {u"md\uC544\uB871\uCCB4"_ustr, "mdalong"}, + {u"md\uC774\uC19D\uCCB4"_ustr, "mdeasop"}, + {u"hg\uFF7A\uFF9E\uFF7C\uFF6F\uFF78e"_ustr, "hggothice"}, + {u"hgp\uFF7A\uFF9E\uFF7C\uFF6F\uFF78e"_ustr, "hgpgothice"}, + {u"hgs\uFF7A\uFF9E\uFF7C\uFF6F\uFF78e"_ustr, "hgsgothice"}, + {u"hg\uFF7A\uFF9E\uFF7C\uFF6F\uFF78m"_ustr, "hggothicm"}, + {u"hgp\uFF7A\uFF9E\uFF7C\uFF6F\uFF78m"_ustr, "hgpgothicm"}, + {u"hgs\uFF7A\uFF9E\uFF7C\uFF6F\uFF78m"_ustr, "hgsgothicm"}, + {u"hg\u884C\u66F8\u4F53"_ustr, "hggyoshotai"}, + {u"hgp\u884C\u66F8\u4F53"_ustr, "hgpgyoshotai"}, + {u"hgs\u884C\u66F8\u4F53"_ustr, "hgsgyoshotai"}, + {u"hg\u6559\u79D1\u66F8\u4F53"_ustr, "hgkyokashotai"}, + {u"hgp\u6559\u79D1\u66F8\u4F53"_ustr, "hgpkyokashotai"}, + {u"hgs\u6559\u79D1\u66F8\u4F53"_ustr, "hgskyokashotai"}, + {u"hg\u660E\u671Db"_ustr, "hgminchob"}, + {u"hgp\u660E\u671Db"_ustr, "hgpminchob"}, + {u"hgs\u660E\u671Db"_ustr, "hgsminchob"}, + {u"hg\u660E\u671De"_ustr, "hgminchoe"}, + {u"hgp\u660E\u671De"_ustr, "hgpminchoe"}, + {u"hgs\u660E\u671De"_ustr, "hgsminchoe"}, + {u"hg\u5275\u82F1\u89D2\uFF8E\uFF9F\uFF6F\uFF8C\uFF9F\u4F53"_ustr, "hgsoeikakupoptai"}, + {u"hgp\u5275\u82F1\u89D2\uFF8E\uFF9F\uFF6F\uFF8C\uFF9F\u4F53"_ustr, "hgpsoeikakupopta"}, + {u"hgs\u5275\u82F1\u89D2\uFF8E\uFF9F\uFF6F\uFF8C\uFF9F\u4F53"_ustr, "hgssoeikakupopta"}, + {u"hg\u5275\u82F1\uFF8C\uFF9F\uFF9A\uFF7E\uFF9E\uFF9D\uFF7Deb"_ustr, + "hgsoeipresenceeb"}, + {u"hgp\u5275\u82F1\uFF8C\uFF9F\uFF9A\uFF7E\uFF9E\uFF9D\uFF7Deb"_ustr, + "hgpsoeipresenceeb"}, + {u"hgs\u5275\u82F1\uFF8C\uFF9F\uFF9A\uFF7E\uFF9E\uFF9D\uFF7Deb"_ustr, + "hgssoeipresenceeb"}, + {u"hg\u5275\u82F1\u89D2\uFF7A\uFF9E\uFF7C\uFF6F\uFF78ub"_ustr, "hgsoeikakugothicub"}, + {u"hgp\u5275\u82F1\u89D2\uFF7A\uFF9E\uFF7C\uFF6F\uFF78ub"_ustr, "hgpsoeikakugothicub"}, + {u"hgs\u5275\u82F1\u89D2\uFF7A\uFF9E\uFF7C\uFF6F\uFF78ub"_ustr, "hgssoeikakugothicub"}, + {u"hg\u6B63\u6977\u66F8\u4F53-pro"_ustr, "hgseikaishotaipro"}, + {u"hg\u4E38\uFF7A\uFF9E\uFF7C\uFF6F\uFF78-pro"_ustr, "hgmarugothicmpro"}, + {u"\u30D2\u30E9\u30AE\u30CE\u660E\u671Dpro"_ustr, "hiraginominchopro"}, + {u"\u30D2\u30E9\u30AE\u30CE\u660E\u671Dpron"_ustr, "hiraginominchopron"}, + {u"\u30D2\u30E9\u30AE\u30CE\u89D2\u30B4\u30B7\u30C3\u30AF"_ustr, "hiraginosans"}, + {u"\u30D2\u30E9\u30AE\u30CE\u89D2\u30B4pro"_ustr, "hiraginokakugothicpro"}, + {u"\u30D2\u30E9\u30AE\u30CE\u89D2\u30B4pron"_ustr, "hiraginokakugothicpron"}, + {u"\u30D2\u30E9\u30AE\u30CE\u4E38\u30B4pro"_ustr, "hiraginomarugothicpro"}, + {u"\u30D2\u30E9\u30AE\u30CE\u4E38\u30B4pron"_ustr, "hiraginomarugothicpron"}, + {u"\u6E38\u30B4\u30B7\u30C3\u30AF"_ustr, "yugothic"}, + {u"\u6E38\u30B4\u30B7\u30C3\u30AF\u4F53"_ustr, "yugothictai"}, + {u"\u6E38\u660E\u671D"_ustr, "yumincho"}, + {u"\u6E38\u660E\u671D\u4F53"_ustr, "yuminchotai"}, + {u"\u6E90\u30CE\u89D2\u30B4\u30B7\u30C3\u30AF"_ustr, "sourcehansans"}, + {u"\u6E90\u30CE\u89D2\u30B4\u30B7\u30C3\u30AFjp"_ustr, "sourcehansansjp"}, + {u"\u6E90\u30CE\u89D2\u30B4\u30B7\u30C3\u30AFhw"_ustr, "sourcehansanshw"}, + {u"\u6E90\u30CE\u660E\u671D"_ustr, "sourcehanserif"}, + {u"\u6E90\u30CE\u660E\u671Djp"_ustr, "sourcehanserifjp"}, + {u"ipamj\u660E\u671D"_ustr, "ipamjmincho"}, + {u"ipaex\u30B4\u30B7\u30C3\u30AF"_ustr, "ipaexgothic"}, + {u"ipaex\u660E\u671D"_ustr, "ipaexmimcho"}}; + + FontNameDictionary::const_iterator it = aDictionary.find( rNameStr ); + if( it != aDictionary.end() ) + rNameStr = it->second; + } + + return rNameStr; +} + +std::u16string_view GetNextFontToken( std::u16string_view rTokenStr, sal_Int32& rIndex ) +{ + // check for valid start index + size_t nStringLen = rTokenStr.size(); + if( o3tl::make_unsigned(rIndex) >= nStringLen ) + { + rIndex = -1; + return {}; + } + + // find the next token delimiter and return the token substring + const sal_Unicode* pStr = rTokenStr.data() + rIndex; + const sal_Unicode* pEnd = rTokenStr.data() + nStringLen; + for(; pStr < pEnd; ++pStr ) + if( (*pStr == ';') || (*pStr == ',') ) + break; + + sal_Int32 nTokenStart = rIndex; + sal_Int32 nTokenLen; + if( pStr < pEnd ) + { + rIndex = sal::static_int_cast<sal_Int32>(pStr - rTokenStr.data()); + nTokenLen = rIndex - nTokenStart; + ++rIndex; // skip over token separator + } + else + { + // no token delimiter found => handle last token + rIndex = -1; + + // optimize if the token string consists of just one token + if( !nTokenStart ) + { + return rTokenStr; + } + else + { + nTokenLen = nStringLen - nTokenStart; + } + } + + return rTokenStr.substr( nTokenStart, nTokenLen ); +} + +static bool ImplIsFontToken( std::u16string_view rName, std::u16string_view rToken ) +{ + sal_Int32 nIndex = 0; + do + { + std::u16string_view aTempName = GetNextFontToken( rName, nIndex ); + if ( rToken == aTempName ) + return true; + } + while ( nIndex != -1 ); + + return false; +} + +static void ImplAppendFontToken( OUString& rName, std::u16string_view rNewToken ) +{ + if ( !rName.isEmpty() ) + { + rName += ";"; + } + rName += rNewToken; +} + +void AddTokenFontName( OUString& rName, std::u16string_view rNewToken ) +{ + if ( !ImplIsFontToken( rName, rNewToken ) ) + ImplAppendFontToken( rName, rNewToken ); +} + +OUString GetSubsFontName( std::u16string_view rName, SubsFontFlags nFlags ) +{ + OUString aName; + + sal_Int32 nIndex = 0; + OUString aOrgName = GetEnglishSearchFontName( + GetNextFontToken( rName, nIndex ) ); + + // #93662# do not try to replace StarSymbol with MS only font + if( nFlags == (SubsFontFlags::MS|SubsFontFlags::ONLYONE) + && ( aOrgName == "starsymbol" + || aOrgName == "opensymbol" ) ) + return aName; + + if (nFlags & SubsFontFlags::MS) + { + const utl::FontNameAttr* pAttr = utl::FontSubstConfiguration::get().getSubstInfo( aOrgName ); + if (pAttr) + for( const auto& rSubstitution : pAttr->MSSubstitutions ) + if( ! ImplIsFontToken( rName, rSubstitution ) ) + { + ImplAppendFontToken( aName, rSubstitution ); + if( nFlags & SubsFontFlags::ONLYONE ) + { + break; + } + } + } + + return aName; +} + +bool IsOpenSymbol(std::u16string_view rFontName) +{ + sal_Int32 nIndex = 0; + std::u16string_view sFamilyNm(GetNextFontToken(rFontName, nIndex)); + return (o3tl::equalsIgnoreAsciiCase(sFamilyNm, "starsymbol") || + o3tl::equalsIgnoreAsciiCase(sFamilyNm, "opensymbol")); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/misc/mediadescriptor.cxx b/unotools/source/misc/mediadescriptor.cxx new file mode 100644 index 0000000000..f797728f7b --- /dev/null +++ b/unotools/source/misc/mediadescriptor.cxx @@ -0,0 +1,500 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <comphelper/docpasswordhelper.hxx> +#include <sal/log.hxx> +#include <unotools/configmgr.hxx> +#include <unotools/mediadescriptor.hxx> +#include <unotools/securityoptions.hxx> +#include <unotools/ucbhelper.hxx> +#include <comphelper/namedvaluecollection.hxx> +#include <comphelper/stillreadwriteinteraction.hxx> + +#include <com/sun/star/ucb/ContentCreationException.hpp> +#include <com/sun/star/ucb/XContent.hpp> +#include <com/sun/star/task/XInteractionHandler.hpp> +#include <com/sun/star/io/XStream.hpp> +#include <com/sun/star/io/XActiveDataSink.hpp> +#include <com/sun/star/io/XSeekable.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <com/sun/star/uri/UriReferenceFactory.hpp> +#include <com/sun/star/uri/XUriReference.hpp> +#include <com/sun/star/ucb/PostCommandArgument2.hpp> +#include <officecfg/Office/Common.hxx> +#include <ucbhelper/content.hxx> +#include <ucbhelper/commandenvironment.hxx> +#include <ucbhelper/activedatasink.hxx> +#include <comphelper/processfactory.hxx> +#include <tools/urlobj.hxx> +#include <osl/diagnose.h> +#include <comphelper/diagnose_ex.hxx> + +namespace utl { + +namespace { + +OUString removeFragment(OUString const & uri) { + css::uno::Reference< css::uri::XUriReference > ref( + css::uri::UriReferenceFactory::create( + comphelper::getProcessComponentContext())-> + parse(uri)); + if (ref.is()) { + ref->clearFragment(); + return ref->getUriReference(); + } else { + SAL_WARN("unotools.misc", "cannot parse <" << uri << ">"); + return uri; + } +} + +} + +MediaDescriptor::MediaDescriptor() +{ +} + +MediaDescriptor::MediaDescriptor(const css::uno::Sequence< css::beans::PropertyValue >& lSource) + : SequenceAsHashMap(lSource) +{ +} + +bool MediaDescriptor::isStreamReadOnly() const +{ + bool bReadOnly = false; + + // check for explicit readonly state + const_iterator pIt = find(MediaDescriptor::PROP_READONLY); + if (pIt != end()) + { + pIt->second >>= bReadOnly; + return bReadOnly; + } + + // streams based on post data are readonly by definition + pIt = find(MediaDescriptor::PROP_POSTDATA); + if (pIt != end()) + return true; + + // A XStream capsulate XInputStream and XOutputStream ... + // If it exists - the file must be open in read/write mode! + pIt = find(MediaDescriptor::PROP_STREAM); + if (pIt != end()) + return false; + + // Only file system content provider is able to provide XStream + // so for this content impossibility to create XStream triggers + // switch to readonly mode. + try + { + css::uno::Reference< css::ucb::XContent > xContent = getUnpackedValueOrDefault(MediaDescriptor::PROP_UCBCONTENT, css::uno::Reference< css::ucb::XContent >()); + if (xContent.is()) + { + css::uno::Reference< css::ucb::XContentIdentifier > xId = xContent->getIdentifier(); + OUString aScheme; + if (xId.is()) + aScheme = xId->getContentProviderScheme(); + + if (aScheme.equalsIgnoreAsciiCase("file")) + bReadOnly = true; + else + { + ::ucbhelper::Content aContent(xContent, + utl::UCBContentHelper::getDefaultCommandEnvironment(), + comphelper::getProcessComponentContext()); + aContent.getPropertyValue("IsReadOnly") >>= bReadOnly; + } + } + } + catch(const css::uno::RuntimeException& ) + { throw; } + catch(const css::uno::Exception&) + {} + + return bReadOnly; +} + +css::uno::Any MediaDescriptor::getComponentDataEntry( const OUString& rName ) const +{ + comphelper::SequenceAsHashMap::const_iterator aPropertyIter = find( PROP_COMPONENTDATA ); + if( aPropertyIter != end() ) + return comphelper::NamedValueCollection( aPropertyIter->second ).get( rName ); + return css::uno::Any(); +} + +void MediaDescriptor::setComponentDataEntry( const OUString& rName, const css::uno::Any& rValue ) +{ + if( rValue.hasValue() ) + { + // get or create the 'ComponentData' property entry + css::uno::Any& rCompDataAny = operator[]( PROP_COMPONENTDATA ); + // insert the value (retain sequence type, create NamedValue elements by default) + bool bHasNamedValues = !rCompDataAny.hasValue() || rCompDataAny.has< css::uno::Sequence< css::beans::NamedValue > >(); + bool bHasPropValues = rCompDataAny.has< css::uno::Sequence< css::beans::PropertyValue > >(); + OSL_ENSURE( bHasNamedValues || bHasPropValues, "MediaDescriptor::setComponentDataEntry - incompatible 'ComponentData' property in media descriptor" ); + if( bHasNamedValues || bHasPropValues ) + { + // insert or overwrite the passed value + comphelper::SequenceAsHashMap aCompDataMap( rCompDataAny ); + aCompDataMap[ rName ] = rValue; + // write back the sequence (restore sequence with correct element type) + rCompDataAny = aCompDataMap.getAsConstAny( bHasPropValues ); + } + } + else + { + // if an empty Any is passed, clear the entry + clearComponentDataEntry( rName ); + } +} + +void MediaDescriptor::clearComponentDataEntry( const OUString& rName ) +{ + comphelper::SequenceAsHashMap::iterator aPropertyIter = find( PROP_COMPONENTDATA ); + if( aPropertyIter == end() ) + return; + + css::uno::Any& rCompDataAny = aPropertyIter->second; + bool bHasNamedValues = rCompDataAny.has< css::uno::Sequence< css::beans::NamedValue > >(); + bool bHasPropValues = rCompDataAny.has< css::uno::Sequence< css::beans::PropertyValue > >(); + OSL_ENSURE( bHasNamedValues || bHasPropValues, "MediaDescriptor::clearComponentDataEntry - incompatible 'ComponentData' property in media descriptor" ); + if( bHasNamedValues || bHasPropValues ) + { + // remove the value with the passed name + comphelper::SequenceAsHashMap aCompDataMap( rCompDataAny ); + aCompDataMap.erase( rName ); + // write back the sequence, or remove it completely if it is empty + if( aCompDataMap.empty() ) + erase( aPropertyIter ); + else + rCompDataAny = aCompDataMap.getAsConstAny( bHasPropValues ); + } +} + +css::uno::Sequence< css::beans::NamedValue > MediaDescriptor::requestAndVerifyDocPassword( + comphelper::IDocPasswordVerifier& rVerifier, + comphelper::DocPasswordRequestType eRequestType, + const ::std::vector< OUString >* pDefaultPasswords ) +{ + css::uno::Sequence< css::beans::NamedValue > aMediaEncData = getUnpackedValueOrDefault( + PROP_ENCRYPTIONDATA, css::uno::Sequence< css::beans::NamedValue >() ); + OUString aMediaPassword = getUnpackedValueOrDefault( + PROP_PASSWORD, OUString() ); + css::uno::Reference< css::task::XInteractionHandler > xInteractHandler = getUnpackedValueOrDefault( + PROP_INTERACTIONHANDLER, css::uno::Reference< css::task::XInteractionHandler >() ); + OUString aDocumentName = getUnpackedValueOrDefault( + PROP_URL, OUString() ); + + bool bIsDefaultPassword = false; + css::uno::Sequence< css::beans::NamedValue > aEncryptionData = comphelper::DocPasswordHelper::requestAndVerifyDocPassword( + rVerifier, aMediaEncData, aMediaPassword, xInteractHandler, aDocumentName, eRequestType, pDefaultPasswords, &bIsDefaultPassword ); + + erase( PROP_PASSWORD ); + erase( PROP_ENCRYPTIONDATA ); + + // insert encryption info into media descriptor + // TODO + if( aEncryptionData.hasElements() ) + (*this)[ PROP_ENCRYPTIONDATA ] <<= aEncryptionData; + + return aEncryptionData; +} + +bool MediaDescriptor::addInputStream() +{ + return impl_addInputStream( true ); +} + +/*-----------------------------------------------*/ +bool MediaDescriptor::addInputStreamOwnLock() +{ + const bool bLock = !utl::ConfigManager::IsFuzzing() + && officecfg::Office::Common::Misc::UseDocumentSystemFileLocking::get(); + return impl_addInputStream(bLock); +} + +/*-----------------------------------------------*/ +bool MediaDescriptor::impl_addInputStream( bool bLockFile ) +{ + // check for an already existing stream item first + const_iterator pIt = find(MediaDescriptor::PROP_INPUTSTREAM); + if (pIt != end()) + return true; + + try + { + // No stream available - create a new one + // a) data comes as PostData ... + pIt = find(MediaDescriptor::PROP_POSTDATA); + if (pIt != end()) + { + const css::uno::Any& rPostData = pIt->second; + css::uno::Reference< css::io::XInputStream > xPostData; + rPostData >>= xPostData; + + return impl_openStreamWithPostData( xPostData ); + } + + // b) ... or we must get it from the given URL + OUString sURL = getUnpackedValueOrDefault(MediaDescriptor::PROP_URL, OUString()); + if (sURL.isEmpty()) + throw css::uno::Exception("Found no URL.", + css::uno::Reference< css::uno::XInterface >()); + + return impl_openStreamWithURL( removeFragment(sURL), bLockFile ); + } + catch(const css::uno::Exception&) + { + TOOLS_WARN_EXCEPTION("unotools.misc", "invalid MediaDescriptor detected"); + return false; + } +} + +bool MediaDescriptor::impl_openStreamWithPostData( const css::uno::Reference< css::io::XInputStream >& _rxPostData ) +{ + if ( !_rxPostData.is() ) + throw css::lang::IllegalArgumentException("Found invalid PostData.", + css::uno::Reference< css::uno::XInterface >(), 1); + + // PostData can't be used in read/write mode! + (*this)[MediaDescriptor::PROP_READONLY] <<= true; + + // prepare the environment + css::uno::Reference< css::task::XInteractionHandler > xInteraction = getUnpackedValueOrDefault( + MediaDescriptor::PROP_INTERACTIONHANDLER, + css::uno::Reference< css::task::XInteractionHandler >()); + css::uno::Reference< css::ucb::XProgressHandler > xProgress; + rtl::Reference<::ucbhelper::CommandEnvironment> xCommandEnv = new ::ucbhelper::CommandEnvironment(xInteraction, xProgress); + + // media type + OUString sMediaType = getUnpackedValueOrDefault(MediaDescriptor::PROP_MEDIATYPE, OUString()); + if (sMediaType.isEmpty()) + { + sMediaType = "application/x-www-form-urlencoded"; + (*this)[MediaDescriptor::PROP_MEDIATYPE] <<= sMediaType; + } + + // url + OUString sURL( getUnpackedValueOrDefault( PROP_URL, OUString() ) ); + + css::uno::Reference< css::io::XInputStream > xResultStream; + try + { + // seek PostData stream to the beginning + css::uno::Reference< css::io::XSeekable > xSeek( _rxPostData, css::uno::UNO_QUERY ); + if ( xSeek.is() ) + xSeek->seek( 0 ); + + // a content for the URL + ::ucbhelper::Content aContent( sURL, xCommandEnv, comphelper::getProcessComponentContext() ); + + // use post command + css::ucb::PostCommandArgument2 aPostArgument; + aPostArgument.Source = _rxPostData; + css::uno::Reference< css::io::XActiveDataSink > xSink( new ucbhelper::ActiveDataSink ); + aPostArgument.Sink = xSink; + aPostArgument.MediaType = sMediaType; + aPostArgument.Referer = getUnpackedValueOrDefault( PROP_REFERRER, OUString() ); + + aContent.executeCommand( "post", css::uno::Any( aPostArgument ) ); + + // get result + xResultStream = xSink->getInputStream(); + } + catch( const css::uno::Exception& ) + { + } + + // success? + if ( !xResultStream.is() ) + { + OSL_FAIL( "no valid reply to the HTTP-Post" ); + return false; + } + + (*this)[MediaDescriptor::PROP_INPUTSTREAM] <<= xResultStream; + return true; +} + +/*-----------------------------------------------*/ +bool MediaDescriptor::impl_openStreamWithURL( const OUString& sURL, bool bLockFile ) +{ + if (sURL.matchIgnoreAsciiCase(".component:")) + return false; // No UCB content for .component URLs + + OUString referer(getUnpackedValueOrDefault(PROP_REFERRER, OUString())); + if (SvtSecurityOptions::isUntrustedReferer(referer)) { + return false; + } + + // prepare the environment + css::uno::Reference< css::task::XInteractionHandler > xOrgInteraction = getUnpackedValueOrDefault( + MediaDescriptor::PROP_INTERACTIONHANDLER, + css::uno::Reference< css::task::XInteractionHandler >()); + + css::uno::Reference< css::task::XInteractionHandler > xAuthenticationInteraction = getUnpackedValueOrDefault( + MediaDescriptor::PROP_AUTHENTICATIONHANDLER, + css::uno::Reference< css::task::XInteractionHandler >()); + + rtl::Reference<comphelper::StillReadWriteInteraction> xInteraction = new comphelper::StillReadWriteInteraction(xOrgInteraction,xAuthenticationInteraction); + + css::uno::Reference< css::ucb::XProgressHandler > xProgress; + rtl::Reference<::ucbhelper::CommandEnvironment> xCommandEnv = new ::ucbhelper::CommandEnvironment(xInteraction, xProgress); + + // try to create the content + // no content -> no stream => return immediately with FALSE + ::ucbhelper::Content aContent; + css::uno::Reference< css::ucb::XContent > xContent; + try + { + aContent = ::ucbhelper::Content(sURL, xCommandEnv, comphelper::getProcessComponentContext()); + xContent = aContent.get(); + } + catch(const css::uno::RuntimeException&) + { throw; } + catch(const css::ucb::ContentCreationException&) + { + TOOLS_WARN_EXCEPTION("unotools.misc", "url: '" << sURL << "'"); + return false; // TODO error handling + } + catch(const css::uno::Exception&) + { + TOOLS_WARN_EXCEPTION("unotools.misc", "url: '" << sURL << "'"); + return false; // TODO error handling + } + + // try to open the file in read/write mode + // (if it's allowed to do so). + // But handle errors in a "hidden mode". Because + // we try it readonly later - if read/write is not an option. + css::uno::Reference< css::io::XStream > xStream; + css::uno::Reference< css::io::XInputStream > xInputStream; + + bool bReadOnly = false; + bool bModeRequestedExplicitly = false; + const_iterator pIt = find(MediaDescriptor::PROP_READONLY); + if (pIt != end()) + { + pIt->second >>= bReadOnly; + bModeRequestedExplicitly = true; + } + + if ( !bReadOnly && bLockFile ) + { + try + { + // TODO: use "special" still interaction to suppress error messages + xStream = aContent.openWriteableStream(); + if (xStream.is()) + xInputStream = xStream->getInputStream(); + } + catch(const css::uno::RuntimeException&) + { throw; } + catch(const css::uno::Exception&) + { + css::uno::Any ex( cppu::getCaughtException() ); + // ignore exception, if reason was problem reasoned on + // open it in WRITABLE mode! Then we try it READONLY + // later a second time. + // All other errors must be handled as real error an + // break this method. + if (!xInteraction->wasWriteError() || bModeRequestedExplicitly) + { + SAL_WARN("unotools.misc","url: '" << sURL << "' " << exceptionToString(ex)); + // If the protocol is webdav, then we need to treat the stream as readonly, even if the + // operation was requested as read/write explicitly (the WebDAV UCB implementation is monodirectional + // read or write not both at the same time). + if ( !INetURLObject( sURL ).isAnyKnownWebDAVScheme() ) + return false; + } + xStream.clear(); + xInputStream.clear(); + } + } + + // If opening of the stream in read/write mode was not allowed + // or failed by an error - we must try it in readonly mode. + if (!xInputStream.is()) + { + OUString aScheme; + + try + { + css::uno::Reference< css::ucb::XContentIdentifier > xContId( + aContent.get().is() ? aContent.get()->getIdentifier() : nullptr ); + + if ( xContId.is() ) + aScheme = xContId->getContentProviderScheme(); + + // Only file system content provider is able to provide XStream + // so for this content impossibility to create XStream triggers + // switch to readonly mode in case of opening with locking on + if( bLockFile && aScheme.equalsIgnoreAsciiCase("file") ) + bReadOnly = true; + else + { + bool bRequestReadOnly = bReadOnly; + aContent.getPropertyValue("IsReadOnly") >>= bReadOnly; + if ( bReadOnly && !bRequestReadOnly && bModeRequestedExplicitly ) + return false; // the document is explicitly requested with WRITABLE mode + } + } + catch(const css::uno::RuntimeException&) + { throw; } + catch(const css::uno::Exception&) + { /* no error handling if IsReadOnly property does not exist for UCP */ } + + if ( bReadOnly ) + (*this)[MediaDescriptor::PROP_READONLY] <<= bReadOnly; + + xInteraction->resetInterceptions(); + xInteraction->resetErrorStates(); + try + { + // all the contents except file-URLs should be opened as usual + if ( bLockFile || !aScheme.equalsIgnoreAsciiCase("file") ) + xInputStream = aContent.openStream(); + else + xInputStream = aContent.openStreamNoLock(); + } + catch(const css::uno::RuntimeException&) + { + throw; + } + catch(const css::uno::Exception&) + { + TOOLS_INFO_EXCEPTION("unotools.misc","url: '" << sURL << "'"); + return false; + } + } + + // add streams to the descriptor + if (xContent.is()) + (*this)[MediaDescriptor::PROP_UCBCONTENT] <<= xContent; + if (xStream.is()) + (*this)[MediaDescriptor::PROP_STREAM] <<= xStream; + if (xInputStream.is()) + (*this)[MediaDescriptor::PROP_INPUTSTREAM] <<= xInputStream; + + // At least we need an input stream. The r/w stream is optional ... + return xInputStream.is(); +} + +} // namespace comphelper + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/misc/sharedunocomponent.cxx b/unotools/source/misc/sharedunocomponent.cxx new file mode 100644 index 0000000000..1b724a2b94 --- /dev/null +++ b/unotools/source/misc/sharedunocomponent.cxx @@ -0,0 +1,199 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> +#include <osl/diagnose.h> + +#include <unotools/sharedunocomponent.hxx> +#include <com/sun/star/lang/XComponent.hpp> +#include <com/sun/star/util/CloseVetoException.hpp> +#include <com/sun/star/util/XCloseable.hpp> +#include <cppuhelper/implbase.hxx> +#include <tools/debug.hxx> +#include <comphelper/diagnose_ex.hxx> + +namespace utl +{ + + using ::com::sun::star::uno::XInterface; + using ::com::sun::star::uno::Reference; + using ::com::sun::star::uno::Exception; + using ::com::sun::star::uno::UNO_QUERY; + using ::com::sun::star::lang::EventObject; + using ::com::sun::star::util::XCloseable; + using ::com::sun::star::util::XCloseListener; + using ::com::sun::star::util::CloseVetoException; + + //= DisposableComponent + + DisposableComponent::DisposableComponent( const Reference< XInterface >& _rxComponent ) + :m_xComponent( _rxComponent, UNO_QUERY ) + { + DBG_ASSERT( m_xComponent.is() || !_rxComponent.is(), "DisposableComponent::DisposableComponent: should be an XComponent!" ); + } + + DisposableComponent::~DisposableComponent() + { + if ( m_xComponent.is() ) + { + try + { + m_xComponent->dispose(); + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "unotools", "DisposableComponent::~DisposableComponent" ); + } + m_xComponent.clear(); + } + } + + typedef ::cppu::WeakImplHelper < XCloseListener + > CloseableComponentImpl_Base; + class CloseableComponentImpl : public CloseableComponentImpl_Base + { + private: + Reference< XCloseable > m_xCloseable; + + CloseableComponentImpl(const CloseableComponentImpl&) = delete; + CloseableComponentImpl& operator=(const CloseableComponentImpl&) = delete; + + public: + explicit CloseableComponentImpl( const Reference< XInterface >& _rxComponent ); + + /** closes the component + + @nofail + */ + void nf_closeComponent(); + + protected: + virtual ~CloseableComponentImpl() override; + + // XCloseListener overridables + virtual void SAL_CALL queryClosing( const EventObject& Source, sal_Bool GetsOwnership ) override; + virtual void SAL_CALL notifyClosing( const EventObject& Source ) override; + + // XEventListener overridables + virtual void SAL_CALL disposing( const css::lang::EventObject& Source ) override; + + private: + /** starts or stops being a CloseListener at the component + + Only to be called upon construction of the instance, or when the component + is to be closed. + + @nofail + */ + void impl_nf_switchListening( bool _bListen ); + }; + + CloseableComponentImpl::CloseableComponentImpl( const Reference< XInterface >& _rxComponent ) + :m_xCloseable( _rxComponent, UNO_QUERY ) + { + DBG_ASSERT( m_xCloseable.is() || !_rxComponent.is(), "CloseableComponentImpl::CloseableComponentImpl: component is not an XCloseable!" ); + impl_nf_switchListening( true ); + } + + CloseableComponentImpl::~CloseableComponentImpl() + { + nf_closeComponent(); + } + + void CloseableComponentImpl::nf_closeComponent() + { + if ( !m_xCloseable.is() ) + // nothing to do + return; + + // stop listening + impl_nf_switchListening( false ); + + // close + try + { + m_xCloseable->close( true ); + } + catch( const CloseVetoException& ) { /* fine */ } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "unotools", "CloseableComponentImpl::nf_closeComponent: caught an unexpected exception!" ); + } + + // reset + m_xCloseable.clear(); + } + + void CloseableComponentImpl::impl_nf_switchListening( bool _bListen ) + { + if ( !m_xCloseable.is() ) + return; + + try + { + if ( _bListen ) + m_xCloseable->addCloseListener( this ); + else + m_xCloseable->removeCloseListener( this ); + } + catch( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "unotools", "CloseableComponentImpl::impl_nf_switchListening" ); + } + } + + void SAL_CALL CloseableComponentImpl::queryClosing( const EventObject& Source, sal_Bool /*GetsOwnership*/ ) + { + // as long as we live, somebody wants to keep the object alive. So, veto the + // closing + DBG_ASSERT( Source.Source == m_xCloseable, "CloseableComponentImpl::queryClosing: where did this come from?" ); + throw CloseVetoException(); + } + + void SAL_CALL CloseableComponentImpl::notifyClosing( const EventObject& Source ) + { + DBG_ASSERT( Source.Source == m_xCloseable, "CloseableComponentImpl::notifyClosing: where did this come from?" ); + + // this should be unreachable: As long as we're a CloseListener, we veto the closing. If we're going + // to close the component ourself, then we revoke ourself as listener *before* the close call. So, + // if this here fires, something went definitely wrong. + OSL_FAIL( "CloseableComponentImpl::notifyClosing: unreachable!" ); + } + + void SAL_CALL CloseableComponentImpl::disposing( const EventObject& Source ) + { + DBG_ASSERT( Source.Source == m_xCloseable, "CloseableComponentImpl::disposing: where did this come from?" ); + OSL_FAIL( "CloseableComponentImpl::disposing: unreachable!" ); + // same reasoning for this assertion as in ->notifyClosing + } + + CloseableComponent::CloseableComponent( const Reference< XInterface >& _rxComponent ) + :m_pImpl( new CloseableComponentImpl( _rxComponent ) ) + { + } + + CloseableComponent::~CloseableComponent() + { + // close the component, deliver ownership to anybody who wants to veto the close + m_pImpl->nf_closeComponent(); + } + +} // namespace utl + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/misc/syslocale.cxx b/unotools/source/misc/syslocale.cxx new file mode 100644 index 0000000000..31b448e658 --- /dev/null +++ b/unotools/source/misc/syslocale.cxx @@ -0,0 +1,177 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <sal/log.hxx> +#include <unotools/localedatawrapper.hxx> +#include <unotools/charclass.hxx> +#include <unotools/syslocale.hxx> +#include <unotools/syslocaleoptions.hxx> +#include <comphelper/lok.hxx> +#include <rtl/tencinfo.h> +#include <rtl/locale.h> +#include <osl/thread.h> +#include <osl/nlsupport.h> + +#include <memory> +#include <mutex> +#include <optional> +#include <vector> + +using namespace osl; +using namespace com::sun::star; + +namespace { + +std::weak_ptr<SvtSysLocale_Impl> g_pSysLocale; + +// static +std::mutex& GetMutex() +{ + // #i77768# Due to a static reference in the toolkit lib + // we need a mutex that lives longer than the svl library. + // Otherwise the dtor would use a destructed mutex!! + static std::mutex* persistentMutex(new std::mutex); + + return *persistentMutex; +} + + +} + +class SvtSysLocale_Impl : public utl::ConfigurationListener +{ +public: + SvtSysLocaleOptions aSysLocaleOptions; + std::optional<LocaleDataWrapper> moLocaleData; + std::optional<CharClass> moCharClass; + + SvtSysLocale_Impl(); + virtual ~SvtSysLocale_Impl() override; + + CharClass& GetCharClass(); + virtual void ConfigurationChanged( utl::ConfigurationBroadcaster*, ConfigurationHints ) override; + +private: + std::vector<OUString> getDateAcceptancePatternsConfig() const; +}; + +SvtSysLocale_Impl::SvtSysLocale_Impl() +{ + moLocaleData.emplace( + aSysLocaleOptions.GetRealLanguageTag(), + getDateAcceptancePatternsConfig() ); + + // listen for further changes + aSysLocaleOptions.AddListener( this ); +} + +SvtSysLocale_Impl::~SvtSysLocale_Impl() +{ + aSysLocaleOptions.RemoveListener( this ); +} + +CharClass& SvtSysLocale_Impl::GetCharClass() +{ + if ( !moCharClass ) + moCharClass.emplace( aSysLocaleOptions.GetRealLanguageTag() ); + return *moCharClass; +} + +void SvtSysLocale_Impl::ConfigurationChanged( utl::ConfigurationBroadcaster*, ConfigurationHints nHint ) +{ + if ( !(nHint & ConfigurationHints::Locale) && + !(nHint & ConfigurationHints::DatePatterns) ) + return; + + std::unique_lock aGuard( GetMutex() ); + + const LanguageTag& rLanguageTag = aSysLocaleOptions.GetRealLanguageTag(); + if ( nHint & ConfigurationHints::Locale ) + { + moCharClass.emplace( rLanguageTag ); + } + moLocaleData.emplace(rLanguageTag, getDateAcceptancePatternsConfig()); +} + +std::vector<OUString> SvtSysLocale_Impl::getDateAcceptancePatternsConfig() const +{ + OUString aStr( aSysLocaleOptions.GetDatePatternsConfigString()); + if (aStr.isEmpty()) + return {}; // reset + ::std::vector< OUString > aVec; + for (sal_Int32 nIndex = 0; nIndex >= 0; /*nop*/) + { + OUString aTok( aStr.getToken( 0, ';', nIndex)); + if (!aTok.isEmpty()) + aVec.push_back( aTok); + } + return aVec; +} + +SvtSysLocale::SvtSysLocale() +{ + std::unique_lock aGuard( GetMutex() ); + pImpl = g_pSysLocale.lock(); + if ( !pImpl ) + { + pImpl = std::make_shared<SvtSysLocale_Impl>(); + g_pSysLocale = pImpl; + } +} + +SvtSysLocale::~SvtSysLocale() +{ + std::unique_lock aGuard( GetMutex() ); + pImpl.reset(); +} + +const LocaleDataWrapper& SvtSysLocale::GetLocaleData() const +{ + return *(pImpl->moLocaleData); +} + +const CharClass& SvtSysLocale::GetCharClass() const +{ + return pImpl->GetCharClass(); +} + +SvtSysLocaleOptions& SvtSysLocale::GetOptions() const +{ + return pImpl->aSysLocaleOptions; +} + +const LanguageTag& SvtSysLocale::GetLanguageTag() const +{ + if (comphelper::LibreOfficeKit::isActive()) + return comphelper::LibreOfficeKit::getLocale(); + + return pImpl->aSysLocaleOptions.GetRealLanguageTag(); +} + +const LanguageTag& SvtSysLocale::GetUILanguageTag() const +{ + if (comphelper::LibreOfficeKit::isActive()) + return comphelper::LibreOfficeKit::getLanguageTag(); + + return pImpl->aSysLocaleOptions.GetRealUILanguageTag(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/misc/wincodepage.cxx b/unotools/source/misc/wincodepage.cxx new file mode 100644 index 0000000000..79353e47ce --- /dev/null +++ b/unotools/source/misc/wincodepage.cxx @@ -0,0 +1,141 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <sal/config.h> + +#include <string_view> + +#include <unotools/wincodepage.hxx> +#include <rtl/textenc.h> + +namespace{ + +struct LangEncodingDef +{ + const std::u16string_view msLangStr; + rtl_TextEncoding meTextEncoding; +}; + +// See https://msdn.microsoft.com/en-us/library/windows/desktop/dd317756 +rtl_TextEncoding impl_getWinTextEncodingFromLangStrANSI(const OUString& sLanguage) +{ + static constexpr LangEncodingDef aLanguageTab[] = + { + { u"en", RTL_TEXTENCODING_MS_1252 }, // Most used -> first in list + { u"th", RTL_TEXTENCODING_MS_874 }, + { u"ja", RTL_TEXTENCODING_MS_932 }, + { u"zh-cn", RTL_TEXTENCODING_MS_936 }, // Chinese (simplified) - must go before "zh" + { u"ko", RTL_TEXTENCODING_MS_949 }, + { u"zh", RTL_TEXTENCODING_MS_950 }, // Chinese (traditional) + { u"bs", RTL_TEXTENCODING_MS_1250 }, + { u"cs", RTL_TEXTENCODING_MS_1250 }, + { u"hr", RTL_TEXTENCODING_MS_1250 }, + { u"hu", RTL_TEXTENCODING_MS_1250 }, + { u"pl", RTL_TEXTENCODING_MS_1250 }, + { u"ro", RTL_TEXTENCODING_MS_1250 }, + { u"sk", RTL_TEXTENCODING_MS_1250 }, + { u"sl", RTL_TEXTENCODING_MS_1250 }, +// { "sr", RTL_TEXTENCODING_MS_1250 }, + { u"sq", RTL_TEXTENCODING_MS_1250 }, + { u"be", RTL_TEXTENCODING_MS_1251 }, + { u"bg", RTL_TEXTENCODING_MS_1251 }, + { u"mk", RTL_TEXTENCODING_MS_1251 }, + { u"ru", RTL_TEXTENCODING_MS_1251 }, + { u"sr", RTL_TEXTENCODING_MS_1251 }, + { u"uk", RTL_TEXTENCODING_MS_1251 }, + { u"es", RTL_TEXTENCODING_MS_1252 }, + { u"el", RTL_TEXTENCODING_MS_1253 }, + { u"tr", RTL_TEXTENCODING_MS_1254 }, + { u"he", RTL_TEXTENCODING_MS_1255 }, + { u"ar", RTL_TEXTENCODING_MS_1256 }, + { u"et", RTL_TEXTENCODING_MS_1257 }, + { u"lt", RTL_TEXTENCODING_MS_1257 }, + { u"lv", RTL_TEXTENCODING_MS_1257 }, + { u"vi", RTL_TEXTENCODING_MS_1258 }, + }; + + for (auto& def : aLanguageTab) + { + if (sLanguage.startsWithIgnoreAsciiCase(def.msLangStr)) + return def.meTextEncoding; + } + + return RTL_TEXTENCODING_MS_1252; +} + +/* ----------------------------------------------------------------------- */ + +// See https://msdn.microsoft.com/en-us/library/windows/desktop/dd317756 +// See http://shapelib.maptools.org/codepage.html +rtl_TextEncoding impl_getWinTextEncodingFromLangStrOEM(const OUString& sLanguage) +{ + static constexpr LangEncodingDef aLanguageTab[] = + { + { u"de", RTL_TEXTENCODING_IBM_437 }, // OEM United States + { u"en-us", RTL_TEXTENCODING_IBM_437 }, // OEM United States + { u"fi", RTL_TEXTENCODING_IBM_437 }, // OEM United States + { u"fr-ca", RTL_TEXTENCODING_IBM_863 }, // OEM French Canadian; French Canadian (DOS) + { u"fr", RTL_TEXTENCODING_IBM_437 }, // OEM United States + { u"it", RTL_TEXTENCODING_IBM_437 }, // OEM United States + { u"nl", RTL_TEXTENCODING_IBM_437 }, // OEM United States + { u"sv", RTL_TEXTENCODING_IBM_437 }, // OEM United States + { u"el", RTL_TEXTENCODING_IBM_737 }, // OEM Greek (formerly 437G); Greek (DOS) + { u"et", RTL_TEXTENCODING_IBM_775 }, // OEM Baltic; Baltic (DOS) + { u"lt", RTL_TEXTENCODING_IBM_775 }, // OEM Baltic; Baltic (DOS) + { u"lv", RTL_TEXTENCODING_IBM_775 }, // OEM Baltic; Baltic (DOS) + { u"en", RTL_TEXTENCODING_IBM_850 }, // OEM Multilingual Latin 1; Western European (DOS) + { u"bs", RTL_TEXTENCODING_IBM_852 }, // OEM Latin 2; Central European (DOS) + { u"cs", RTL_TEXTENCODING_IBM_852 }, // OEM Latin 2; Central European (DOS) + { u"hr", RTL_TEXTENCODING_IBM_852 }, // OEM Latin 2; Central European (DOS) + { u"hu", RTL_TEXTENCODING_IBM_852 }, // OEM Latin 2; Central European (DOS) + { u"pl", RTL_TEXTENCODING_IBM_852 }, // OEM Latin 2; Central European (DOS) + { u"ro", RTL_TEXTENCODING_IBM_852 }, // OEM Latin 2; Central European (DOS) + { u"sk", RTL_TEXTENCODING_IBM_852 }, // OEM Latin 2; Central European (DOS) + { u"sl", RTL_TEXTENCODING_IBM_852 }, // OEM Latin 2; Central European (DOS) +// { "sr", RTL_TEXTENCODING_IBM_852 }, // OEM Latin 2; Central European (DOS) + { u"bg", RTL_TEXTENCODING_IBM_855 }, // OEM Cyrillic (primarily Russian) + { u"mk", RTL_TEXTENCODING_IBM_855 }, // OEM Cyrillic (primarily Russian) + { u"sr", RTL_TEXTENCODING_IBM_855 }, // OEM Cyrillic (primarily Russian) + { u"tr", RTL_TEXTENCODING_IBM_857 }, // OEM Turkish; Turkish (DOS) + { u"pt", RTL_TEXTENCODING_IBM_860 }, // OEM Portuguese; Portuguese (DOS) + { u"is", RTL_TEXTENCODING_IBM_861 }, // OEM Icelandic; Icelandic (DOS) + { u"he", RTL_TEXTENCODING_IBM_862 }, // OEM Hebrew; Hebrew (DOS) + { u"ar", RTL_TEXTENCODING_IBM_864 }, // OEM Arabic; Arabic (864) + { u"da", RTL_TEXTENCODING_IBM_865 }, // OEM Nordic; Nordic (DOS) + { u"nn", RTL_TEXTENCODING_IBM_865 }, // OEM Nordic; Nordic (DOS) + { u"be", RTL_TEXTENCODING_IBM_866 }, // OEM Russian; Cyrillic (DOS) + { u"ru", RTL_TEXTENCODING_IBM_866 }, // OEM Russian; Cyrillic (DOS) + { u"uk", RTL_TEXTENCODING_IBM_866 }, // OEM Russian; Cyrillic (DOS) + { u"th", RTL_TEXTENCODING_MS_874 }, // ANSI/OEM Thai (ISO 8859-11); Thai (Windows) + { u"ja", RTL_TEXTENCODING_MS_932 }, // ANSI/OEM Japanese; Japanese (Shift-JIS) + { u"zh-cn", RTL_TEXTENCODING_MS_936 }, // ANSI/OEM Simplified Chinese (PRC, Singapore); Chinese Simplified (GB2312) + { u"ko", RTL_TEXTENCODING_MS_949 }, // ANSI/OEM Korean (Unified Hangul Code) + { u"zh", RTL_TEXTENCODING_MS_950 }, // ANSI/OEM Traditional Chinese (Taiwan; Hong Kong SAR, PRC); Chinese Traditional (Big5) + { u"vi", RTL_TEXTENCODING_MS_1258 }, // ANSI/OEM Vietnamese; Vietnamese (Windows) + }; + + for (auto& def : aLanguageTab) + { + if (sLanguage.startsWithIgnoreAsciiCase(def.msLangStr)) + return def.meTextEncoding; + } + + return RTL_TEXTENCODING_IBM_850; +} + +} // namespace + +rtl_TextEncoding utl_getWinTextEncodingFromLangStr(const OUString& sLanguage, bool bOEM) +{ + return bOEM ? + impl_getWinTextEncodingFromLangStrOEM(sLanguage) : + impl_getWinTextEncodingFromLangStrANSI(sLanguage); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/streaming/streamhelper.cxx b/unotools/source/streaming/streamhelper.cxx new file mode 100644 index 0000000000..12f6ad7afd --- /dev/null +++ b/unotools/source/streaming/streamhelper.cxx @@ -0,0 +1,123 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <com/sun/star/io/BufferSizeExceededException.hpp> +#include <com/sun/star/io/IOException.hpp> +#include <com/sun/star/io/NotConnectedException.hpp> +#include <o3tl/safeint.hxx> +#include <unotools/streamhelper.hxx> + +namespace utl +{ + +sal_Int32 SAL_CALL OInputStreamHelper::readBytes(css::uno::Sequence< sal_Int8 >& aData, sal_Int32 nBytesToRead) +{ + if (!m_xLockBytes.is()) + throw css::io::NotConnectedException(OUString(), getXWeak()); + + if (nBytesToRead < 0) + throw css::io::BufferSizeExceededException(OUString(), getXWeak()); + + std::scoped_lock aGuard( m_aMutex ); + if (aData.getLength() < nBytesToRead) + aData.realloc(nBytesToRead); + + std::size_t nRead(0); + ErrCode nError = m_xLockBytes->ReadAt(m_nActPos, static_cast<void*>(aData.getArray()), nBytesToRead, &nRead); + m_nActPos += nRead; + + if (nError != ERRCODE_NONE) + throw css::io::IOException(OUString(), getXWeak()); + + // adjust sequence if data read is lower than the desired data + if (nRead < o3tl::make_unsigned(aData.getLength())) + aData.realloc( nRead ); + + return nRead; +} + +void SAL_CALL OInputStreamHelper::seek( sal_Int64 location ) +{ + std::scoped_lock aGuard( m_aMutex ); + m_nActPos = location; +} + +sal_Int64 SAL_CALL OInputStreamHelper::getPosition( ) +{ + return m_nActPos; +} + +sal_Int64 SAL_CALL OInputStreamHelper::getLength( ) +{ + if (!m_xLockBytes.is()) + return 0; + + std::scoped_lock aGuard( m_aMutex ); + SvLockBytesStat aStat; + m_xLockBytes->Stat( &aStat ); + return aStat.nSize; +} + +sal_Int32 SAL_CALL OInputStreamHelper::readSomeBytes(css::uno::Sequence< sal_Int8 >& aData, + sal_Int32 nMaxBytesToRead) +{ + // read all data desired + return readBytes(aData, nMaxBytesToRead); +} + +void SAL_CALL OInputStreamHelper::skipBytes(sal_Int32 nBytesToSkip) +{ + std::scoped_lock aGuard( m_aMutex ); + if (!m_xLockBytes.is()) + throw css::io::NotConnectedException(OUString(), getXWeak()); + + if (nBytesToSkip < 0) + throw css::io::BufferSizeExceededException(OUString(), getXWeak()); + + m_nActPos += nBytesToSkip; +} + +sal_Int32 SAL_CALL OInputStreamHelper::available() +{ + std::scoped_lock aGuard( m_aMutex ); + if (!m_xLockBytes.is()) + throw css::io::NotConnectedException(OUString(), getXWeak()); + + return m_nAvailable; +} + +void SAL_CALL OInputStreamHelper::closeInput() +{ + std::scoped_lock aGuard( m_aMutex ); + if (!m_xLockBytes.is()) + throw css::io::NotConnectedException(OUString(), getXWeak()); + + m_xLockBytes = nullptr; +} + +void SAL_CALL OInputStreamHelper::acquire() SAL_NOEXCEPT +{ + cppu::WeakImplHelper<css::io::XInputStream, css::io::XSeekable>::acquire(); +} + +} // namespace utl + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/streaming/streamwrap.cxx b/unotools/source/streaming/streamwrap.cxx new file mode 100644 index 0000000000..beb909f36b --- /dev/null +++ b/unotools/source/streaming/streamwrap.cxx @@ -0,0 +1,338 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <com/sun/star/io/BufferSizeExceededException.hpp> +#include <com/sun/star/io/NotConnectedException.hpp> +#include <o3tl/safeint.hxx> +#include <unotools/streamwrap.hxx> +#include <tools/stream.hxx> + +namespace utl +{ + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::io; +using namespace ::com::sun::star::lang; + +OInputStreamWrapper::OInputStreamWrapper( SvStream& _rStream ) + :m_pSvStream(&_rStream) + ,m_bSvStreamOwner(false) +{ +} + +OInputStreamWrapper::OInputStreamWrapper( SvStream* pStream, bool bOwner ) + :m_pSvStream( pStream ) + ,m_bSvStreamOwner( bOwner ) +{ +} + +OInputStreamWrapper::OInputStreamWrapper( std::unique_ptr<SvStream> pStream ) + :m_pSvStream( pStream.release() ) + ,m_bSvStreamOwner( true ) +{ +} + +OInputStreamWrapper::~OInputStreamWrapper() +{ + if( m_bSvStreamOwner ) + delete m_pSvStream; +} + +sal_Int32 SAL_CALL OInputStreamWrapper::readBytes(css::uno::Sequence< sal_Int8 >& aData, sal_Int32 nBytesToRead) +{ + checkConnected(); + + if (nBytesToRead < 0) + throw css::io::BufferSizeExceededException(OUString(), getXWeak()); + + std::scoped_lock aGuard( m_aMutex ); + + if (aData.getLength() < nBytesToRead) + aData.realloc(nBytesToRead); + + sal_uInt32 nRead = m_pSvStream->ReadBytes(static_cast<void*>(aData.getArray()), nBytesToRead); + checkError(); + + // If read characters < MaxLength, adjust css::uno::Sequence + if (nRead < o3tl::make_unsigned(aData.getLength())) + aData.realloc( nRead ); + + return nRead; +} + +sal_Int32 OInputStreamWrapper::readSomeBytes(sal_Int8* pData, sal_Int32 nBytesToRead) +{ + checkConnected(); + + if (nBytesToRead < 0) + throw css::io::BufferSizeExceededException(OUString(), getXWeak()); + + std::scoped_lock aGuard( m_aMutex ); + + sal_uInt32 nRead = m_pSvStream->ReadBytes(static_cast<void*>(pData), nBytesToRead); + checkError(); + + return nRead; +} + +sal_Int32 SAL_CALL OInputStreamWrapper::readSomeBytes(css::uno::Sequence< sal_Int8 >& aData, sal_Int32 nMaxBytesToRead) +{ + checkError(); + + if (nMaxBytesToRead < 0) + throw css::io::BufferSizeExceededException(OUString(), getXWeak()); + + if (m_pSvStream->eof()) + { + aData.realloc(0); + return 0; + } + else + return readBytes(aData, nMaxBytesToRead); +} + +void SAL_CALL OInputStreamWrapper::skipBytes(sal_Int32 nBytesToSkip) +{ + std::scoped_lock aGuard( m_aMutex ); + checkError(); + + m_pSvStream->SeekRel(nBytesToSkip); + checkError(); +} + +sal_Int32 SAL_CALL OInputStreamWrapper::available() +{ + std::scoped_lock aGuard( m_aMutex ); + checkConnected(); + + sal_Int64 nAvailable = m_pSvStream->remainingSize(); + checkError(); + + return std::min<sal_Int64>(SAL_MAX_INT32, nAvailable); +} + +void SAL_CALL OInputStreamWrapper::closeInput() +{ + std::scoped_lock aGuard( m_aMutex ); + if (m_pSvStream) + { + if (m_bSvStreamOwner) + delete m_pSvStream; + + m_pSvStream = nullptr; + } +} + +void OInputStreamWrapper::checkConnected() const +{ + if (!m_pSvStream) + throw css::io::NotConnectedException(OUString(), const_cast<OInputStreamWrapper*>(this)->getXWeak()); +} + +void OInputStreamWrapper::checkError() const +{ + checkConnected(); + + auto const e = m_pSvStream->SvStream::GetError(); + if (e != ERRCODE_NONE) + // TODO: really evaluate the error + throw css::io::NotConnectedException("utl::OInputStreamWrapper error " + e.toString(), const_cast<OInputStreamWrapper*>(this)->getXWeak()); +} + +//= OSeekableInputStreamWrapper + +OSeekableInputStreamWrapper::~OSeekableInputStreamWrapper() = default; + +OSeekableInputStreamWrapper::OSeekableInputStreamWrapper(SvStream& _rStream) +{ + SetStream( &_rStream, false ); +} + +OSeekableInputStreamWrapper::OSeekableInputStreamWrapper(SvStream* _pStream, bool _bOwner) +{ + SetStream( _pStream, _bOwner ); +} + +void SAL_CALL OSeekableInputStreamWrapper::seek( sal_Int64 _nLocation ) +{ + std::scoped_lock aGuard( m_aMutex ); + checkConnected(); + + m_pSvStream->Seek(static_cast<sal_uInt64>(_nLocation)); + checkError(); +} + +sal_Int64 SAL_CALL OSeekableInputStreamWrapper::getPosition( ) +{ + std::scoped_lock aGuard( m_aMutex ); + checkConnected(); + + sal_uInt64 nPos = m_pSvStream->Tell(); + checkError(); + return static_cast<sal_Int64>(nPos); +} + +sal_Int64 SAL_CALL OSeekableInputStreamWrapper::getLength( ) +{ + std::scoped_lock aGuard( m_aMutex ); + checkConnected(); + + checkError(); + + sal_Int64 nEndPos = m_pSvStream->TellEnd(); + + return nEndPos; +} + +//= OOutputStreamWrapper + +OOutputStreamWrapper::OOutputStreamWrapper(SvStream& _rStream): + rStream(_rStream) +{} + +OOutputStreamWrapper::~OOutputStreamWrapper() {} + +void SAL_CALL OOutputStreamWrapper::writeBytes(const css::uno::Sequence< sal_Int8 >& aData) +{ + sal_uInt32 nWritten = rStream.WriteBytes(aData.getConstArray(), aData.getLength()); + ErrCode err = rStream.GetError(); + if ( (ERRCODE_NONE != err) + || (nWritten != static_cast<sal_uInt32>(aData.getLength())) + ) + { + throw css::io::BufferSizeExceededException(OUString(), getXWeak()); + } +} + +void SAL_CALL OOutputStreamWrapper::flush() +{ + rStream.FlushBuffer(); + checkError(); +} + +void SAL_CALL OOutputStreamWrapper::closeOutput() +{ +} + +void OOutputStreamWrapper::checkError() const +{ + if (rStream.GetError() != ERRCODE_NONE) + // TODO: really evaluate the error + throw css::io::NotConnectedException(OUString(), const_cast<OOutputStreamWrapper*>(this)->getXWeak()); +} + +//= OSeekableOutputStreamWrapper + +OSeekableOutputStreamWrapper::OSeekableOutputStreamWrapper(SvStream& _rStream) + :OOutputStreamWrapper(_rStream) +{ +} + +OSeekableOutputStreamWrapper::~OSeekableOutputStreamWrapper() {} + +Any SAL_CALL OSeekableOutputStreamWrapper::queryInterface( const Type& _rType ) +{ + Any aReturn = OOutputStreamWrapper::queryInterface(_rType); + if (!aReturn.hasValue()) + aReturn = OSeekableOutputStreamWrapper_Base::queryInterface(_rType); + return aReturn; +} + +void SAL_CALL OSeekableOutputStreamWrapper::seek( sal_Int64 _nLocation ) +{ + rStream.Seek(static_cast<sal_uInt32>(_nLocation)); + checkError(); +} + +sal_Int64 SAL_CALL OSeekableOutputStreamWrapper::getPosition( ) +{ + sal_uInt64 nPos = rStream.Tell(); + checkError(); + return static_cast<sal_Int64>(nPos); +} + +sal_Int64 SAL_CALL OSeekableOutputStreamWrapper::getLength( ) +{ + checkError(); + + sal_Int64 nEndPos = rStream.TellEnd(); + + return nEndPos; +} + +OStreamWrapper::~OStreamWrapper() = default; + +OStreamWrapper::OStreamWrapper(SvStream& _rStream) +{ + SetStream( &_rStream, false ); +} + +OStreamWrapper::OStreamWrapper(std::unique_ptr<SvStream> pStream) +{ + SetStream( pStream.release(), true ); +} + +OStreamWrapper::OStreamWrapper(SvStream* pStream, bool bOwner) +{ + SetStream( pStream, bOwner ); +} + +css::uno::Reference< css::io::XInputStream > SAL_CALL OStreamWrapper::getInputStream( ) +{ + return this; +} + +css::uno::Reference< css::io::XOutputStream > SAL_CALL OStreamWrapper::getOutputStream( ) +{ + return this; +} + +void SAL_CALL OStreamWrapper::writeBytes(const css::uno::Sequence< sal_Int8 >& aData) +{ + sal_uInt32 nWritten = m_pSvStream->WriteBytes(aData.getConstArray(), aData.getLength()); + ErrCode err = m_pSvStream->GetError(); + if ( (ERRCODE_NONE != err) + || (nWritten != static_cast<sal_uInt32>(aData.getLength())) + ) + { + throw css::io::BufferSizeExceededException(OUString(), getXWeak()); + } +} + +void SAL_CALL OStreamWrapper::flush() +{ + m_pSvStream->FlushBuffer(); + if (m_pSvStream->GetError() != ERRCODE_NONE) + throw css::io::NotConnectedException(OUString(), getXWeak()); +} + +void SAL_CALL OStreamWrapper::closeOutput() +{ +} + +void SAL_CALL OStreamWrapper::truncate() +{ + m_pSvStream->SetStreamSize(0); +} + +} // namespace utl + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/ucbhelper/XTempFile.hxx b/unotools/source/ucbhelper/XTempFile.hxx new file mode 100644 index 0000000000..2b0ec33cb4 --- /dev/null +++ b/unotools/source/ucbhelper/XTempFile.hxx @@ -0,0 +1,119 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +#pragma once + +#include <optional> +#include <com/sun/star/io/XInputStream.hpp> +#include <com/sun/star/io/XOutputStream.hpp> +#include <com/sun/star/io/XTempFile.hpp> +#include <com/sun/star/io/XTruncate.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/beans/XPropertyAccess.hpp> +#include <com/sun/star/beans/XFastPropertySet.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <cppuhelper/implbase.hxx> +#include <mutex> +#include <unotools/tempfile.hxx> + +namespace com::sun::star::uno { class XComponentContext; } + +class SvStream; +namespace utl { class TempFile; } + + +typedef ::cppu::WeakImplHelper< css::io::XTempFile + , css::io::XInputStream + , css::io::XOutputStream + , css::io::XTruncate + , css::beans::XPropertySet + , css::beans::XFastPropertySet + , css::beans::XPropertyAccess + , css::lang::XServiceInfo> OTempFileBase; + +class OTempFileService : public OTempFileBase +{ + std::optional<utl::TempFileNamed> mpTempFile; + std::mutex maMutex; + SvStream* mpStream; + bool mbRemoveFile; + bool mbInClosed; + bool mbOutClosed; + +protected: + void checkError () const; + void checkConnected (); + +public: + explicit OTempFileService (css::uno::Reference< css::uno::XComponentContext > const & context); + + //Methods + // XTypeProvider + virtual css::uno::Sequence< css::uno::Type > SAL_CALL getTypes( ) override; + + // XServiceInfo + virtual sal_Bool SAL_CALL supportsService(const OUString& sServiceName) override; + virtual OUString SAL_CALL getImplementationName() override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override; + + // XTempFile + virtual sal_Bool SAL_CALL getRemoveFile() override; + virtual void SAL_CALL setRemoveFile( sal_Bool _removefile ) override; + virtual OUString SAL_CALL getUri() override; + virtual OUString SAL_CALL getResourceName() override; + + // XInputStream + virtual ::sal_Int32 SAL_CALL readBytes( css::uno::Sequence< ::sal_Int8 >& aData, ::sal_Int32 nBytesToRead ) override; + virtual ::sal_Int32 SAL_CALL readSomeBytes( css::uno::Sequence< ::sal_Int8 >& aData, ::sal_Int32 nMaxBytesToRead ) override; + virtual void SAL_CALL skipBytes( ::sal_Int32 nBytesToSkip ) override; + virtual ::sal_Int32 SAL_CALL available( ) override; + virtual void SAL_CALL closeInput( ) override; + // XOutputStream + virtual void SAL_CALL writeBytes( const css::uno::Sequence< ::sal_Int8 >& aData ) override; + virtual void SAL_CALL flush( ) override; + virtual void SAL_CALL closeOutput( ) override; + // XSeekable + virtual void SAL_CALL seek( sal_Int64 location ) override; + virtual sal_Int64 SAL_CALL getPosition( ) override; + virtual sal_Int64 SAL_CALL getLength( ) override; + // XStream + virtual css::uno::Reference< css::io::XInputStream > SAL_CALL getInputStream( ) override; + virtual css::uno::Reference< css::io::XOutputStream > SAL_CALL getOutputStream( ) override; + // XTruncate + virtual void SAL_CALL truncate() override; + + // XPropertySet + virtual ::css::uno::Reference< ::css::beans::XPropertySetInfo > SAL_CALL getPropertySetInfo() override; + virtual void SAL_CALL setPropertyValue( const ::rtl::OUString& aPropertyName, const ::css::uno::Any& aValue ) override; + virtual ::css::uno::Any SAL_CALL getPropertyValue( const ::rtl::OUString& PropertyName ) override; + virtual void SAL_CALL addPropertyChangeListener( const ::rtl::OUString& aPropertyName, const ::css::uno::Reference< ::css::beans::XPropertyChangeListener >& xListener ) override; + virtual void SAL_CALL removePropertyChangeListener( const ::rtl::OUString& aPropertyName, const ::css::uno::Reference< ::css::beans::XPropertyChangeListener >& aListener ) override; + virtual void SAL_CALL addVetoableChangeListener( const ::rtl::OUString& PropertyName, const ::css::uno::Reference< ::css::beans::XVetoableChangeListener >& aListener ) override; + virtual void SAL_CALL removeVetoableChangeListener( const ::rtl::OUString& PropertyName, const ::css::uno::Reference< ::css::beans::XVetoableChangeListener >& aListener ) override; + // XFastPropertySet + virtual void SAL_CALL setFastPropertyValue( ::sal_Int32 nHandle, const ::css::uno::Any& aValue ) override; + virtual ::css::uno::Any SAL_CALL getFastPropertyValue( ::sal_Int32 nHandle ) override; + // XPropertyAccess + virtual ::css::uno::Sequence< ::css::beans::PropertyValue > SAL_CALL getPropertyValues() override; + virtual void SAL_CALL setPropertyValues( const ::css::uno::Sequence< ::css::beans::PropertyValue >& aProps ) override; + + + virtual ~OTempFileService () override; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/ucbhelper/localfilehelper.cxx b/unotools/source/ucbhelper/localfilehelper.cxx new file mode 100644 index 0000000000..bdabd5f0ac --- /dev/null +++ b/unotools/source/ucbhelper/localfilehelper.cxx @@ -0,0 +1,92 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <com/sun/star/sdbc/XResultSet.hpp> +#include <com/sun/star/ucb/XContentAccess.hpp> +#include <com/sun/star/ucb/CommandAbortedException.hpp> +#include <comphelper/DirectoryHelper.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/sequence.hxx> +#include <sal/log.hxx> +#include <unotools/localfilehelper.hxx> +#include <rtl/ustring.hxx> +#include <ucbhelper/content.hxx> +#include <vector> + +using namespace ::osl; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::ucb; + +namespace utl +{ + +css::uno::Sequence < OUString > LocalFileHelper::GetFolderContents( const OUString& rFolder, bool bFolder ) +{ + std::vector< OUString > vFiles; + try + { + ::ucbhelper::Content aCnt( + rFolder, Reference< XCommandEnvironment >(), + comphelper::getProcessComponentContext() ); + Reference< css::sdbc::XResultSet > xResultSet; + css::uno::Sequence< OUString > aProps { "Url" }; + + try + { + ::ucbhelper::ResultSetInclude eInclude = bFolder ? ::ucbhelper::INCLUDE_FOLDERS_AND_DOCUMENTS : ::ucbhelper::INCLUDE_DOCUMENTS_ONLY; + xResultSet = aCnt.createCursor( aProps, eInclude ); + } + catch( css::ucb::CommandAbortedException& ) + { + } + catch( Exception& ) + { + } + + if ( xResultSet.is() ) + { + Reference< XContentAccess > xContentAccess( xResultSet, UNO_QUERY ); + try + { + while ( xResultSet->next() ) + vFiles.push_back( xContentAccess->queryContentIdentifierString() ); + } + catch( css::ucb::CommandAbortedException& ) + { + } + catch( Exception& ) + { + } + } + } + catch( Exception& ) + { + } + + return comphelper::containerToSequence(vFiles); +} + +void removeTree(OUString const & url) { + const bool bError = comphelper::DirectoryHelper::deleteDirRecursively(url); + SAL_WARN_IF(bError, "desktop.app", "error removing directory " << url); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/ucbhelper/progresshandlerwrap.cxx b/unotools/source/ucbhelper/progresshandlerwrap.cxx new file mode 100644 index 0000000000..29c1067ec7 --- /dev/null +++ b/unotools/source/ucbhelper/progresshandlerwrap.cxx @@ -0,0 +1,88 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <unotools/progresshandlerwrap.hxx> +#include <com/sun/star/task/XStatusIndicator.hpp> +#include <utility> + +namespace utl +{ + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::task; +using namespace ::com::sun::star::ucb; + +ProgressHandlerWrap::ProgressHandlerWrap( css::uno::Reference< css::task::XStatusIndicator > xSI ) +: m_xStatusIndicator(std::move( xSI )) +{ +} + +static bool getStatusFromAny_Impl( const Any& aAny, OUString& aText, sal_Int32& nNum ) +{ + bool bNumIsSet = false; + + Sequence< Any > aSetList; + if( aAny >>= aSetList ) + for( const auto& rSet : std::as_const(aSetList) ) + { + if( !bNumIsSet && ( rSet >>= nNum ) ) + bNumIsSet = true; + else + aText.isEmpty() && ( rSet >>= aText ); + } + + return bNumIsSet; +} + +void SAL_CALL ProgressHandlerWrap::push( const Any& Status ) +{ + if( !m_xStatusIndicator.is() ) + return; + + OUString aText; + sal_Int32 nRange; + + if( getStatusFromAny_Impl( Status, aText, nRange ) ) + m_xStatusIndicator->start( aText, nRange ); +} + +void SAL_CALL ProgressHandlerWrap::update( const Any& Status ) +{ + if( !m_xStatusIndicator.is() ) + return; + + OUString aText; + sal_Int32 nValue; + + if( getStatusFromAny_Impl( Status, aText, nValue ) ) + { + if( !aText.isEmpty() ) m_xStatusIndicator->setText( aText ); + m_xStatusIndicator->setValue( nValue ); + } +} + +void SAL_CALL ProgressHandlerWrap::pop() +{ + if( m_xStatusIndicator.is() ) + m_xStatusIndicator->end(); +} + +} // namespace utl + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/ucbhelper/tempfile.cxx b/unotools/source/ucbhelper/tempfile.cxx new file mode 100644 index 0000000000..992ff8814d --- /dev/null +++ b/unotools/source/ucbhelper/tempfile.cxx @@ -0,0 +1,808 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <cassert> +#include <utility> + +#include <com/sun/star/io/BufferSizeExceededException.hpp> +#include <com/sun/star/io/NotConnectedException.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <unotools/tempfile.hxx> +#include <rtl/ustring.hxx> +#include <o3tl/safeint.hxx> +#include <osl/mutex.hxx> +#include <osl/detail/file.h> +#include <osl/file.hxx> +#include <tools/time.hxx> +#include <tools/debug.hxx> +#include <tools/Guid.hxx> +#include <comphelper/DirectoryHelper.hxx> + +#ifdef UNX +#include <unistd.h> +#elif defined( _WIN32 ) +#include <process.h> +#endif + +using namespace osl; + +namespace +{ + OUString gTempNameBase_Impl; +} + +namespace utl +{ + +static OUString getParentName( std::u16string_view aFileName ) +{ + size_t lastIndex = aFileName.rfind( '/' ); + OUString aParent; + + if (lastIndex != std::u16string_view::npos) + { + aParent = aFileName.substr(0, lastIndex); + + if (aParent.endsWith(":") && aParent.getLength() == 6) + aParent += "/"; + + if (aParent.equalsIgnoreAsciiCase("file://")) + aParent = "file:///"; + } + + return aParent; +} + +static bool ensuredir( const OUString& rUnqPath ) +{ + OUString aPath; + if ( rUnqPath.isEmpty() ) + return false; + + // remove trailing slash + if ( rUnqPath.endsWith("/") ) + aPath = rUnqPath.copy( 0, rUnqPath.getLength() - 1 ); + else + aPath = rUnqPath; + + // HACK: create directory on a mount point with nobrowse option + // returns ENOSYS in any case !! + osl::Directory aDirectory( aPath ); + osl::FileBase::RC nError = aDirectory.open(); + aDirectory.close(); + if( nError == osl::File::E_None ) + return true; + + // try to create the directory + nError = osl::Directory::create( aPath ); + bool bSuccess = ( nError == osl::File::E_None || nError == osl::FileBase::E_EXIST ); + if( !bSuccess ) + { + // perhaps parent(s) don't exist + OUString aParentDir = getParentName( aPath ); + if ( aParentDir != aPath ) + { + bSuccess = ensuredir( getParentName( aPath ) ); + + // After parent directory structure exists try it one's more + if ( bSuccess ) + { + // Parent directory exists, retry creation of directory + nError = osl::Directory::create( aPath ); + bSuccess =( nError == osl::File::E_None || nError == osl::FileBase::E_EXIST ); + } + } + } + + return bSuccess; +} + +static OUString ConstructTempDir_Impl( const OUString* pParent, bool bCreateParentDirs ) +{ + OUString aName; + + // Ignore pParent on iOS. We don't want to create any temp files + // in the same directory where the document being edited is. +#ifndef IOS + if ( pParent && !pParent->isEmpty() ) + { + // test for valid filename + OUString aRet; + if ((osl::FileBase::getSystemPathFromFileURL(*pParent, aRet) + == osl::FileBase::E_None) + && (osl::FileBase::getFileURLFromSystemPath(aRet, aRet) + == osl::FileBase::E_None)) + { + ::osl::DirectoryItem aItem; + sal_Int32 i = aRet.getLength(); + if ( aRet[i-1] == '/' ) + i--; + + if ( DirectoryItem::get( aRet.copy(0, i), aItem ) == FileBase::E_None || bCreateParentDirs ) + aName = aRet; + } + } +#else + (void) pParent; + (void) bCreateParentDirs; +#endif + + if ( aName.isEmpty() ) + { + if (gTempNameBase_Impl.isEmpty()) + { + OUString ustrTempDirURL; + ::osl::FileBase::RC rc = ::osl::File::getTempDirURL( + ustrTempDirURL ); + if (rc == ::osl::FileBase::E_None) + gTempNameBase_Impl = ustrTempDirURL; + ensuredir( aName ); + } + // if no parent or invalid parent : use default directory + DBG_ASSERT( !gTempNameBase_Impl.isEmpty(), "No TempDir!" ); + aName = gTempNameBase_Impl; + } + + // Make sure that directory ends with a separator + if( !aName.isEmpty() && !aName.endsWith("/") ) + aName += "/"; + + return aName; +} + +namespace { + +class Tokens { +public: + virtual bool next(OUString *) = 0; + +protected: + virtual ~Tokens() {} // avoid warnings +}; + +class SequentialTokens: public Tokens { +public: + explicit SequentialTokens(bool showZero): m_value(0), m_show(showZero) {} + + bool next(OUString * token) override { + assert(token != nullptr); + if (m_value == SAL_MAX_UINT32) { + return false; + } + *token = m_show ? OUString::number(m_value) : OUString(); + ++m_value; + m_show = true; + return true; + } + +private: + sal_uInt32 m_value; + bool m_show; +}; + +class UniqueTokens: public Tokens { +public: + UniqueTokens(): m_count(0) {} + + bool next(OUString * token) override { + assert(token != nullptr); + // Because of the shared globalValue, no single instance of UniqueTokens + // is guaranteed to exhaustively test all 36^6 possible values, but stop + // after that many attempts anyway: + sal_uInt32 radix = 36; + sal_uInt32 max = radix * radix * radix * radix * radix * radix; + // 36^6 == 2'176'782'336 < SAL_MAX_UINT32 == 4'294'967'295 + if (m_count == max) { + return false; + } + sal_uInt32 v; + { + osl::MutexGuard g(osl::Mutex::getGlobalMutex()); + globalValue + = ((globalValue == SAL_MAX_UINT32 + ? tools::Time::GetSystemTicks() : globalValue + 1) + % max); + v = globalValue; + } + *token = OUString::number(v, radix); + ++m_count; + return true; + } + +private: + static sal_uInt32 globalValue; + + sal_uInt32 m_count; +}; + +} + +sal_uInt32 UniqueTokens::globalValue = SAL_MAX_UINT32; + +namespace +{ + class TempDirCreatedObserver : public DirectoryCreationObserver + { + public: + virtual void DirectoryCreated(const OUString& aDirectoryUrl) override + { + File::setAttributes( aDirectoryUrl, osl_File_Attribute_OwnRead | + osl_File_Attribute_OwnWrite | osl_File_Attribute_OwnExe ); + }; + }; +}; + +static OUString lcl_createName( + std::u16string_view rLeadingChars, Tokens & tokens, std::u16string_view pExtension, + const OUString* pParent, bool bDirectory, bool bKeep, bool bLock, + bool bCreateParentDirs ) +{ + OUString aName = ConstructTempDir_Impl( pParent, bCreateParentDirs ); + if ( bCreateParentDirs ) + { + size_t nOffset = rLeadingChars.rfind(u"/"); + OUString aDirName; + if (std::u16string_view::npos != nOffset) + aDirName = aName + rLeadingChars.substr( 0, nOffset ); + else + aDirName = aName; + TempDirCreatedObserver observer; + FileBase::RC err = Directory::createPath( aDirName, &observer ); + if ( err != FileBase::E_None && err != FileBase::E_EXIST ) + return OUString(); + } + aName += rLeadingChars; + + OUString token; + while (tokens.next(&token)) + { + OUString aTmp( aName + token ); + if ( !pExtension.empty() ) + aTmp += pExtension; + else + aTmp += ".tmp"; + if ( bDirectory ) + { + FileBase::RC err = Directory::create( + aTmp, + (osl_File_OpenFlag_Read | osl_File_OpenFlag_Write + | osl_File_OpenFlag_Private)); + if ( err == FileBase::E_None ) + { + // !bKeep: only for creating a name, not a file or directory + if ( bKeep || Directory::remove( aTmp ) == FileBase::E_None ) + return aTmp; + else + return OUString(); + } + else if ( err != FileBase::E_EXIST ) + // if f.e. name contains invalid chars stop trying to create dirs + return OUString(); + } + else + { + DBG_ASSERT( bKeep, "Too expensive, use directory for creating name!" ); + File aFile( aTmp ); + FileBase::RC err = aFile.open( + osl_File_OpenFlag_Create | osl_File_OpenFlag_Private + | (bLock ? 0 : osl_File_OpenFlag_NoLock)); + if ( err == FileBase::E_None || (bLock && err == FileBase::E_NOLCK) ) + { + aFile.close(); + return aTmp; + } + else if ( err != FileBase::E_EXIST ) + { + // if f.e. name contains invalid chars stop trying to create dirs + // but if there is a folder with such name proceed further + + DirectoryItem aTmpItem; + FileStatus aTmpStatus( osl_FileStatus_Mask_Type ); + if ( DirectoryItem::get( aTmp, aTmpItem ) != FileBase::E_None + || aTmpItem.getFileStatus( aTmpStatus ) != FileBase::E_None + || aTmpStatus.getFileType() != FileStatus::Directory ) + return OUString(); + } + } + } + return OUString(); +} + +static OUString CreateTempName_Impl( const OUString* pParent, bool bKeep, bool bDir = true ) +{ + OUString aEyeCatcher = "lu"; +#ifdef UNX +#ifdef DBG_UTIL + const char* eye = getenv("LO_TESTNAME"); + if(eye) + { + aEyeCatcher = OUString(eye, strlen(eye), RTL_TEXTENCODING_ASCII_US); + } +#else + static const pid_t pid = getpid(); + static const OUString aPidString = OUString::number(pid); + aEyeCatcher += aPidString; +#endif +#elif defined(_WIN32) + static const int pid = _getpid(); + static const OUString aPidString = OUString::number(pid); + aEyeCatcher += aPidString; +#endif + UniqueTokens t; + return lcl_createName( aEyeCatcher, t, u"", pParent, bDir, bKeep, + false, false); +} + +static OUString CreateTempNameFast() +{ + OUString aEyeCatcher = "lu"; +#ifdef UNX +#ifdef DBG_UTIL + const char* eye = getenv("LO_TESTNAME"); + if(eye) + { + aEyeCatcher = OUString(eye, strlen(eye), RTL_TEXTENCODING_ASCII_US); + } +#else + static const pid_t pid = getpid(); + static const OUString aPidString = OUString::number(pid); + aEyeCatcher += aPidString; +#endif +#elif defined(_WIN32) + static const int pid = _getpid(); + static const OUString aPidString = OUString::number(pid); + aEyeCatcher += aPidString; +#endif + + OUString aName = ConstructTempDir_Impl( /*pParent*/nullptr, /*bCreateParentDirs*/false ) + aEyeCatcher; + + tools::Guid aGuid(tools::Guid::Generate); + + return aName + aGuid.getOUString() + ".tmp" ; +} + +OUString CreateTempName() +{ + OUString aName(CreateTempName_Impl( nullptr, false )); + + // convert to file URL + OUString aTmp; + if ( !aName.isEmpty() ) + FileBase::getSystemPathFromFileURL( aName, aTmp ); + return aTmp; +} + +TempFileFast::TempFileFast( ) +{ +} + +TempFileFast::TempFileFast(TempFileFast && other) noexcept : + mxStream(std::move(other.mxStream)) +{ +} + +TempFileFast::~TempFileFast() +{ + CloseStream(); +} + +SvStream* TempFileFast::GetStream( StreamMode eMode ) +{ + if (!mxStream) + { + OUString aName = CreateTempNameFast(); +#ifdef _WIN32 + mxStream.reset(new SvFileStream(aName, eMode | StreamMode::TEMPORARY | StreamMode::DELETE_ON_CLOSE)); +#else + mxStream.reset(new SvFileStream(aName, eMode | StreamMode::TEMPORARY)); +#endif + } + return mxStream.get(); +} + +void TempFileFast::CloseStream() +{ + if (mxStream) + { +#if !defined _WIN32 + OUString aName = mxStream->GetFileName(); +#endif + mxStream.reset(); +#ifdef _WIN32 + // On Windows, the file is opened with FILE_FLAG_DELETE_ON_CLOSE, so it will delete as soon as the handle closes. + // On other platforms, we need to explicitly delete it. +#else + if (!aName.isEmpty() && (osl::FileBase::getFileURLFromSystemPath(aName, aName) == osl::FileBase::E_None)) + File::remove(aName); +#endif + } +} + +OUString CreateTempURL( const OUString* pParent, bool bDirectory ) +{ + return CreateTempName_Impl( pParent, true, bDirectory ); +} + +OUString CreateTempURL( std::u16string_view rLeadingChars, bool _bStartWithZero, + std::u16string_view pExtension, const OUString* pParent, + bool bCreateParentDirs ) +{ + SequentialTokens t(_bStartWithZero); + return lcl_createName( rLeadingChars, t, pExtension, pParent, false, + true, true, bCreateParentDirs ); +} + +TempFileNamed::TempFileNamed( const OUString* pParent, bool bDirectory ) + : bIsDirectory( bDirectory ) + , bKillingFileEnabled( false ) +{ + aName = CreateTempName_Impl( pParent, true, bDirectory ); +} + +TempFileNamed::TempFileNamed( std::u16string_view rLeadingChars, bool _bStartWithZero, + std::u16string_view pExtension, const OUString* pParent, + bool bCreateParentDirs ) + : bIsDirectory( false ) + , bKillingFileEnabled( false ) +{ + SequentialTokens t(_bStartWithZero); + aName = lcl_createName( rLeadingChars, t, pExtension, pParent, false, + true, true, bCreateParentDirs ); +} + +TempFileNamed::TempFileNamed(TempFileNamed && other) noexcept : + aName(std::move(other.aName)), pStream(std::move(other.pStream)), bIsDirectory(other.bIsDirectory), + bKillingFileEnabled(other.bKillingFileEnabled) +{ + other.bKillingFileEnabled = false; +} + +TempFileNamed::~TempFileNamed() +{ + if ( !bKillingFileEnabled ) + return; + + pStream.reset(); + if ( bIsDirectory ) + { + comphelper::DirectoryHelper::deleteDirRecursively(aName); + } + else + { + File::remove( aName ); + } +} + +bool TempFileNamed::IsValid() const +{ + return !aName.isEmpty(); +} + +OUString TempFileNamed::GetFileName() const +{ + OUString aTmp; + FileBase::getSystemPathFromFileURL(aName, aTmp); + return aTmp; +} + +OUString const & TempFileNamed::GetURL() const +{ + // if you request the URL, then you presumably want to access this via UCB, + // and UCB will want to open the file via a separate file handle, which means + // we have to make this file data actually hit disk. We do this here (and not + // elsewhere) to make the other (normal) paths fast. Flushing to disk + // really slows temp files down. + if (pStream) + pStream->Flush(); + return aName; +} + +SvStream* TempFileNamed::GetStream( StreamMode eMode ) +{ + if (!pStream) + { + if (!aName.isEmpty()) + pStream.reset(new SvFileStream(aName, eMode | StreamMode::TEMPORARY)); + else + pStream.reset(new SvMemoryStream); + } + + return pStream.get(); +} + +void TempFileNamed::CloseStream() +{ + pStream.reset(); +} + +OUString SetTempNameBaseDirectory( const OUString &rBaseName ) +{ + if( rBaseName.isEmpty() ) + return OUString(); + + OUString aUnqPath( rBaseName ); + + // remove trailing slash + if ( rBaseName.endsWith("/") ) + aUnqPath = rBaseName.copy( 0, rBaseName.getLength() - 1 ); + + // try to create the directory + bool bRet = false; + osl::FileBase::RC err = osl::Directory::create( aUnqPath ); + if ( err != FileBase::E_None && err != FileBase::E_EXIST ) + // perhaps parent(s) don't exist + bRet = ensuredir( aUnqPath ); + else + bRet = true; + + // failure to create base directory means returning an empty string + OUString aTmp; + if ( bRet ) + { + // append own internal directory + OUString &rTempNameBase_Impl = gTempNameBase_Impl; + rTempNameBase_Impl = rBaseName + "/"; + + TempFileNamed aBase( {}, true ); + if ( aBase.IsValid() ) + // use it in case of success + rTempNameBase_Impl = aBase.aName; + + // return system path of used directory + FileBase::getSystemPathFromFileURL( rTempNameBase_Impl, aTmp ); + } + + return aTmp; +} + +OUString GetTempNameBaseDirectory() +{ + return ConstructTempDir_Impl(nullptr, false); +} + + +TempFileFastService::TempFileFastService() +: mbInClosed( false ) +, mbOutClosed( false ) +{ + mpTempFile.emplace(); + mpStream = mpTempFile->GetStream(StreamMode::READWRITE); +} + +TempFileFastService::~TempFileFastService () +{ +} + +// XInputStream + +sal_Int32 SAL_CALL TempFileFastService::readBytes( css::uno::Sequence< sal_Int8 >& aData, sal_Int32 nBytesToRead ) +{ + std::unique_lock aGuard( maMutex ); + if ( mbInClosed ) + throw css::io::NotConnectedException ( OUString(), getXWeak() ); + + checkConnected(); + if (nBytesToRead < 0) + throw css::io::BufferSizeExceededException( OUString(), getXWeak()); + + if (aData.getLength() < nBytesToRead) + aData.realloc(nBytesToRead); + + sal_uInt32 nRead = mpStream->ReadBytes(static_cast<void*>(aData.getArray()), nBytesToRead); + checkError(); + + if (nRead < o3tl::make_unsigned(aData.getLength())) + aData.realloc( nRead ); + + return nRead; +} + +sal_Int32 SAL_CALL TempFileFastService::readSomeBytes( css::uno::Sequence< sal_Int8 >& aData, sal_Int32 nMaxBytesToRead ) +{ + { + std::unique_lock aGuard( maMutex ); + if ( mbInClosed ) + throw css::io::NotConnectedException ( OUString(), getXWeak() ); + + checkConnected(); + checkError(); + + if (nMaxBytesToRead < 0) + throw css::io::BufferSizeExceededException( OUString(), getXWeak() ); + + if (mpStream->eof()) + { + aData.realloc(0); + return 0; + } + } + return readBytes(aData, nMaxBytesToRead); +} + +void SAL_CALL TempFileFastService::skipBytes( sal_Int32 nBytesToSkip ) +{ + std::unique_lock aGuard( maMutex ); + if ( mbInClosed ) + throw css::io::NotConnectedException ( OUString(), getXWeak() ); + + checkConnected(); + checkError(); + mpStream->SeekRel(nBytesToSkip); + checkError(); +} + +sal_Int32 SAL_CALL TempFileFastService::available() +{ + std::unique_lock aGuard( maMutex ); + if ( mbInClosed ) + throw css::io::NotConnectedException ( OUString(), getXWeak() ); + + checkConnected(); + + sal_Int64 nAvailable = mpStream->remainingSize(); + checkError(); + + return std::min<sal_Int64>(SAL_MAX_INT32, nAvailable); +} + +void SAL_CALL TempFileFastService::closeInput() +{ + std::unique_lock aGuard( maMutex ); + if ( mbInClosed ) + throw css::io::NotConnectedException ( OUString(), getXWeak() ); + + mbInClosed = true; + + if ( mbOutClosed ) + { + // stream will be deleted by TempFile implementation + mpStream = nullptr; + mpTempFile.reset(); + } +} + +// XOutputStream + +void SAL_CALL TempFileFastService::writeBytes( const css::uno::Sequence< sal_Int8 >& aData ) +{ + std::unique_lock aGuard( maMutex ); + if ( mbOutClosed ) + throw css::io::NotConnectedException ( OUString(), getXWeak() ); + + checkConnected(); + sal_uInt32 nWritten = mpStream->WriteBytes(aData.getConstArray(), aData.getLength()); + checkError(); + if ( nWritten != static_cast<sal_uInt32>(aData.getLength())) + throw css::io::BufferSizeExceededException( OUString(), getXWeak() ); +} + +void SAL_CALL TempFileFastService::flush() +{ + std::unique_lock aGuard( maMutex ); + if ( mbOutClosed ) + throw css::io::NotConnectedException ( OUString(), getXWeak() ); + + checkConnected(); + mpStream->Flush(); + checkError(); +} + +void SAL_CALL TempFileFastService::closeOutput() +{ + std::unique_lock aGuard( maMutex ); + if ( mbOutClosed ) + throw css::io::NotConnectedException ( OUString(), getXWeak() ); + + mbOutClosed = true; + if (mpStream) + { + // so that if you then open the InputStream, you can read the content + mpStream->FlushBuffer(); + mpStream->Seek(0); + } + + if ( mbInClosed ) + { + // stream will be deleted by TempFile implementation + mpStream = nullptr; + mpTempFile.reset(); + } +} + +void TempFileFastService::checkError() const +{ + if (!mpStream || mpStream->SvStream::GetError () != ERRCODE_NONE ) + throw css::io::NotConnectedException ( OUString(), const_cast < TempFileFastService * > (this)->getXWeak() ); +} + +void TempFileFastService::checkConnected() +{ + if (!mpStream) + throw css::io::NotConnectedException ( OUString(), getXWeak() ); +} + +// XSeekable + +void SAL_CALL TempFileFastService::seek( sal_Int64 nLocation ) +{ + std::unique_lock aGuard( maMutex ); + checkConnected(); + checkError(); + if ( nLocation < 0 ) + throw css::lang::IllegalArgumentException(); + + sal_Int64 nNewLoc = mpStream->Seek(static_cast<sal_uInt32>(nLocation) ); + if ( nNewLoc != nLocation ) + throw css::lang::IllegalArgumentException(); + checkError(); +} + +sal_Int64 SAL_CALL TempFileFastService::getPosition() +{ + std::unique_lock aGuard( maMutex ); + checkConnected(); + + sal_uInt64 nPos = mpStream->Tell(); + checkError(); + return static_cast<sal_Int64>(nPos); +} + +sal_Int64 SAL_CALL TempFileFastService::getLength() +{ + std::unique_lock aGuard( maMutex ); + checkConnected(); + + checkError(); + + sal_Int64 nEndPos = mpStream->TellEnd(); + + return nEndPos; +} + +// XStream + +css::uno::Reference< css::io::XInputStream > SAL_CALL TempFileFastService::getInputStream() +{ + return this; +} + +css::uno::Reference< css::io::XOutputStream > SAL_CALL TempFileFastService::getOutputStream() +{ + return this; +} + +// XTruncate + +void SAL_CALL TempFileFastService::truncate() +{ + std::unique_lock aGuard( maMutex ); + checkConnected(); + // SetStreamSize() call does not change the position + mpStream->Seek( 0 ); + mpStream->SetStreamSize( 0 ); + checkError(); +} + + + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/ucbhelper/ucbhelper.cxx b/unotools/source/ucbhelper/ucbhelper.cxx new file mode 100644 index 0000000000..aaa9d214bb --- /dev/null +++ b/unotools/source/ucbhelper/ucbhelper.cxx @@ -0,0 +1,413 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <cassert> +#include <vector> + +#include <com/sun/star/sdbc/XResultSet.hpp> +#include <com/sun/star/task/XInteractionHandler.hpp> +#include <com/sun/star/task/InteractionHandler.hpp> +#include <com/sun/star/ucb/CommandAbortedException.hpp> +#include <com/sun/star/ucb/ContentInfo.hpp> +#include <com/sun/star/ucb/ContentInfoAttribute.hpp> +#include <com/sun/star/ucb/IOErrorCode.hpp> +#include <com/sun/star/ucb/InteractiveIOException.hpp> +#include <com/sun/star/ucb/NameClashException.hpp> +#include <com/sun/star/ucb/UniversalContentBroker.hpp> +#include <com/sun/star/ucb/XCommandEnvironment.hpp> +#include <com/sun/star/ucb/XContentAccess.hpp> +#include <com/sun/star/ucb/XUniversalContentBroker.hpp> +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/Exception.hpp> +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/uno/RuntimeException.hpp> +#include <com/sun/star/uno/Sequence.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/simplefileaccessinteraction.hxx> +#include <osl/file.hxx> +#include <rtl/ustring.hxx> +#include <sal/log.hxx> +#include <tools/datetime.hxx> +#include <tools/urlobj.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <ucbhelper/commandenvironment.hxx> +#include <ucbhelper/content.hxx> +#include <unotools/ucbhelper.hxx> + +namespace com::sun::star::ucb { class XProgressHandler; } +namespace com::sun::star::uno { class XComponentContext; } +namespace com::sun::star::util { struct DateTime; } + +namespace { + +OUString canonic(OUString const & url) { + INetURLObject o(url); + SAL_WARN_IF(o.HasError(), "unotools.ucbhelper", "Invalid URL \"" << url << '"'); + return o.GetMainURL(INetURLObject::DecodeMechanism::NONE); +} + +ucbhelper::Content content(OUString const & url) { + return ucbhelper::Content( + canonic(url), + utl::UCBContentHelper::getDefaultCommandEnvironment(), + comphelper::getProcessComponentContext()); +} + +ucbhelper::Content content(INetURLObject const & url) { + return ucbhelper::Content( + url.GetMainURL(INetURLObject::DecodeMechanism::NONE), + utl::UCBContentHelper::getDefaultCommandEnvironment(), + comphelper::getProcessComponentContext()); +} + +std::vector<OUString> getContents(OUString const & url) { + try { + std::vector<OUString> cs; + ucbhelper::Content c(content(url)); + css::uno::Sequence<OUString> args { "Title" }; + css::uno::Reference<css::sdbc::XResultSet> res( c.createCursor(args), css::uno::UNO_SET_THROW); + css::uno::Reference<css::ucb::XContentAccess> acc( res, css::uno::UNO_QUERY_THROW); + while (res->next()) { + cs.push_back(acc->queryContentIdentifierString()); + } + return cs; + } catch (css::uno::RuntimeException const &) { + throw; + } catch (css::ucb::CommandAbortedException const &) { + assert(false && "this cannot happen"); + throw; + } catch (css::uno::Exception const &) { + TOOLS_INFO_EXCEPTION("unotools.ucbhelper", "getContents(" << url << ")"); + return std::vector<OUString>(); + } +} + +OUString getCasePreservingUrl(const INetURLObject& url) { + return + content(url).executeCommand( + "getCasePreservingURL", + css::uno::Any()). + get<OUString>(); +} + +DateTime convert(css::util::DateTime const & dt) { + return DateTime(dt); +} + +} + +css::uno::Reference< css::ucb::XCommandEnvironment > utl::UCBContentHelper::getDefaultCommandEnvironment() +{ + css::uno::Reference< css::task::XInteractionHandler > xIH( + css::task::InteractionHandler::createWithParent( + comphelper::getProcessComponentContext(), nullptr ) ); + + css::uno::Reference< css::ucb::XProgressHandler > xProgress; + rtl::Reference<ucbhelper::CommandEnvironment> pCommandEnv = + new ::ucbhelper::CommandEnvironment( + new comphelper::SimpleFileAccessInteraction( xIH ), xProgress ); + + return pCommandEnv; +} + +bool utl::UCBContentHelper::IsDocument(OUString const & url) { + try { + return content(url).isDocument(); + } catch (css::uno::RuntimeException const &) { + throw; + } catch (css::ucb::CommandAbortedException const &) { + assert(false && "this cannot happen"); + throw; + } catch (css::uno::Exception const &) { + TOOLS_INFO_EXCEPTION("unotools.ucbhelper", "UCBContentHelper::IsDocument(" << url << ")"); + return false; + } +} + +css::uno::Any utl::UCBContentHelper::GetProperty( + OUString const & url, OUString const & property) +{ + try { + return content(url).getPropertyValue(property); + } catch (css::uno::RuntimeException const &) { + throw; + } catch (css::ucb::CommandAbortedException const &) { + assert(false && "this cannot happen"); + throw; + } catch (css::uno::Exception const &) { + TOOLS_INFO_EXCEPTION("unotools.ucbhelper", "UCBContentHelper::GetProperty(" << url << ", " << property << ")"); + return css::uno::Any(); + } +} + +bool utl::UCBContentHelper::IsFolder(OUString const & url) { + try { + return content(url).isFolder(); + } catch (css::uno::RuntimeException const &) { + throw; + } catch (css::ucb::CommandAbortedException const &) { + assert(false && "this cannot happen"); + throw; + } catch (css::uno::Exception const &) { + TOOLS_INFO_EXCEPTION("unotools.ucbhelper", "UCBContentHelper::IsFolder(" << url << ")"); + return false; + } +} + +bool utl::UCBContentHelper::GetTitle( + OUString const & url, OUString * title) +{ + assert(title != nullptr); + try { + return content(url).getPropertyValue("Title") >>= *title; + } catch (css::uno::RuntimeException const &) { + throw; + } catch (css::ucb::CommandAbortedException const &) { + assert(false && "this cannot happen"); + throw; + } catch (css::uno::Exception const &) { + TOOLS_INFO_EXCEPTION("unotools.ucbhelper", "UCBContentHelper::GetTitle(" << url << ")"); + return false; + } +} + +bool utl::UCBContentHelper::Kill(OUString const & url) { + try { + content(url).executeCommand( + "delete", + css::uno::Any(true)); + return true; + } catch (css::uno::RuntimeException const &) { + throw; + } catch (css::ucb::CommandAbortedException const &) { + assert(false && "this cannot happen"); + throw; + } catch (css::uno::Exception const &) { + TOOLS_INFO_EXCEPTION("unotools.ucbhelper", "UCBContentHelper::Kill(" << url << ")"); + return false; + } +} + +bool utl::UCBContentHelper::MakeFolder( + ucbhelper::Content & parent, OUString const & title, + ucbhelper::Content & result) +{ + bool exists = false; + try { + const css::uno::Sequence<css::ucb::ContentInfo> info( + parent.queryCreatableContentsInfo()); + for (const auto& rInfo : info) { + // Simply look for the first KIND_FOLDER: + if ((rInfo.Attributes + & css::ucb::ContentInfoAttribute::KIND_FOLDER) + != 0) + { + // Make sure the only required bootstrap property is "Title": + if ( rInfo.Properties.getLength() != 1 || rInfo.Properties[0].Name != "Title" ) + { + continue; + } + if (parent.insertNewContent(rInfo.Type, { "Title" }, { css::uno::Any(title) }, result)) + { + return true; + } + } + } + } catch (css::ucb::InteractiveIOException const & e) { + if (e.Code == css::ucb::IOErrorCode_ALREADY_EXISTING) { + exists = true; + } else { + TOOLS_INFO_EXCEPTION( + "unotools.ucbhelper", + "UCBContentHelper::MakeFolder(" << title << ")"); + } + } catch (css::ucb::NameClashException const &) { + exists = true; + } catch (css::uno::RuntimeException const &) { + throw; + } catch (css::ucb::CommandAbortedException const &) { + assert(false && "this cannot happen"); + throw; + } catch (css::uno::Exception const &) { + TOOLS_INFO_EXCEPTION( + "unotools.ucbhelper", + "UCBContentHelper::MakeFolder(" << title << ") "); + } + if (exists) { + INetURLObject o(parent.getURL()); + o.Append(title); + result = content(o); + return true; + } else { + return false; + } +} + +bool utl::UCBContentHelper::IsYounger( + OUString const & younger, OUString const & older) +{ + try { + return + convert( + content(younger).getPropertyValue( + "DateModified"). + get<css::util::DateTime>()) + > convert( + content(older).getPropertyValue( + "DateModified"). + get<css::util::DateTime>()); + } catch (css::uno::RuntimeException const &) { + throw; + } catch (css::ucb::CommandAbortedException const &) { + assert(false && "this cannot happen"); + throw; + } catch (css::uno::Exception const &) { + TOOLS_INFO_EXCEPTION( + "unotools.ucbhelper", + "UCBContentHelper::IsYounger(" << younger << ", " << older << ")"); + return false; + } +} + +bool utl::UCBContentHelper::Exists(OUString const & url) { + OUString pathname; + if (osl::FileBase::getSystemPathFromFileURL(url, pathname) + == osl::FileBase::E_None) + { + // Try to create a directory entry for the given URL: + OUString url2; + if (osl::FileBase::getFileURLFromSystemPath(pathname, url2) + == osl::FileBase::E_None) + { + // #106526 osl_getDirectoryItem is an existence check, no further + // osl_getFileStatus call necessary: + osl::DirectoryItem item; + return osl::DirectoryItem::get(url2, item) == osl::FileBase::E_None; + } else { + return false; + } + } else { + // Divide URL into folder and name part: + INetURLObject o(url); + OUString name( + o.getName( + INetURLObject::LAST_SEGMENT, true, + INetURLObject::DecodeMechanism::WithCharset)); + o.removeSegment(); + o.removeFinalSlash(); + std::vector<OUString> cs( + getContents(o.GetMainURL(INetURLObject::DecodeMechanism::NONE))); + return std::any_of(cs.begin(), cs.end(), + [&name](const OUString& rItem) { + return INetURLObject(rItem). + getName(INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::WithCharset). + equalsIgnoreAsciiCase(name); }); + } +} + +bool utl::UCBContentHelper::IsSubPath( + OUString const & parent, OUString const & child) +{ + // The comparison is done in the following way: + // - First, compare case sensitively + // - If names are different, try a fallback comparing case insensitively + // - If the last comparison succeeded, get case preserving normalized names + // for the files and compare them + // (The second step is required because retrieving the normalized names + // might be very expensive in some cases.) + INetURLObject candidate(child); + INetURLObject folder(parent); + if (candidate.GetProtocol() != folder.GetProtocol()) { + return false; + } + INetURLObject candidateLower(child.toAsciiLowerCase()); + INetURLObject folderLower(parent.toAsciiLowerCase()); + try { + INetURLObject tmp; + do { + if (candidate == folder + || (candidate.GetProtocol() == INetProtocol::File + && candidateLower == folderLower + && (getCasePreservingUrl(candidate) + == getCasePreservingUrl(folder)))) + { + return true; + } + tmp = candidate; + } while (candidate.removeSegment() && candidateLower.removeSegment() + && candidate != tmp); + // INetURLObject::removeSegment sometimes returns true without + // modifying the URL, e.g., in case of "file:///" + } catch (css::uno::RuntimeException const &) { + throw; + } catch (css::ucb::CommandAbortedException const &) { + assert(false && "this cannot happen"); + throw; + } catch (css::uno::Exception const &) { + TOOLS_INFO_EXCEPTION( + "unotools.ucbhelper", + "UCBContentHelper::IsSubPath(" << parent << ", " << child << ")"); + } + return false; +} + +bool utl::UCBContentHelper::EqualURLs( + OUString const & url1, OUString const & url2) +{ + if (url1.isEmpty() || url2.isEmpty()) { + return false; + } + css::uno::Reference< css::ucb::XUniversalContentBroker > ucb( + css::ucb::UniversalContentBroker::create( + comphelper::getProcessComponentContext())); + return + ucb->compareContentIds( + ucb->createContentIdentifier(canonic(url1)), + ucb->createContentIdentifier(canonic(url2))) + == 0; +} + +bool utl::UCBContentHelper::ensureFolder( + const css::uno::Reference< css::uno::XComponentContext >& xCtx, + const css::uno::Reference< css::ucb::XCommandEnvironment >& xEnv, + std::u16string_view rFolder, ucbhelper::Content & result) noexcept +{ + try + { + INetURLObject aURL( rFolder ); + OUString aTitle = aURL.getName( INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::WithCharset ); + aURL.removeSegment(); + ::ucbhelper::Content aParent; + + if ( ::ucbhelper::Content::create( aURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ), + xEnv, xCtx, aParent ) ) + { + return ::utl::UCBContentHelper::MakeFolder(aParent, aTitle, result); + } + } + catch (...) + { + } + + return false; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/ucbhelper/ucblockbytes.cxx b/unotools/source/ucbhelper/ucblockbytes.cxx new file mode 100644 index 0000000000..182b1f674e --- /dev/null +++ b/unotools/source/ucbhelper/ucblockbytes.cxx @@ -0,0 +1,1338 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include "ucblockbytes.hxx" + +#include <sal/log.hxx> +#include <comphelper/processfactory.hxx> +#include <salhelper/condition.hxx> +#include <osl/thread.hxx> +#include <osl/diagnose.h> +#include <tools/urlobj.hxx> +#include <tools/solar.h> +#include <ucbhelper/interactionrequest.hxx> +#include <com/sun/star/lang/XUnoTunnel.hpp> +#include <com/sun/star/task/XInteractionAbort.hpp> +#include <com/sun/star/ucb/InteractiveNetworkConnectException.hpp> +#include <com/sun/star/ucb/CommandFailedException.hpp> +#include <com/sun/star/ucb/ContentCreationException.hpp> +#include <com/sun/star/ucb/CommandAbortedException.hpp> +#include <com/sun/star/ucb/UnsupportedDataSinkException.hpp> +#include <com/sun/star/ucb/InteractiveIOException.hpp> +#include <com/sun/star/ucb/XContentIdentifier.hpp> +#include <com/sun/star/ucb/XContent.hpp> +#include <com/sun/star/io/IOException.hpp> +#include <com/sun/star/io/XActiveDataStreamer.hpp> +#include <com/sun/star/io/TempFile.hpp> +#include <com/sun/star/ucb/XCommandProcessor.hpp> +#include <com/sun/star/task/XInteractionHandler.hpp> +#include <com/sun/star/ucb/OpenCommandArgument2.hpp> +#include <com/sun/star/ucb/PostCommandArgument2.hpp> +#include <com/sun/star/ucb/OpenMode.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/beans/XPropertiesChangeNotifier.hpp> +#include <com/sun/star/beans/XPropertiesChangeListener.hpp> +#include <com/sun/star/io/XActiveDataSink.hpp> +#include <com/sun/star/io/XActiveDataControl.hpp> +#include <com/sun/star/io/XSeekable.hpp> +#include <cppuhelper/implbase.hxx> +#include <tools/debug.hxx> +#include <com/sun/star/io/XTruncate.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> + +#include <comphelper/bytereader.hxx> +#include <comphelper/storagehelper.hxx> +#include <ucbhelper/content.hxx> +#include <unotools/tempfile.hxx> +#include <mutex> +#include <utility> + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::io; +using namespace ::com::sun::star::ucb; +using namespace ::com::sun::star::task; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::beans; + +namespace utl +{ + +namespace { + +/** + Helper class for getting a XInputStream when opening a content + */ +class UcbDataSink_Impl : public ::cppu::WeakImplHelper< XActiveDataControl, XActiveDataSink > +{ + UcbLockBytesRef m_xLockBytes; + +public: + explicit UcbDataSink_Impl( UcbLockBytes* pLockBytes ) + : m_xLockBytes( pLockBytes ) + {} + + // XActiveDataControl. + virtual void SAL_CALL addListener ( const Reference<XStreamListener> &/*rxListener*/) override {} + virtual void SAL_CALL removeListener ( const Reference<XStreamListener> &/*rxListener*/) override {} + virtual void SAL_CALL start() override {} + virtual void SAL_CALL terminate() override + { m_xLockBytes->terminate(); } + + // XActiveDataSink. + virtual void SAL_CALL setInputStream ( const Reference<XInputStream> &rxInputStream) override + { m_xLockBytes->setInputStream(rxInputStream); } + virtual Reference<XInputStream> SAL_CALL getInputStream() override + { return m_xLockBytes->getInputStream(); } +}; + +/** + Helper class for getting a XStream when opening a content + */ +class UcbStreamer_Impl : public ::cppu::WeakImplHelper< XActiveDataStreamer, XActiveDataControl > +{ + Reference < XStream > m_xStream; + UcbLockBytesRef m_xLockBytes; + +public: + explicit UcbStreamer_Impl( UcbLockBytes* pLockBytes ) + : m_xLockBytes( pLockBytes ) + {} + + // XActiveDataControl. + virtual void SAL_CALL addListener ( const Reference<XStreamListener> &/*rxListener*/) override {} + virtual void SAL_CALL removeListener ( const Reference<XStreamListener> &/*rxListener*/) override {} + virtual void SAL_CALL start() override {} + virtual void SAL_CALL terminate() override + { m_xLockBytes->terminate(); } + + // XActiveDataStreamer + virtual void SAL_CALL setStream( const Reference< XStream >& aStream ) override + { m_xStream = aStream; m_xLockBytes->setStream( aStream ); } + virtual Reference< XStream > SAL_CALL getStream() override + { return m_xStream; } +}; + +/** + Helper class for managing interactions and progress when executing UCB commands + */ +class UcbTaskEnvironment : public ::cppu::WeakImplHelper< XCommandEnvironment > +{ + Reference< XInteractionHandler > m_xInteractionHandler; + Reference< XProgressHandler > m_xProgressHandler; + +public: + UcbTaskEnvironment( const Reference< XInteractionHandler>& rxInteractionHandler, + const Reference< XProgressHandler>& rxProgressHandler ) + : m_xInteractionHandler( rxInteractionHandler ) + , m_xProgressHandler( rxProgressHandler ) + {} + + virtual Reference<XInteractionHandler> SAL_CALL getInteractionHandler() override + { return m_xInteractionHandler; } + + virtual Reference<XProgressHandler> SAL_CALL getProgressHandler() override + { return m_xProgressHandler; } +}; + +/** + Helper class for property change notifies when executing UCB commands +*/ +class UcbPropertiesChangeListener_Impl : public ::cppu::WeakImplHelper< XPropertiesChangeListener > +{ +public: + UcbLockBytesRef m_xLockBytes; + + explicit UcbPropertiesChangeListener_Impl( UcbLockBytesRef xRef ) + : m_xLockBytes(std::move( xRef )) + {} + + virtual void SAL_CALL disposing ( const EventObject &/*rEvent*/) override {} + virtual void SAL_CALL propertiesChange ( const Sequence<PropertyChangeEvent> &rEvent) override; +}; + +} + +void SAL_CALL UcbPropertiesChangeListener_Impl::propertiesChange ( const Sequence<PropertyChangeEvent> &rEvent) +{ + for (const auto& rPropChangeEvent : rEvent) + { + if (rPropChangeEvent.PropertyName == "DocumentHeader") + { + m_xLockBytes->SetStreamValid(); + } + } +} + +namespace { + +class Moderator + : public osl::Thread +{ + // usage restriction: + // It might be possible, that the call to the interactionhandler and/or + // progresshandler is done asynchronously, while the 'execute' simply + // returns. This would imply that these class must be refcounted!!! + +public: + /// @throws ContentCreationException + /// @throws RuntimeException + Moderator( + Reference < XContent > const & xContent, + Reference < XInteractionHandler > const & xInteract, + Command aArg + ); + + enum class ResultType { + NORESULT, + + INTERACTIONREQUEST, // reply expected + + INPUTSTREAM, + STREAM, + + RESULT, + TIMEDOUT, + COMMANDABORTED, + COMMANDFAILED, + INTERACTIVEIO, + UNSUPPORTED, + GENERAL + }; + + class ConditionRes + : public salhelper::Condition + { + public: + ConditionRes(osl::Mutex& aMutex,Moderator& aModerator) + : salhelper::Condition(aMutex), + m_aModerator(aModerator) + { + } + + protected: + bool applies() const override { + return m_aModerator.m_aResultType != ResultType::NORESULT; + } + + private: + Moderator& m_aModerator; + }; + + struct Result { + ResultType type; + Any result; + IOErrorCode ioErrorCode; + }; + + Result getResult(const sal_uInt32 milliSec); + + enum ReplyType { + NOREPLY, + EXIT, + REQUESTHANDLED + }; + + class ConditionRep + : public salhelper::Condition + { + public: + ConditionRep(osl::Mutex& aMutex,Moderator& aModerator) + : salhelper::Condition(aMutex), + m_aModerator(aModerator) + { + } + + protected: + bool applies() const override { + return m_aModerator.m_aReplyType != NOREPLY; + } + + private: + Moderator& m_aModerator; + }; + + void setReply(ReplyType); + + void handle( const Reference<XInteractionRequest >& Request ); + + void setStream(const Reference< XStream >& aStream); + void setInputStream(const Reference<XInputStream> &rxInputStream); + +protected: + virtual void SAL_CALL run() override; + virtual void SAL_CALL onTerminated() override; + +private: + osl::Mutex m_aMutex; + + friend class ConditionRes; + + ConditionRes m_aRes; + ResultType m_aResultType; + IOErrorCode m_nIOErrorCode; + Any m_aResult; + + friend class ConditionRep; + + ConditionRep m_aRep; + ReplyType m_aReplyType; + + Command m_aArg; + ::ucbhelper::Content m_aContent; +}; + +class ModeratorsActiveDataStreamer + : public ::cppu::WeakImplHelper<XActiveDataStreamer> +{ +public: + + explicit ModeratorsActiveDataStreamer(Moderator &theModerator); + + // XActiveDataStreamer + virtual void SAL_CALL + setStream( + const Reference< XStream >& aStream + ) override; + + virtual Reference<XStream> SAL_CALL getStream () override + { + std::scoped_lock aGuard(m_aMutex); + return m_xStream; + } + +private: + Moderator& m_aModerator; + + std::mutex m_aMutex; + Reference<XStream> m_xStream; +}; + +class ModeratorsActiveDataSink + : public ::cppu::WeakImplHelper<XActiveDataSink> +{ +public: + + explicit ModeratorsActiveDataSink(Moderator &theModerator); + + // XActiveDataSink. + virtual void SAL_CALL + setInputStream ( + const Reference<XInputStream> &rxInputStream + ) override; + + virtual Reference<XInputStream> SAL_CALL getInputStream() override + { + std::scoped_lock aGuard(m_aMutex); + return m_xStream; + } + +private: + Moderator& m_aModerator; + std::mutex m_aMutex; + Reference<XInputStream> m_xStream; +}; + +} + +ModeratorsActiveDataSink::ModeratorsActiveDataSink(Moderator &theModerator) + : m_aModerator(theModerator) +{ +} + +// XActiveDataSink. +void SAL_CALL +ModeratorsActiveDataSink::setInputStream ( + const Reference<XInputStream> &rxInputStream +) +{ + m_aModerator.setInputStream(rxInputStream); + std::scoped_lock aGuard(m_aMutex); + m_xStream = rxInputStream; +} + +ModeratorsActiveDataStreamer::ModeratorsActiveDataStreamer( + Moderator &theModerator +) + : m_aModerator(theModerator) +{ +} + +// XActiveDataStreamer. +void SAL_CALL +ModeratorsActiveDataStreamer::setStream ( + const Reference<XStream> &rxStream +) +{ + m_aModerator.setStream(rxStream); + std::scoped_lock aGuard(m_aMutex); + m_xStream = rxStream; +} + +namespace { + +class ModeratorsInteractionHandler + : public ::cppu::WeakImplHelper<XInteractionHandler> +{ +public: + + explicit ModeratorsInteractionHandler(Moderator &theModerator); + + virtual void SAL_CALL + handle( const Reference<XInteractionRequest >& Request ) override; + +private: + + Moderator& m_aModerator; +}; + +} + +ModeratorsInteractionHandler::ModeratorsInteractionHandler( + Moderator &aModerator) + : m_aModerator(aModerator) +{ +} + +void SAL_CALL +ModeratorsInteractionHandler::handle( + const Reference<XInteractionRequest >& Request +) +{ + // wakes up the mainthread + m_aModerator.handle(Request); +} + +Moderator::Moderator( + Reference < XContent > const & xContent, + Reference < XInteractionHandler > const & xInteract, + Command aArg +) + : m_aRes(m_aMutex,*this), + m_aResultType(ResultType::NORESULT), + m_nIOErrorCode(IOErrorCode_ABORT), + m_aRep(m_aMutex,*this), + m_aReplyType(NOREPLY), + m_aArg(std::move(aArg)), + m_aContent( + xContent, + new UcbTaskEnvironment( + xInteract.is() ? new ModeratorsInteractionHandler(*this) : nullptr, + nullptr), + comphelper::getProcessComponentContext()) +{ + // now exchange the whole data sink stuff + // with a thread safe version + + Reference<XInterface> *pxSink = nullptr; + + PostCommandArgument2 aPostArg; + OpenCommandArgument2 aOpenArg; + + int dec(2); + if(m_aArg.Argument >>= aPostArg) { + pxSink = &aPostArg.Sink; + dec = 0; + } + else if(m_aArg.Argument >>= aOpenArg) { + pxSink = &aOpenArg.Sink; + dec = 1; + } + + if(dec ==2) + throw ContentCreationException(); + + Reference < XActiveDataSink > xActiveSink(*pxSink,UNO_QUERY); + if(xActiveSink.is()) + pxSink->set(getXWeak(new ModeratorsActiveDataSink(*this))); + + Reference<XActiveDataStreamer> xStreamer( *pxSink, UNO_QUERY ); + if ( xStreamer.is() ) + pxSink->set(getXWeak(new ModeratorsActiveDataStreamer(*this))); + + if(dec == 0) + m_aArg.Argument <<= aPostArg; + else if(dec == 1) + m_aArg.Argument <<= aOpenArg; +} + +Moderator::Result Moderator::getResult(const sal_uInt32 milliSec) +{ + Result ret; + try { + salhelper::ConditionWaiter aWaiter(m_aRes,milliSec); + ret.type = m_aResultType; + ret.result = m_aResult; + ret.ioErrorCode = m_nIOErrorCode; + + // reset + m_aResultType = ResultType::NORESULT; + } + catch (const salhelper::ConditionWaiter::timedout&) + { + ret.type = ResultType::TIMEDOUT; + } + + return ret; +} + +void Moderator::setReply(ReplyType aReplyType ) +{ + salhelper::ConditionModifier aMod(m_aRep); + m_aReplyType = aReplyType; +} + +void Moderator::handle( const Reference<XInteractionRequest >& Request ) +{ + ReplyType aReplyType; + + do { + { + salhelper::ConditionModifier aMod(m_aRes); + m_aResultType = ResultType::INTERACTIONREQUEST; + m_aResult <<= Request; + } + + { + salhelper::ConditionWaiter aWait(m_aRep); + aReplyType = m_aReplyType; + + // reset + m_aReplyType = NOREPLY; + } + + if(aReplyType == EXIT) { + const Sequence<Reference<XInteractionContinuation> > aSeq( + Request->getContinuations()); + for(const auto& rContinuation : aSeq) { + Reference<XInteractionAbort> aRef(rContinuation,UNO_QUERY); + if(aRef.is()) { + aRef->select(); + } + } + + // resignal the exit condition + setReply(EXIT); + break; + } + } while(aReplyType != REQUESTHANDLED); +} + +void Moderator::setStream(const Reference< XStream >& aStream) +{ + { + salhelper::ConditionModifier aMod(m_aRes); + m_aResultType = ResultType::STREAM; + m_aResult <<= aStream; + } + ReplyType aReplyType; + { + salhelper::ConditionWaiter aWait(m_aRep); + aReplyType = m_aReplyType; + m_aReplyType = NOREPLY; + } + if(aReplyType == EXIT) + setReply(EXIT); +} + +void Moderator::setInputStream(const Reference<XInputStream> &rxInputStream) +{ + { + salhelper::ConditionModifier aMod(m_aRes); + m_aResultType = ResultType::INPUTSTREAM; + m_aResult <<= rxInputStream; + } + ReplyType aReplyType; + { + salhelper::ConditionWaiter aWait(m_aRep); + aReplyType = m_aReplyType; + m_aReplyType = NOREPLY; + } + if(aReplyType == EXIT) + setReply(EXIT); +} + +void SAL_CALL Moderator::run() +{ + osl_setThreadName("utl::Moderator"); + + ResultType aResultType; + Any aResult; + IOErrorCode nIOErrorCode = IOErrorCode_ABORT; + + try + { + aResult = m_aContent.executeCommand(m_aArg.Name,m_aArg.Argument); + aResultType = ResultType::RESULT; + } + catch (const CommandAbortedException&) + { + aResultType = ResultType::COMMANDABORTED; + } + catch (const CommandFailedException&) + { + aResultType = ResultType::COMMANDFAILED; + } + catch (const InteractiveIOException& r) + { + nIOErrorCode = r.Code; + aResultType = ResultType::INTERACTIVEIO; + } + catch (const UnsupportedDataSinkException &) + { + aResultType = ResultType::UNSUPPORTED; + } + catch (const Exception&) + { + aResultType = ResultType::GENERAL; + } + + { + salhelper::ConditionModifier aMod(m_aRes); + m_aResultType = aResultType; + m_aResult = aResult; + m_nIOErrorCode = nIOErrorCode; + } +} + +void SAL_CALL Moderator::onTerminated() +{ + { + salhelper::ConditionWaiter aWaiter(m_aRep); + } + delete this; +} + +/** + Function for opening UCB contents synchronously, + but with handled timeout; +*/ +static bool UCBOpenContentSync_( + const UcbLockBytesRef& xLockBytes, + const Reference < XContent >& xContent, + const Command& rArg, + const Reference < XInterface >& xSink, + const Reference < XInteractionHandler >& xInteract ); + +static bool UCBOpenContentSync( + const UcbLockBytesRef& xLockBytes, + Reference < XContent > const & xContent, + const Command& rArg, + const Reference < XInterface >& xSink, + Reference < XInteractionHandler > const & xInteract ) +{ + // http protocol must be handled in a special way: + // during the opening process the input stream may change + // only the last inputstream after notifying the document + // headers is valid + + Reference<XContentIdentifier> xContId( + xContent.is() ? xContent->getIdentifier() : nullptr ); + + OUString aScheme; + if(xContId.is()) + aScheme = xContId->getContentProviderScheme(); + + // now determine whether we use a timeout or not; + if( ! aScheme.equalsIgnoreAsciiCase("http") && + ! aScheme.equalsIgnoreAsciiCase("https") && + ! aScheme.equalsIgnoreAsciiCase("vnd.sun.star.webdav") && + ! aScheme.equalsIgnoreAsciiCase("vnd.sun.star.webdavs") && + ! aScheme.equalsIgnoreAsciiCase("ftp")) + return UCBOpenContentSync_( + xLockBytes,xContent,rArg,xSink,xInteract); + + if ( !aScheme.equalsIgnoreAsciiCase( "http" ) && + !aScheme.equalsIgnoreAsciiCase( "https" ) ) + xLockBytes->SetStreamValid(); + + Reference< XPropertiesChangeListener > xListener; + Reference< XPropertiesChangeNotifier > xProps(xContent,UNO_QUERY); + if(xProps.is()) { + xListener = + new UcbPropertiesChangeListener_Impl(xLockBytes); + xProps->addPropertiesChangeListener( + Sequence< OUString >(), + xListener); + } + + bool bException(false); + bool bAborted(false); + bool bResultAchieved(false); + + Moderator* pMod = nullptr; + try + { + pMod = new Moderator(xContent,xInteract,rArg); + pMod->create(); + //TODO: a protocol is missing how to join with the launched thread before exit(3), to + // ensure the thread is no longer relying on any infrastructure while that + // infrastructure is being shut down in atexit handlers + } + catch (const ContentCreationException&) + { + bResultAchieved = bException = true; + xLockBytes->SetError( ERRCODE_IO_GENERAL ); + } + + sal_uInt32 nTimeout(5000); // initially 5000 milliSec + while(!bResultAchieved) { + + // try to get the result for with timeout + Moderator::Result res = pMod->getResult(nTimeout); + + switch(res.type) { + case Moderator::ResultType::STREAM: + { + Reference<XStream> result; + if(res.result >>= result) { + Reference < XActiveDataStreamer > xStreamer( + xSink, UNO_QUERY + ); + + if(xStreamer.is()) + xStreamer->setStream(result); + } + pMod->setReply(Moderator::REQUESTHANDLED); + break; + } + case Moderator::ResultType::INPUTSTREAM: + { + Reference<XInputStream> result; + res.result >>= result; + Reference < XActiveDataSink > xActiveSink( + xSink, UNO_QUERY + ); + + if(xActiveSink.is()) + xActiveSink->setInputStream(result); + pMod->setReply(Moderator::REQUESTHANDLED); + break; + } + case Moderator::ResultType::TIMEDOUT: + { + Reference<XInteractionRetry> xRet; + if(xInteract.is()) { + INetURLObject aURL( + xContId.is() ? + xContId->getContentIdentifier() : + OUString() ); + InteractiveNetworkConnectException aExcep( + "server not responding after five seconds", {}, + InteractionClassification_ERROR, aURL.GetHost()); + Any request; + request <<= aExcep; + rtl::Reference<ucbhelper::InteractionRequest> xIR = + new ucbhelper::InteractionRequest(request); + rtl::Reference<ucbhelper::InteractionRetry> retryP = + new ucbhelper::InteractionRetry(xIR.get()); + rtl::Reference<ucbhelper::InteractionAbort> abortP = + new ucbhelper::InteractionAbort(xIR.get()); + Sequence<Reference<XInteractionContinuation> > aSeq { retryP, abortP }; + + xIR->setContinuations(aSeq); + xInteract->handle(xIR); + rtl::Reference< ucbhelper::InteractionContinuation > ref + = xIR->getSelection(); + if(ref.is()) { + Reference<XInterface> xInt(ref); + xRet.set(xInt,UNO_QUERY); + } + } + + if(!xRet.is()) { + bAborted = true; + xLockBytes->SetError(ERRCODE_ABORT); + } + + break; + } + case Moderator::ResultType::INTERACTIONREQUEST: + { + Reference<XInteractionRequest> Request; + res.result >>= Request; + xInteract->handle(Request); + pMod->setReply(Moderator::REQUESTHANDLED); + break; + } + case Moderator::ResultType::RESULT: + { + bResultAchieved = true; + break; + } + case Moderator::ResultType::COMMANDABORTED: + { + bAborted = true; + xLockBytes->SetError( ERRCODE_ABORT ); + break; + } + case Moderator::ResultType::COMMANDFAILED: + { + bAborted = true; + xLockBytes->SetError( ERRCODE_ABORT ); + break; + } + case Moderator::ResultType::INTERACTIVEIO: + { + bException = true; + if ( res.ioErrorCode == IOErrorCode_ACCESS_DENIED || + res.ioErrorCode == IOErrorCode_LOCKING_VIOLATION ) + xLockBytes->SetError( ERRCODE_IO_ACCESSDENIED ); + else if ( res.ioErrorCode == IOErrorCode_NOT_EXISTING ) + xLockBytes->SetError( ERRCODE_IO_NOTEXISTS ); + else if ( res.ioErrorCode == IOErrorCode_CANT_READ ) + xLockBytes->SetError( ERRCODE_IO_CANTREAD ); + else + xLockBytes->SetError( ERRCODE_IO_GENERAL ); + break; + } + case Moderator::ResultType::UNSUPPORTED: + { + bException = true; + xLockBytes->SetError( ERRCODE_IO_NOTSUPPORTED ); + break; + } + default: + { + bException = true; + xLockBytes->SetError( ERRCODE_IO_GENERAL ); + break; + } + } + + bResultAchieved |= bException; + bResultAchieved |= bAborted; + if(nTimeout == 5000) nTimeout *= 2; + } + + if(pMod) pMod->setReply(Moderator::EXIT); + + if ( bAborted || bException ) + { + Reference < XActiveDataSink > xActiveSink( xSink, UNO_QUERY ); + if ( xActiveSink.is() ) + xActiveSink->setInputStream( Reference < XInputStream >() ); + + Reference < XActiveDataStreamer > xStreamer( xSink, UNO_QUERY ); + if ( xStreamer.is() ) + xStreamer->setStream( Reference < XStream >() ); + } + + Reference < XActiveDataControl > xControl( xSink, UNO_QUERY ); + if ( xControl.is() ) + xControl->terminate(); + + if ( xProps.is() ) + xProps->removePropertiesChangeListener( + Sequence< OUString >(), + xListener ); + + return ( bAborted || bException ); +} + +/** + Function for opening UCB contents synchronously + */ +static bool UCBOpenContentSync_( + const UcbLockBytesRef& xLockBytes, + const Reference < XContent >& xContent, + const Command& rArg, + const Reference < XInterface >& xSink, + const Reference < XInteractionHandler >& xInteract ) +{ + ::ucbhelper::Content aContent( + xContent, new UcbTaskEnvironment( xInteract, nullptr ), + comphelper::getProcessComponentContext() ); + Reference < XContentIdentifier > xIdent = xContent->getIdentifier(); + OUString aScheme = xIdent->getContentProviderScheme(); + + // http protocol must be handled in a special way: during the opening process the input stream may change + // only the last inputstream after notifying the document headers is valid + if ( !aScheme.equalsIgnoreAsciiCase("http") ) + xLockBytes->SetStreamValid(); + + Reference< XPropertiesChangeListener > xListener = new UcbPropertiesChangeListener_Impl( xLockBytes ); + Reference< XPropertiesChangeNotifier > xProps ( xContent, UNO_QUERY ); + if ( xProps.is() ) + xProps->addPropertiesChangeListener( Sequence< OUString >(), xListener ); + + bool bException = false; + bool bAborted = false; + + try + { + aContent.executeCommand( rArg.Name, rArg.Argument ); + } + catch (const CommandAbortedException&) + { + bAborted = true; + xLockBytes->SetError( ERRCODE_ABORT ); + } + catch (const CommandFailedException&) + { + bAborted = true; + xLockBytes->SetError( ERRCODE_ABORT ); + } + catch (const InteractiveIOException& r) + { + bException = true; + if ( r.Code == IOErrorCode_ACCESS_DENIED || r.Code == IOErrorCode_LOCKING_VIOLATION ) + xLockBytes->SetError( ERRCODE_IO_ACCESSDENIED ); + else if ( r.Code == IOErrorCode_NOT_EXISTING ) + xLockBytes->SetError( ERRCODE_IO_NOTEXISTS ); + else if ( r.Code == IOErrorCode_CANT_READ ) + xLockBytes->SetError( ERRCODE_IO_CANTREAD ); + else + xLockBytes->SetError( ERRCODE_IO_GENERAL ); + } + catch (const UnsupportedDataSinkException&) + { + bException = true; + xLockBytes->SetError( ERRCODE_IO_NOTSUPPORTED ); + } + catch (const Exception&) + { + bException = true; + xLockBytes->SetError( ERRCODE_IO_GENERAL ); + } + + if ( bAborted || bException ) + { + Reference < XActiveDataSink > xActiveSink( xSink, UNO_QUERY ); + if ( xActiveSink.is() ) + xActiveSink->setInputStream( Reference < XInputStream >() ); + + Reference < XActiveDataStreamer > xStreamer( xSink, UNO_QUERY ); + if ( xStreamer.is() ) + xStreamer->setStream( Reference < XStream >() ); + } + + Reference < XActiveDataControl > xControl( xSink, UNO_QUERY ); + if ( xControl.is() ) + xControl->terminate(); + + if ( xProps.is() ) + xProps->removePropertiesChangeListener( Sequence< OUString >(), xListener ); + + return ( bAborted || bException ); +} + +UcbLockBytes::UcbLockBytes() + : m_nError( ERRCODE_NONE ) + , m_bTerminated (false) + , m_bDontClose( false ) + , m_bStreamValid (false) +{ + SetSynchronMode(); +} + +UcbLockBytes::~UcbLockBytes() +{ + if ( !m_bDontClose ) + { + if ( m_xInputStream.is() ) + { + try + { + m_xInputStream->closeInput(); + } + catch (const RuntimeException&) + { + } + catch (const IOException&) + { + } + } + } + + if ( m_xInputStream.is() || !m_xOutputStream.is() ) + return; + + try + { + m_xOutputStream->closeOutput(); + } + catch (const RuntimeException&) + { + } + catch (const IOException&) + { + } +} + +Reference < XInputStream > UcbLockBytes::getInputStream() +{ + std::unique_lock aGuard( m_aMutex ); + m_bDontClose = true; + return m_xInputStream; +} + +void UcbLockBytes::setStream( const Reference<XStream>& aStream ) +{ + std::unique_lock aGuard( m_aMutex ); + if ( aStream.is() ) + { + m_xOutputStream = aStream->getOutputStream(); + setInputStreamImpl( aGuard, aStream->getInputStream(), false ); + m_xSeekable.set( aStream, UNO_QUERY ); + } + else + { + m_xOutputStream.clear(); + setInputStreamImpl( aGuard, Reference < XInputStream >() ); + } +} + +bool UcbLockBytes::setInputStream( const Reference<XInputStream> &rxInputStream, bool bSetXSeekable ) +{ + std::unique_lock aGuard( m_aMutex ); + return setInputStreamImpl(aGuard, rxInputStream, bSetXSeekable); +} + +bool UcbLockBytes::setInputStreamImpl( std::unique_lock<std::mutex>& /*rGuard*/, const Reference<XInputStream> &rxInputStream, bool bSetXSeekable ) +{ + bool bRet = false; + + try + { + if ( !m_bDontClose && m_xInputStream.is() ) + m_xInputStream->closeInput(); + + m_xInputStream = rxInputStream; + + if( bSetXSeekable ) + { + m_xSeekable.set( rxInputStream, UNO_QUERY ); + if( !m_xSeekable.is() && rxInputStream.is() ) + { + Reference < XComponentContext > xContext = ::comphelper::getProcessComponentContext(); + rtl::Reference< utl::TempFileFastService > rxTempOut( new utl::TempFileFastService ); + + ::comphelper::OStorageHelper::CopyInputToOutput( rxInputStream, rxTempOut ); + m_xInputStream.set( rxTempOut ); + m_xSeekable.set( rxTempOut ); + } + } + + bRet = m_xInputStream.is(); + } + catch (const Exception&) + { + } + + if ( m_bStreamValid && m_xInputStream.is() ) + m_aInitialized.set(); + + return bRet; +} + +void UcbLockBytes::SetStreamValid() +{ + m_bStreamValid = true; + if ( m_xInputStream.is() ) + m_aInitialized.set(); +} + +void UcbLockBytes::terminate() +{ + m_bTerminated = true; + m_aInitialized.set(); + m_aTerminated.set(); + + if ( GetError() == ERRCODE_NONE && !m_xInputStream.is() ) + { + OSL_FAIL("No InputStream, but no error set!" ); + SetError( ERRCODE_IO_NOTEXISTS ); + } +} + +ErrCode UcbLockBytes::ReadAt(sal_uInt64 const nPos, + void *pBuffer, std::size_t nCount, std::size_t *pRead) const +{ + if ( IsSynchronMode() ) + { + UcbLockBytes* pThis = const_cast < UcbLockBytes* >( this ); + pThis->m_aInitialized.wait(); + } + + Reference <XInputStream> xStream = getInputStream(); + if ( !xStream.is() ) + { + if ( m_bTerminated ) + return ERRCODE_IO_CANTREAD; + else + return ERRCODE_IO_PENDING; + } + + if ( pRead ) + *pRead = 0; + + Reference <XSeekable> xSeekable = getSeekable(); + if ( !xSeekable.is() ) + return ERRCODE_IO_CANTREAD; + + try + { + xSeekable->seek( nPos ); + } + catch (const IOException&) + { + return ERRCODE_IO_CANTSEEK; + } + catch (const css::lang::IllegalArgumentException&) + { + return ERRCODE_IO_CANTSEEK; + } + + sal_Int32 nSize; + + if(nCount > 0x7FFFFFFF) + { + nCount = 0x7FFFFFFF; + } + try + { + if ( !m_bTerminated && !IsSynchronMode() ) + { + sal_uInt64 nLen = xSeekable->getLength(); + if ( nPos + nCount > nLen ) + return ERRCODE_IO_PENDING; + } + + comphelper::ByteReader* pByteReader = dynamic_cast< comphelper::ByteReader* >(xStream.get()); + if (pByteReader) + { + nSize = pByteReader->readSomeBytes( static_cast<sal_Int8*>(pBuffer), sal_Int32(nCount) ); + } + else + { + Sequence<sal_Int8> aData; + nSize = xStream->readBytes( aData, sal_Int32(nCount) ); + memcpy (pBuffer, aData.getConstArray(), nSize); + } + } + catch (const IOException&) + { + return ERRCODE_IO_CANTREAD; + } + + if (pRead) + *pRead = static_cast<std::size_t>(nSize); + + return ERRCODE_NONE; +} + +ErrCode UcbLockBytes::WriteAt(sal_uInt64 const nPos, const void *pBuffer, + std::size_t nCount, std::size_t *pWritten) +{ + if ( pWritten ) + *pWritten = 0; + + DBG_ASSERT( IsSynchronMode(), "Writing is only possible in SynchronMode!" ); + DBG_ASSERT( m_aInitialized.check(), "Writing bevor stream is ready!" ); + + Reference <XSeekable> xSeekable = getSeekable(); + Reference <XOutputStream> xOutputStream = getOutputStream(); + if ( !xOutputStream.is() || !xSeekable.is() ) + return ERRCODE_IO_CANTWRITE; + + try + { + xSeekable->seek( nPos ); + } + catch (const IOException&) + { + return ERRCODE_IO_CANTSEEK; + } + + sal_Int8 const * pData = static_cast<sal_Int8 const *>(pBuffer); + Sequence<sal_Int8> aData( pData, nCount ); + try + { + xOutputStream->writeBytes( aData ); + if ( pWritten ) + *pWritten = nCount; + } + catch (const Exception&) + { + return ERRCODE_IO_CANTWRITE; + } + + return ERRCODE_NONE; +} + +ErrCode UcbLockBytes::Flush() const +{ + Reference <XOutputStream > xOutputStream = getOutputStream(); + if ( !xOutputStream.is() ) + return ERRCODE_IO_CANTWRITE; + + try + { + xOutputStream->flush(); + } + catch (const Exception&) + { + return ERRCODE_IO_CANTWRITE; + } + + return ERRCODE_NONE; +} + +ErrCode UcbLockBytes::SetSize (sal_uInt64 const nNewSize) +{ + SvLockBytesStat aStat; + Stat( &aStat ); + std::size_t nSize = aStat.nSize; + + if ( nSize > nNewSize ) + { + Reference < XTruncate > xTrunc( getOutputStream(), UNO_QUERY ); + if ( xTrunc.is() ) + { + xTrunc->truncate(); + nSize = 0; + } + else { + SAL_INFO("unotools.ucbhelper", "Not truncable!"); + } + } + + if ( nSize < nNewSize ) + { + std::size_t nDiff = nNewSize-nSize, nCount=0; + std::unique_ptr<sal_uInt8[]> pBuffer(new sal_uInt8[ nDiff ]); + memset(pBuffer.get(), 0, nDiff); // initialize for enhanced security + WriteAt( nSize, pBuffer.get(), nDiff, &nCount ); + if ( nCount != nDiff ) + return ERRCODE_IO_CANTWRITE; + } + + return ERRCODE_NONE; +} + +ErrCode UcbLockBytes::Stat( SvLockBytesStat *pStat ) const +{ + if ( IsSynchronMode() ) + { + UcbLockBytes* pThis = const_cast < UcbLockBytes* >( this ); + pThis->m_aInitialized.wait(); + } + + if (!pStat) + return ERRCODE_IO_INVALIDPARAMETER; + + Reference <XInputStream> xStream = getInputStream(); + Reference <XSeekable> xSeekable = getSeekable(); + + if ( !xStream.is() ) + { + if ( m_bTerminated ) + return ERRCODE_IO_INVALIDACCESS; + else + return ERRCODE_IO_PENDING; + } + else if( !xSeekable.is() ) + return ERRCODE_IO_CANTTELL; + + try + { + pStat->nSize = sal_uLong(xSeekable->getLength()); + } + catch (const IOException&) + { + return ERRCODE_IO_CANTTELL; + } + + return ERRCODE_NONE; +} + +UcbLockBytesRef UcbLockBytes::CreateInputLockBytes( const Reference< XInputStream >& xInputStream ) +{ + if( !xInputStream.is() ) + return nullptr; + + UcbLockBytesRef xLockBytes = new UcbLockBytes; + xLockBytes->setDontClose(); + xLockBytes->setInputStream( xInputStream ); + xLockBytes->terminate(); + return xLockBytes; +} + +UcbLockBytesRef UcbLockBytes::CreateLockBytes( const Reference< XStream >& xStream ) +{ + if( !xStream.is() ) + return nullptr; + + UcbLockBytesRef xLockBytes = new UcbLockBytes; + xLockBytes->setDontClose(); + xLockBytes->setStream( xStream ); + xLockBytes->terminate(); + return xLockBytes; +} + +UcbLockBytesRef UcbLockBytes::CreateLockBytes( const Reference < XContent >& xContent, const Sequence < PropertyValue >& rProps, + StreamMode eOpenMode, const Reference < XInteractionHandler >& xInteractionHandler ) +{ + if( !xContent.is() ) + return nullptr; + + UcbLockBytesRef xLockBytes = new UcbLockBytes; + xLockBytes->SetSynchronMode(); + Reference< XActiveDataControl > xSink; + if ( eOpenMode & StreamMode::WRITE ) + xSink = new UcbStreamer_Impl(xLockBytes.get()); + else + xSink = new UcbDataSink_Impl(xLockBytes.get()); + + if ( rProps.hasElements() ) + { + Reference < XCommandProcessor > xProcessor( xContent, UNO_QUERY ); + Command aCommand; + aCommand.Name = "setPropertyValues"; + aCommand.Handle = -1; /* unknown */ + aCommand.Argument <<= rProps; + xProcessor->execute( aCommand, 0, Reference < XCommandEnvironment >() ); + } + + OpenCommandArgument2 aArgument; + aArgument.Sink = xSink; + aArgument.Mode = OpenMode::DOCUMENT; + + Command aCommand; + aCommand.Name = "open"; + aCommand.Argument <<= aArgument; + + bool bError = UCBOpenContentSync( xLockBytes, + xContent, + aCommand, + xSink, + xInteractionHandler ); + + if ( xLockBytes->GetError() == ERRCODE_NONE && ( bError || !xLockBytes->getInputStream().is() ) ) + { + OSL_FAIL("No InputStream, but no error set!" ); + xLockBytes->SetError( ERRCODE_IO_GENERAL ); + } + + return xLockBytes; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/ucbhelper/ucblockbytes.hxx b/unotools/source/ucbhelper/ucblockbytes.hxx new file mode 100644 index 0000000000..d866015b25 --- /dev/null +++ b/unotools/source/ucbhelper/ucblockbytes.hxx @@ -0,0 +1,144 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +#pragma once + +#include <com/sun/star/uno/Reference.hxx> + +#include <osl/conditn.hxx> +#include <tools/stream.hxx> +#include <comphelper/errcode.hxx> +#include <mutex> + +namespace com +{ + namespace sun + { + namespace star + { + namespace task + { + class XInteractionHandler; + } + namespace io + { + class XStream; + class XInputStream; + class XOutputStream; + class XSeekable; + } + namespace ucb + { + class XContent; + } + namespace beans + { + struct PropertyValue; + } + } + } +} + +namespace utl +{ +class UcbLockBytes; +typedef tools::SvRef<UcbLockBytes> UcbLockBytesRef; + +class UcbLockBytes : public SvLockBytes +{ + osl::Condition m_aInitialized; + osl::Condition m_aTerminated; + std::mutex m_aMutex; + + css::uno::Reference < css::io::XInputStream > m_xInputStream; + css::uno::Reference < css::io::XOutputStream > m_xOutputStream; + css::uno::Reference < css::io::XSeekable > m_xSeekable; + + ErrCode m_nError; + + bool m_bTerminated; + bool m_bDontClose; + bool m_bStreamValid; + + UcbLockBytes(); +protected: + virtual ~UcbLockBytes() override; + +public: + // properties: Referer, PostMimeType + static UcbLockBytesRef CreateLockBytes( const css::uno::Reference < css::ucb::XContent >& xContent, + const css::uno::Sequence < css::beans::PropertyValue >& rProps, + StreamMode eMode, + const css::uno::Reference < css::task::XInteractionHandler >& xInter ); + + static UcbLockBytesRef CreateInputLockBytes( const css::uno::Reference < css::io::XInputStream >& xContent ); + static UcbLockBytesRef CreateLockBytes( const css::uno::Reference < css::io::XStream >& xContent ); + + // SvLockBytes + virtual ErrCode ReadAt(sal_uInt64 nPos, void *pBuffer, std::size_t nCount, std::size_t *pRead) const override; + virtual ErrCode WriteAt(sal_uInt64, const void*, std::size_t, std::size_t *pWritten) override; + virtual ErrCode Flush() const override; + virtual ErrCode SetSize(sal_uInt64) override; + virtual ErrCode Stat ( SvLockBytesStat *pStat ) const override; + + void SetError( ErrCode nError ) + { m_nError = nError; } + + ErrCode const & GetError() const + { return m_nError; } + + // calling this method delegates the responsibility to call closeinput to the caller! + css::uno::Reference < css::io::XInputStream > getInputStream(); + + bool setInputStream( const css::uno::Reference < css::io::XInputStream > &rxInputStream, + bool bSetXSeekable = true ); + void setStream( const css::uno::Reference < css::io::XStream > &rxStream ); + void terminate(); + + css::uno::Reference < css::io::XInputStream > getInputStream() const + { + std::unique_lock aGuard( const_cast< UcbLockBytes* >(this)->m_aMutex ); + return m_xInputStream; + } + + css::uno::Reference < css::io::XOutputStream > getOutputStream() const + { + std::unique_lock aGuard( const_cast< UcbLockBytes* >(this)->m_aMutex ); + return m_xOutputStream; + } + + css::uno::Reference < css::io::XSeekable > getSeekable() const + { + std::unique_lock aGuard( const_cast< UcbLockBytes* >(this)->m_aMutex ); + return m_xSeekable; + } + + void setDontClose() + { m_bDontClose = true; } + + void SetStreamValid(); + +private: + bool setInputStreamImpl( std::unique_lock<std::mutex>& rGuard, + const css::uno::Reference < css::io::XInputStream > &rxInputStream, + bool bSetXSeekable = true ); +}; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/ucbhelper/ucbstreamhelper.cxx b/unotools/source/ucbhelper/ucbstreamhelper.cxx new file mode 100644 index 0000000000..e3437a864f --- /dev/null +++ b/unotools/source/ucbhelper/ucbstreamhelper.cxx @@ -0,0 +1,237 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <rtl/ustring.hxx> +#include <unotools/ucbstreamhelper.hxx> +#include <comphelper/processfactory.hxx> +#include <com/sun/star/task/InteractionHandler.hpp> +#include <com/sun/star/ucb/ContentCreationException.hpp> +#include <com/sun/star/ucb/CommandAbortedException.hpp> +#include <com/sun/star/ucb/InsertCommandArgument.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> + +#include <comphelper/simplefileaccessinteraction.hxx> +#include <ucbhelper/content.hxx> +#include <unotools/streamwrap.hxx> +#include "ucblockbytes.hxx" + +namespace com::sun::star::ucb { class XCommandEnvironment; } + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::io; +using namespace ::com::sun::star::ucb; +using namespace ::com::sun::star::task; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::beans; + +namespace utl +{ + +static std::unique_ptr<SvStream> lcl_CreateStream( const OUString& rFileName, StreamMode eOpenMode, + const Reference < XInteractionHandler >& xInteractionHandler, + bool bEnsureFileExists ) +{ + std::unique_ptr<SvStream> pStream; + UcbLockBytesRef xLockBytes; + if ( eOpenMode & StreamMode::WRITE ) + { + bool bTruncate = bool( eOpenMode & StreamMode::TRUNC ); + if ( bTruncate ) + { + try + { + // truncate is implemented with deleting the original file + ::ucbhelper::Content aCnt( + rFileName, Reference < XCommandEnvironment >(), + comphelper::getProcessComponentContext() ); + aCnt.executeCommand( "delete", css::uno::Any( true ) ); + } + + catch ( const CommandAbortedException& ) + { + // couldn't truncate/delete + } + catch ( const ContentCreationException& ) + { + } + catch ( const Exception& ) + { + } + } + + if ( bEnsureFileExists || bTruncate ) + { + try + { + // make sure that the desired file exists before trying to open + SvMemoryStream aStream(0,0); + rtl::Reference<::utl::OInputStreamWrapper> xInput = new ::utl::OInputStreamWrapper( aStream ); + + ::ucbhelper::Content aContent( + rFileName, Reference < XCommandEnvironment >(), + comphelper::getProcessComponentContext() ); + InsertCommandArgument aInsertArg; + aInsertArg.Data = xInput; + + aInsertArg.ReplaceExisting = false; + Any aCmdArg; + aCmdArg <<= aInsertArg; + aContent.executeCommand( "insert", aCmdArg ); + } + + // it is NOT an error when the stream already exists and no truncation was desired + catch ( const CommandAbortedException& ) + { + // currently never an error is detected ! + } + catch ( const ContentCreationException& ) + { + } + catch ( const Exception& ) + { + } + } + } + + try + { + // create LockBytes using UCB + ::ucbhelper::Content aContent( + rFileName, Reference < XCommandEnvironment >(), + comphelper::getProcessComponentContext() ); + xLockBytes = UcbLockBytes::CreateLockBytes( aContent.get(), Sequence < PropertyValue >(), + eOpenMode, xInteractionHandler ); + if ( xLockBytes.is() ) + { + pStream.reset( new SvStream( xLockBytes.get() ) ); + pStream->SetBufferSize( 4096 ); + pStream->SetError( xLockBytes->GetError() ); + } + } + catch ( const CommandAbortedException& ) + { + } + catch ( const ContentCreationException& ) + { + } + catch ( const Exception& ) + { + } + + return pStream; +} + +std::unique_ptr<SvStream> UcbStreamHelper::CreateStream(const OUString& rFileName, StreamMode eOpenMode, css::uno::Reference<css::awt::XWindow> xParentWin) +{ + // related tdf#99312 + // create a specialized interaction handler to manages Web certificates and Web credentials when needed + Reference< XInteractionHandler > xIH( + css::task::InteractionHandler::createWithParent(comphelper::getProcessComponentContext(), xParentWin)); + Reference<XInteractionHandler> xIHScoped(new comphelper::SimpleFileAccessInteraction(xIH)); + + return lcl_CreateStream( rFileName, eOpenMode, xIHScoped, true /* bEnsureFileExists */ ); +} + +std::unique_ptr<SvStream> UcbStreamHelper::CreateStream(const OUString& rFileName, StreamMode eOpenMode, + bool bFileExists, css::uno::Reference<css::awt::XWindow> xParentWin) +{ + // related tdf#99312 + // create a specialized interaction handler to manages Web certificates and Web credentials when needed + Reference< XInteractionHandler > xIH( + css::task::InteractionHandler::createWithParent(comphelper::getProcessComponentContext(), xParentWin)); + Reference<XInteractionHandler> xIHScoped(new comphelper::SimpleFileAccessInteraction(xIH)); + return lcl_CreateStream( rFileName, eOpenMode, xIHScoped,!bFileExists ); +} + + +std::unique_ptr<SvStream> UcbStreamHelper::CreateStream( const Reference < XInputStream >& xStream ) +{ + std::unique_ptr<SvStream> pStream; + UcbLockBytesRef xLockBytes = UcbLockBytes::CreateInputLockBytes( xStream ); + if ( xLockBytes.is() ) + { + pStream.reset( new SvStream( xLockBytes.get() ) ); + pStream->SetBufferSize( 4096 ); + pStream->SetError( xLockBytes->GetError() ); + } + + return pStream; +} + +std::unique_ptr<SvStream> UcbStreamHelper::CreateStream( const Reference < XStream >& xStream ) +{ + std::unique_ptr<SvStream> pStream; + if ( xStream->getOutputStream().is() ) + { + UcbLockBytesRef xLockBytes = UcbLockBytes::CreateLockBytes( xStream ); + if ( xLockBytes.is() ) + { + pStream.reset( new SvStream( xLockBytes.get() ) ); + pStream->SetBufferSize( 4096 ); + pStream->SetError( xLockBytes->GetError() ); + } + } + else + return CreateStream( xStream->getInputStream() ); + + return pStream; +} + +std::unique_ptr<SvStream> UcbStreamHelper::CreateStream( const Reference < XInputStream >& xStream, bool bCloseStream ) +{ + std::unique_ptr<SvStream> pStream; + UcbLockBytesRef xLockBytes = UcbLockBytes::CreateInputLockBytes( xStream ); + if ( xLockBytes.is() ) + { + if ( !bCloseStream ) + xLockBytes->setDontClose(); + + pStream.reset( new SvStream( xLockBytes.get() ) ); + pStream->SetBufferSize( 4096 ); + pStream->SetError( xLockBytes->GetError() ); + } + + return pStream; +}; + +std::unique_ptr<SvStream> UcbStreamHelper::CreateStream( const Reference < XStream >& xStream, bool bCloseStream ) +{ + std::unique_ptr<SvStream> pStream; + if ( xStream->getOutputStream().is() ) + { + UcbLockBytesRef xLockBytes = UcbLockBytes::CreateLockBytes( xStream ); + if ( xLockBytes.is() ) + { + if ( !bCloseStream ) + xLockBytes->setDontClose(); + + pStream.reset( new SvStream( xLockBytes.get() ) ); + pStream->SetBufferSize( 4096 ); + pStream->SetError( xLockBytes->GetError() ); + } + } + else + return CreateStream( xStream->getInputStream(), bCloseStream ); + + return pStream; +}; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/ucbhelper/xtempfile.cxx b/unotools/source/ucbhelper/xtempfile.cxx new file mode 100644 index 0000000000..5e0cf5cbdb --- /dev/null +++ b/unotools/source/ucbhelper/xtempfile.cxx @@ -0,0 +1,435 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include "XTempFile.hxx" +#include <com/sun/star/io/BufferSizeExceededException.hpp> +#include <com/sun/star/io/NotConnectedException.hpp> +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <cppuhelper/typeprovider.hxx> +#include <o3tl/safeint.hxx> +#include <unotools/tempfile.hxx> +#include <cppuhelper/propshlp.hxx> +#include <cppuhelper/supportsservice.hxx> + +OTempFileService::OTempFileService(css::uno::Reference< css::uno::XComponentContext > const &) +: mpStream( nullptr ) +, mbRemoveFile( true ) +, mbInClosed( false ) +, mbOutClosed( false ) +{ + mpTempFile.emplace(); + mpTempFile->EnableKillingFile(); +} + +OTempFileService::~OTempFileService () +{ +} + +// XTypeProvider + +css::uno::Sequence< css::uno::Type > SAL_CALL OTempFileService::getTypes( ) +{ + static ::cppu::OTypeCollection ourTypeCollection( + cppu::UnoType<css::beans::XPropertySet>::get() + ,OTempFileBase::getTypes() ); + + return ourTypeCollection.getTypes(); +}; + +// XTempFile + +sal_Bool SAL_CALL OTempFileService::getRemoveFile() +{ + std::unique_lock aGuard( maMutex ); + + if ( !mpTempFile ) + { + // the stream is already disconnected + throw css::uno::RuntimeException("Not connected to a file."); + } + + return mbRemoveFile; +}; +void SAL_CALL OTempFileService::setRemoveFile( sal_Bool _removefile ) +{ + std::unique_lock aGuard( maMutex ); + + if ( !mpTempFile ) + { + // the stream is already disconnected + throw css::uno::RuntimeException("Not connected to a file."); + } + + mbRemoveFile = _removefile; + mpTempFile->EnableKillingFile( mbRemoveFile ); +}; +OUString SAL_CALL OTempFileService::getUri() +{ + std::unique_lock aGuard( maMutex ); + + if ( !mpTempFile ) + { + throw css::uno::RuntimeException("Not connected to a file."); + } + + return mpTempFile->GetURL(); + +}; +OUString SAL_CALL OTempFileService::getResourceName() +{ + std::unique_lock aGuard( maMutex ); + + if ( !mpTempFile ) + { + throw css::uno::RuntimeException("Not connected to a file."); + } + + return mpTempFile->GetFileName(); +}; + +// XInputStream + +sal_Int32 SAL_CALL OTempFileService::readBytes( css::uno::Sequence< sal_Int8 >& aData, sal_Int32 nBytesToRead ) +{ + std::unique_lock aGuard( maMutex ); + if ( mbInClosed ) + throw css::io::NotConnectedException ( OUString(), getXWeak() ); + + checkConnected(); + if (nBytesToRead < 0) + throw css::io::BufferSizeExceededException( OUString(), getXWeak()); + + if (aData.getLength() < nBytesToRead) + aData.realloc(nBytesToRead); + + sal_uInt32 nRead = mpStream->ReadBytes(static_cast<void*>(aData.getArray()), nBytesToRead); + checkError(); + + if (nRead < o3tl::make_unsigned(aData.getLength())) + aData.realloc( nRead ); + + return nRead; +} +sal_Int32 SAL_CALL OTempFileService::readSomeBytes( css::uno::Sequence< sal_Int8 >& aData, sal_Int32 nMaxBytesToRead ) +{ + { + std::unique_lock aGuard( maMutex ); + if ( mbInClosed ) + throw css::io::NotConnectedException ( OUString(), getXWeak() ); + + checkConnected(); + checkError(); + + if (nMaxBytesToRead < 0) + throw css::io::BufferSizeExceededException( OUString(), getXWeak() ); + + if (mpStream->eof()) + { + aData.realloc(0); + return 0; + } + } + return readBytes(aData, nMaxBytesToRead); +} +void SAL_CALL OTempFileService::skipBytes( sal_Int32 nBytesToSkip ) +{ + std::unique_lock aGuard( maMutex ); + if ( mbInClosed ) + throw css::io::NotConnectedException ( OUString(), getXWeak() ); + + checkConnected(); + checkError(); + mpStream->SeekRel(nBytesToSkip); + checkError(); +} +sal_Int32 SAL_CALL OTempFileService::available( ) +{ + std::unique_lock aGuard( maMutex ); + if ( mbInClosed ) + throw css::io::NotConnectedException ( OUString(), getXWeak() ); + + checkConnected(); + + sal_Int64 nAvailable = mpStream->remainingSize(); + checkError(); + + return std::min<sal_Int64>(SAL_MAX_INT32, nAvailable); +} +void SAL_CALL OTempFileService::closeInput( ) +{ + std::unique_lock aGuard( maMutex ); + if ( mbInClosed ) + throw css::io::NotConnectedException ( OUString(), getXWeak() ); + + mbInClosed = true; + + if ( mbOutClosed ) + { + // stream will be deleted by TempFile implementation + mpStream = nullptr; + mpTempFile.reset(); + } +} + +// XOutputStream + +void SAL_CALL OTempFileService::writeBytes( const css::uno::Sequence< sal_Int8 >& aData ) +{ + std::unique_lock aGuard( maMutex ); + if ( mbOutClosed ) + throw css::io::NotConnectedException ( OUString(), getXWeak() ); + + checkConnected(); + sal_uInt32 nWritten = mpStream->WriteBytes(aData.getConstArray(), aData.getLength()); + checkError(); + if ( nWritten != static_cast<sal_uInt32>(aData.getLength())) + throw css::io::BufferSizeExceededException( OUString(), getXWeak() ); +} +void SAL_CALL OTempFileService::flush( ) +{ + std::unique_lock aGuard( maMutex ); + if ( mbOutClosed ) + throw css::io::NotConnectedException ( OUString(), getXWeak() ); + + checkConnected(); + mpStream->Flush(); + checkError(); +} +void SAL_CALL OTempFileService::closeOutput( ) +{ + std::unique_lock aGuard( maMutex ); + if ( mbOutClosed ) + throw css::io::NotConnectedException ( OUString(), getXWeak() ); + + mbOutClosed = true; + if (mpStream) + { + // so that if you then open the InputStream, you can read the content + mpStream->FlushBuffer(); + mpStream->Seek(0); + } + + if ( mbInClosed ) + { + // stream will be deleted by TempFile implementation + mpStream = nullptr; + mpTempFile.reset(); + } +} + +void OTempFileService::checkError () const +{ + if (!mpStream || mpStream->SvStream::GetError () != ERRCODE_NONE ) + throw css::io::NotConnectedException ( OUString(), const_cast < OTempFileService * > (this)->getXWeak() ); +} +void OTempFileService::checkConnected () +{ + if (!mpStream && mpTempFile) + { + // Ideally we should open this SHARE_DENYALL, but the JunitTest_unotools_complex test wants to open + // this file directly and read from it. + mpStream = mpTempFile->GetStream(StreamMode::READ | StreamMode::WRITE + | StreamMode::SHARE_DENYWRITE); + } + + if (!mpStream) + throw css::io::NotConnectedException ( OUString(), getXWeak() ); +} + +// XSeekable + +void SAL_CALL OTempFileService::seek( sal_Int64 nLocation ) +{ + std::unique_lock aGuard( maMutex ); + checkConnected(); + checkError(); + sal_Int64 nEndPos = mpStream->TellEnd(); + if ( nLocation < 0 || nLocation > nEndPos ) + throw css::lang::IllegalArgumentException(); + + mpStream->Seek(static_cast<sal_uInt32>(nLocation) ); + checkError(); +} +sal_Int64 SAL_CALL OTempFileService::getPosition( ) +{ + std::unique_lock aGuard( maMutex ); + checkConnected(); + + sal_uInt64 nPos = mpStream->Tell(); + checkError(); + return static_cast<sal_Int64>(nPos); +} +sal_Int64 SAL_CALL OTempFileService::getLength( ) +{ + std::unique_lock aGuard( maMutex ); + checkConnected(); + + checkError(); + + sal_Int64 nEndPos = mpStream->TellEnd(); + + return nEndPos; +} + +// XStream + +css::uno::Reference< css::io::XInputStream > SAL_CALL OTempFileService::getInputStream() +{ + return this; +} + +css::uno::Reference< css::io::XOutputStream > SAL_CALL OTempFileService::getOutputStream() +{ + return this; +} + +// XTruncate + +void SAL_CALL OTempFileService::truncate() +{ + std::unique_lock aGuard( maMutex ); + checkConnected(); + // SetStreamSize() call does not change the position + mpStream->Seek( 0 ); + mpStream->SetStreamSize( 0 ); + checkError(); +} + +#define PROPERTY_HANDLE_URI 1 +#define PROPERTY_HANDLE_REMOVE_FILE 2 +#define PROPERTY_HANDLE_RESOURCE_NAME 3 + +// XPropertySet +::css::uno::Reference< ::css::beans::XPropertySetInfo > OTempFileService::getPropertySetInfo() +{ + // Create a table that map names to index values. + // attention: properties need to be sorted by name! + static cppu::OPropertyArrayHelper ourPropertyInfo( + { + css::beans::Property( "Uri", PROPERTY_HANDLE_URI, cppu::UnoType<OUString>::get(), + css::beans::PropertyAttribute::READONLY ), + css::beans::Property( "RemoveFile", PROPERTY_HANDLE_REMOVE_FILE, cppu::UnoType<bool>::get(), + 0 ), + css::beans::Property( "ResourceName", PROPERTY_HANDLE_RESOURCE_NAME, cppu::UnoType<OUString>::get(), + css::beans::PropertyAttribute::READONLY ) + }, + true ); + static css::uno::Reference< css::beans::XPropertySetInfo > xInfo( + ::cppu::OPropertySetHelper::createPropertySetInfo( ourPropertyInfo ) ); + return xInfo; +} +void OTempFileService::setPropertyValue( const ::rtl::OUString& aPropertyName, const ::css::uno::Any& aValue ) +{ + if ( aPropertyName == "RemoveFile" ) + setRemoveFile( aValue.get<bool>() ); + else + { + assert(false); + throw css::beans::UnknownPropertyException(aPropertyName); + } +} +::css::uno::Any OTempFileService::getPropertyValue( const ::rtl::OUString& aPropertyName ) +{ + if ( aPropertyName == "RemoveFile" ) + return css::uno::Any(getRemoveFile()); + else if ( aPropertyName == "ResourceName" ) + return css::uno::Any(getResourceName()); + else if ( aPropertyName == "Uri" ) + return css::uno::Any(getUri()); + else + { + assert(false); + throw css::beans::UnknownPropertyException(aPropertyName); + } +} +void OTempFileService::addPropertyChangeListener( const ::rtl::OUString& /*aPropertyName*/, const ::css::uno::Reference< ::css::beans::XPropertyChangeListener >& /*xListener*/ ) +{ + assert(false); +} +void OTempFileService::removePropertyChangeListener( const ::rtl::OUString& /*aPropertyName*/, const ::css::uno::Reference< ::css::beans::XPropertyChangeListener >& /*xListener*/ ) +{ + assert(false); +} +void OTempFileService::addVetoableChangeListener( const ::rtl::OUString& /*aPropertyName*/, const ::css::uno::Reference< ::css::beans::XVetoableChangeListener >& /*xListener*/ ) +{ + assert(false); +} +void OTempFileService::removeVetoableChangeListener( const ::rtl::OUString& /*aPropertyName*/, const ::css::uno::Reference< ::css::beans::XVetoableChangeListener >& /*xListener*/ ) +{ + assert(false); +} +// XFastPropertySet +void OTempFileService::setFastPropertyValue( ::sal_Int32 nHandle, const ::css::uno::Any& aValue ) +{ + switch (nHandle) + { + case PROPERTY_HANDLE_REMOVE_FILE: setRemoveFile( aValue.get<bool>() ); return; + } + assert(false); + throw css::beans::UnknownPropertyException(OUString::number(nHandle)); +} +::css::uno::Any OTempFileService::getFastPropertyValue( ::sal_Int32 nHandle ) +{ + switch (nHandle) + { + case PROPERTY_HANDLE_REMOVE_FILE: return css::uno::Any(getRemoveFile()); + case PROPERTY_HANDLE_RESOURCE_NAME: return css::uno::Any(getResourceName()); + case PROPERTY_HANDLE_URI: return css::uno::Any(getUri()); + } + assert(false); + throw css::beans::UnknownPropertyException(OUString::number(nHandle)); +} +// XPropertyAccess +::css::uno::Sequence< ::css::beans::PropertyValue > OTempFileService::getPropertyValues() +{ + return { + css::beans::PropertyValue("Uri", PROPERTY_HANDLE_URI, css::uno::Any(getUri()), css::beans::PropertyState_DEFAULT_VALUE), + css::beans::PropertyValue("RemoveFile", PROPERTY_HANDLE_REMOVE_FILE, css::uno::Any(getRemoveFile()), css::beans::PropertyState_DEFAULT_VALUE), + css::beans::PropertyValue("ResourceName", PROPERTY_HANDLE_RESOURCE_NAME, css::uno::Any(getResourceName()), css::beans::PropertyState_DEFAULT_VALUE) + }; +} +void OTempFileService::setPropertyValues( const ::css::uno::Sequence< ::css::beans::PropertyValue >& aProps ) +{ + for ( auto const & rPropVal : aProps ) + setPropertyValue( rPropVal.Name, rPropVal.Value ); +} + +// XServiceInfo +sal_Bool OTempFileService::supportsService(const OUString& sServiceName) +{ + return cppu::supportsService(this, sServiceName); +} +OUString OTempFileService::getImplementationName() +{ + return "com.sun.star.io.comp.TempFile"; +} +css::uno::Sequence< OUString > OTempFileService::getSupportedServiceNames() +{ + return { "com.sun.star.io.TempFile" }; +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +unotools_OTempFileService_get_implementation( + css::uno::XComponentContext* context , css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new OTempFileService(context)); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/util/utl.component b/unotools/util/utl.component new file mode 100644 index 0000000000..cd31d1c3e3 --- /dev/null +++ b/unotools/util/utl.component @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + --> + +<component loader="com.sun.star.loader.SharedLibrary" environment="@CPPU_ENV@" + xmlns="http://openoffice.org/2010/uno-components"> + <implementation name="com.sun.star.io.comp.TempFile" + constructor="unotools_OTempFileService_get_implementation"> + <service name="com.sun.star.io.TempFile"/> + </implementation> + <implementation name="com.sun.star.comp.unotools.misc.ServiceDocumenter" + constructor="unotools_ServiceDocument_get_implementation" + single-instance="true"> + <singleton name="com.sun.star.util.theServiceDocumenter"/> + </implementation> +</component> |