summaryrefslogtreecommitdiffstats
path: root/svl/source
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 05:54:39 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 05:54:39 +0000
commit267c6f2ac71f92999e969232431ba04678e7437e (patch)
tree358c9467650e1d0a1d7227a21dac2e3d08b622b2 /svl/source
parentInitial commit. (diff)
downloadlibreoffice-267c6f2ac71f92999e969232431ba04678e7437e.tar.xz
libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.zip
Adding upstream version 4:24.2.0.upstream/4%24.2.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'svl/source')
-rw-r--r--svl/source/config/asiancfg.cxx172
-rw-r--r--svl/source/config/cjkoptions.cxx170
-rw-r--r--svl/source/config/ctloptions.cxx446
-rw-r--r--svl/source/config/itemholder2.cxx117
-rw-r--r--svl/source/config/itemholder2.hxx58
-rw-r--r--svl/source/config/languageoptions.cxx137
-rw-r--r--svl/source/crypto/cryptosign.cxx2385
-rw-r--r--svl/source/filepicker/pickerhistory.cxx72
-rw-r--r--svl/source/fsstor/fsfactory.cxx155
-rw-r--r--svl/source/fsstor/fsstorage.component27
-rw-r--r--svl/source/fsstor/fsstorage.cxx1112
-rw-r--r--svl/source/fsstor/fsstorage.hxx187
-rw-r--r--svl/source/fsstor/oinputstreamcontainer.cxx260
-rw-r--r--svl/source/fsstor/oinputstreamcontainer.hxx83
-rw-r--r--svl/source/fsstor/ostreamcontainer.cxx451
-rw-r--r--svl/source/fsstor/ostreamcontainer.hxx109
-rw-r--r--svl/source/inc/fsfactory.hxx54
-rw-r--r--svl/source/inc/items_helper.hxx60
-rw-r--r--svl/source/inc/poolio.hxx73
-rw-r--r--svl/source/inc/stringio.hxx47
-rw-r--r--svl/source/items/IndexedStyleSheets.cxx244
-rw-r--r--svl/source/items/cenumitm.cxx148
-rw-r--r--svl/source/items/cintitem.cxx209
-rw-r--r--svl/source/items/custritm.cxx72
-rw-r--r--svl/source/items/flagitem.cxx67
-rw-r--r--svl/source/items/globalnameitem.cxx90
-rw-r--r--svl/source/items/grabbagitem.cxx70
-rw-r--r--svl/source/items/ilstitem.cxx88
-rw-r--r--svl/source/items/imageitm.cxx84
-rw-r--r--svl/source/items/int64item.cxx58
-rw-r--r--svl/source/items/intitem.cxx183
-rw-r--r--svl/source/items/itemiter.cxx84
-rw-r--r--svl/source/items/itempool.cxx1253
-rw-r--r--svl/source/items/itemprop.cxx356
-rw-r--r--svl/source/items/itemset.cxx2068
-rw-r--r--svl/source/items/lckbitem.cxx102
-rw-r--r--svl/source/items/legacyitem.cxx69
-rw-r--r--svl/source/items/macitem.cxx243
-rw-r--r--svl/source/items/poolitem.cxx703
-rw-r--r--svl/source/items/ptitem.cxx136
-rw-r--r--svl/source/items/rectitem.cxx132
-rw-r--r--svl/source/items/rngitem.cxx59
-rw-r--r--svl/source/items/sitem.cxx73
-rw-r--r--svl/source/items/slstitm.cxx166
-rw-r--r--svl/source/items/srchitem.cxx662
-rw-r--r--svl/source/items/stringio.cxx34
-rw-r--r--svl/source/items/stritem.cxx42
-rw-r--r--svl/source/items/style.cxx905
-rw-r--r--svl/source/items/stylepool.cxx468
-rw-r--r--svl/source/items/visitem.cxx71
-rw-r--r--svl/source/items/voiditem.cxx70
-rw-r--r--svl/source/items/whiter.cxx93
-rw-r--r--svl/source/misc/PasswordHelper.cxx180
-rw-r--r--svl/source/misc/adrparse.cxx556
-rw-r--r--svl/source/misc/documentlockfile.cxx226
-rw-r--r--svl/source/misc/filenotation.cxx122
-rw-r--r--svl/source/misc/fstathelper.cxx94
-rw-r--r--svl/source/misc/getstringresource.cxx31
-rw-r--r--svl/source/misc/gridprinter.cxx133
-rw-r--r--svl/source/misc/inethist.cxx371
-rw-r--r--svl/source/misc/inettype.cxx443
-rw-r--r--svl/source/misc/lngmisc.cxx132
-rw-r--r--svl/source/misc/lockfilecommon.cxx243
-rw-r--r--svl/source/misc/msodocumentlockfile.cxx269
-rw-r--r--svl/source/misc/ownlist.cxx70
-rw-r--r--svl/source/misc/sharecontrolfile.cxx337
-rw-r--r--svl/source/misc/sharedstring.cxx70
-rw-r--r--svl/source/misc/sharedstringpool.cxx184
-rw-r--r--svl/source/misc/strmadpt.cxx653
-rw-r--r--svl/source/misc/urihelper.cxx884
-rw-r--r--svl/source/notify/SfxBroadcaster.cxx152
-rw-r--r--svl/source/notify/broadcast.cxx244
-rw-r--r--svl/source/notify/listener.cxx89
-rw-r--r--svl/source/notify/lstner.cxx163
-rw-r--r--svl/source/numbers/currencytable.cxx37
-rw-r--r--svl/source/numbers/numfmuno.cxx987
-rw-r--r--svl/source/numbers/numfmuno.hxx222
-rw-r--r--svl/source/numbers/numuno.cxx89
-rw-r--r--svl/source/numbers/supservs.cxx161
-rw-r--r--svl/source/numbers/supservs.hxx80
-rw-r--r--svl/source/numbers/zforfind.cxx4324
-rw-r--r--svl/source/numbers/zforfind.hxx443
-rw-r--r--svl/source/numbers/zforlist.cxx4979
-rw-r--r--svl/source/numbers/zformat.cxx6083
-rw-r--r--svl/source/numbers/zforscan.cxx3332
-rw-r--r--svl/source/numbers/zforscan.hxx304
-rw-r--r--svl/source/passwordcontainer/passwordcontainer.component27
-rw-r--r--svl/source/passwordcontainer/passwordcontainer.cxx1442
-rw-r--r--svl/source/passwordcontainer/passwordcontainer.hxx412
-rw-r--r--svl/source/passwordcontainer/syscreds.cxx269
-rw-r--r--svl/source/passwordcontainer/syscreds.hxx81
-rw-r--r--svl/source/svdde/ddecli.cxx391
-rw-r--r--svl/source/svdde/ddedata.cxx167
-rw-r--r--svl/source/svdde/ddeimp.hxx117
-rw-r--r--svl/source/svdde/ddestrg.cxx47
-rw-r--r--svl/source/svdde/ddesvr.cxx820
-rw-r--r--svl/source/svsql/converter.cxx31
-rw-r--r--svl/source/undo/undo.cxx1391
-rw-r--r--svl/source/uno/pathservice.cxx88
99 files changed, 47247 insertions, 0 deletions
diff --git a/svl/source/config/asiancfg.cxx b/svl/source/config/asiancfg.cxx
new file mode 100644
index 0000000000..a8f4e08e51
--- /dev/null
+++ b/svl/source/config/asiancfg.cxx
@@ -0,0 +1,172 @@
+/* -*- 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 <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/container/ElementExistException.hpp>
+#include <com/sun/star/container/NoSuchElementException.hpp>
+#include <com/sun/star/container/XNameAccess.hpp>
+#include <com/sun/star/container/XNameContainer.hpp>
+#include <com/sun/star/lang/Locale.hpp>
+#include <com/sun/star/lang/XSingleServiceFactory.hpp>
+#include <com/sun/star/uno/Any.hxx>
+#include <com/sun/star/uno/Reference.hxx>
+#include <com/sun/star/uno/Sequence.hxx>
+#include <comphelper/configuration.hxx>
+#include <comphelper/processfactory.hxx>
+#include <officecfg/Office/Common.hxx>
+#include <rtl/ustring.hxx>
+#include <sal/log.hxx>
+#include <sal/types.h>
+#include <i18nlangtag/languagetag.hxx>
+#include <svl/asiancfg.hxx>
+
+namespace {
+
+OUString toString(css::lang::Locale const & locale) {
+ SAL_WARN_IF( locale.Language.indexOf('-') != -1, "svl",
+ "Locale language \"" << locale.Language << "\" contains \"-\"");
+ SAL_WARN_IF( locale.Country.indexOf('-') != -1, "svl",
+ "Locale country \"" << locale.Country << "\" contains \"-\"");
+ return LanguageTag::convertToBcp47( locale, false);
+}
+
+}
+
+struct SvxAsianConfig::Impl {
+ Impl():
+ batch(comphelper::ConfigurationChanges::create())
+ {}
+
+ Impl(const Impl&) = delete;
+ Impl& operator=(const Impl&) = delete;
+
+ std::shared_ptr< comphelper::ConfigurationChanges > batch;
+};
+
+SvxAsianConfig::SvxAsianConfig(): impl_(new Impl) {}
+
+SvxAsianConfig::~SvxAsianConfig() {}
+
+void SvxAsianConfig::Commit() {
+ impl_->batch->commit();
+}
+
+bool SvxAsianConfig::IsKerningWesternTextOnly() const {
+ return
+ officecfg::Office::Common::AsianLayout::IsKerningWesternTextOnly::get();
+}
+
+void SvxAsianConfig::SetKerningWesternTextOnly(bool value) {
+ officecfg::Office::Common::AsianLayout::IsKerningWesternTextOnly::set(
+ value, impl_->batch);
+}
+
+CharCompressType SvxAsianConfig::GetCharDistanceCompression() const {
+ return static_cast<CharCompressType>(officecfg::Office::Common::AsianLayout::CompressCharacterDistance::get());
+}
+
+void SvxAsianConfig::SetCharDistanceCompression(CharCompressType value) {
+ officecfg::Office::Common::AsianLayout::CompressCharacterDistance::set(
+ static_cast<sal_uInt16>(value), impl_->batch);
+}
+
+css::uno::Sequence< css::lang::Locale > SvxAsianConfig::GetStartEndCharLocales()
+ const
+{
+ const css::uno::Sequence< OUString > ns(
+ officecfg::Office::Common::AsianLayout::StartEndCharacters::get()->
+ getElementNames());
+ css::uno::Sequence< css::lang::Locale > ls(ns.getLength());
+ std::transform(ns.begin(), ns.end(), ls.getArray(),
+ [](const OUString& rName) -> css::lang::Locale {
+ return LanguageTag::convertToLocale( rName, false); });
+ return ls;
+}
+
+bool SvxAsianConfig::GetStartEndChars(
+ css::lang::Locale const & locale, OUString & startChars,
+ OUString & endChars) const
+{
+ css::uno::Reference< css::container::XNameAccess > set(
+ officecfg::Office::Common::AsianLayout::StartEndCharacters::get());
+ css::uno::Any v;
+ try {
+ v = set->getByName(toString(locale));
+ } catch (css::container::NoSuchElementException &) {
+ return false;
+ }
+ css::uno::Reference< css::beans::XPropertySet > el(
+ v.get< css::uno::Reference< css::beans::XPropertySet > >(),
+ css::uno::UNO_SET_THROW);
+ startChars = el->getPropertyValue("StartCharacters").get< OUString >();
+ endChars = el->getPropertyValue("EndCharacters").get< OUString >();
+ return true;
+}
+
+void SvxAsianConfig::SetStartEndChars(
+ css::lang::Locale const & locale, OUString const * startChars,
+ OUString const * endChars)
+{
+ assert((startChars == nullptr) == (endChars == nullptr));
+ css::uno::Reference< css::container::XNameContainer > set(
+ officecfg::Office::Common::AsianLayout::StartEndCharacters::get(
+ impl_->batch));
+ OUString name(toString(locale));
+ if (startChars == nullptr) {
+ try {
+ set->removeByName(name);
+ } catch (css::container::NoSuchElementException &) {}
+ } else {
+ bool found;
+ css::uno::Any v;
+ try {
+ v = set->getByName(name);
+ found = true;
+ } catch (css::container::NoSuchElementException &) {
+ found = false;
+ }
+ if (found) {
+ css::uno::Reference< css::beans::XPropertySet > el(
+ v.get< css::uno::Reference< css::beans::XPropertySet > >(),
+ css::uno::UNO_SET_THROW);
+ el->setPropertyValue("StartCharacters", css::uno::Any(*startChars));
+ el->setPropertyValue("EndCharacters", css::uno::Any(*endChars));
+ } else {
+ css::uno::Reference< css::beans::XPropertySet > el(
+ (css::uno::Reference< css::lang::XSingleServiceFactory >(
+ set, css::uno::UNO_QUERY_THROW)->
+ createInstance()),
+ css::uno::UNO_QUERY_THROW);
+ el->setPropertyValue("StartCharacters", css::uno::Any(*startChars));
+ el->setPropertyValue("EndCharacters", css::uno::Any(*endChars));
+ css::uno::Any v2(el);
+ try {
+ set->insertByName(name, v2);
+ } catch (css::container::ElementExistException &) {
+ SAL_INFO("svl", "Concurrent update race for \"" << name << '"');
+ }
+ }
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/config/cjkoptions.cxx b/svl/source/config/cjkoptions.cxx
new file mode 100644
index 0000000000..e43d379f7e
--- /dev/null
+++ b/svl/source/config/cjkoptions.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 <svl/cjkoptions.hxx>
+
+#include <svl/languageoptions.hxx>
+#include <i18nlangtag/lang.h>
+#include <i18nlangtag/languagetag.hxx>
+#include <officecfg/System.hxx>
+#include <officecfg/Office/Common.hxx>
+#include <mutex>
+
+using namespace ::com::sun::star::uno;
+
+static void SvtCJKOptions_Load();
+
+namespace SvtCJKOptions
+{
+
+bool IsCJKFontEnabled()
+{
+ SvtCJKOptions_Load();
+ return officecfg::Office::Common::I18N::CJK::CJKFont::get();
+}
+
+bool IsVerticalTextEnabled()
+{
+ SvtCJKOptions_Load();
+ return officecfg::Office::Common::I18N::CJK::VerticalText::get();
+}
+
+bool IsAsianTypographyEnabled()
+{
+ SvtCJKOptions_Load();
+ return officecfg::Office::Common::I18N::CJK::AsianTypography::get();
+}
+
+bool IsJapaneseFindEnabled()
+{
+ SvtCJKOptions_Load();
+ return officecfg::Office::Common::I18N::CJK::JapaneseFind::get();
+}
+
+bool IsRubyEnabled()
+{
+ SvtCJKOptions_Load();
+ return officecfg::Office::Common::I18N::CJK::Ruby::get();
+}
+
+bool IsChangeCaseMapEnabled()
+{
+ SvtCJKOptions_Load();
+ return officecfg::Office::Common::I18N::CJK::ChangeCaseMap::get();
+}
+
+bool IsDoubleLinesEnabled()
+{
+ SvtCJKOptions_Load();
+ return officecfg::Office::Common::I18N::CJK::DoubleLines::get();
+}
+
+void SetAll(bool bSet)
+{
+ SvtCJKOptions_Load();
+ if ( officecfg::Office::Common::I18N::CJK::CJKFont::isReadOnly()
+ || officecfg::Office::Common::I18N::CJK::VerticalText::isReadOnly()
+ || officecfg::Office::Common::I18N::CJK::AsianTypography::isReadOnly()
+ || officecfg::Office::Common::I18N::CJK::JapaneseFind::isReadOnly()
+ || officecfg::Office::Common::I18N::CJK::Ruby::isReadOnly()
+ || officecfg::Office::Common::I18N::CJK::ChangeCaseMap::isReadOnly()
+ || officecfg::Office::Common::I18N::CJK::DoubleLines::isReadOnly() )
+ return;
+
+ std::shared_ptr<comphelper::ConfigurationChanges> xChanges(comphelper::ConfigurationChanges::create());
+ officecfg::Office::Common::I18N::CJK::CJKFont::set(bSet, xChanges);
+ officecfg::Office::Common::I18N::CJK::VerticalText::set(bSet, xChanges);
+ officecfg::Office::Common::I18N::CJK::AsianTypography::set(bSet, xChanges);
+ officecfg::Office::Common::I18N::CJK::JapaneseFind::set(bSet, xChanges);
+ officecfg::Office::Common::I18N::CJK::Ruby::set(bSet, xChanges);
+ officecfg::Office::Common::I18N::CJK::ChangeCaseMap::set(bSet, xChanges);
+ officecfg::Office::Common::I18N::CJK::DoubleLines::set(bSet, xChanges);
+ xChanges->commit();
+}
+
+bool IsAnyEnabled()
+{
+ SvtCJKOptions_Load();
+ return IsCJKFontEnabled() || IsVerticalTextEnabled() || IsAsianTypographyEnabled() || IsJapaneseFindEnabled() ||
+ IsRubyEnabled() || IsChangeCaseMapEnabled() || IsDoubleLinesEnabled() ;
+}
+
+bool IsAnyReadOnly()
+{
+ SvtCJKOptions_Load();
+ return officecfg::Office::Common::I18N::CJK::CJKFont::isReadOnly()
+ || officecfg::Office::Common::I18N::CJK::VerticalText::isReadOnly()
+ || officecfg::Office::Common::I18N::CJK::AsianTypography::isReadOnly()
+ || officecfg::Office::Common::I18N::CJK::JapaneseFind::isReadOnly()
+ || officecfg::Office::Common::I18N::CJK::Ruby::isReadOnly()
+ || officecfg::Office::Common::I18N::CJK::ChangeCaseMap::isReadOnly()
+ || officecfg::Office::Common::I18N::CJK::DoubleLines::isReadOnly();
+}
+
+} // namespace SvtCJKOptions
+
+
+static std::once_flag gLoadFlag;
+
+static void SvtCJKOptions_Load()
+{
+ std::call_once(gLoadFlag,
+ []()
+ {
+ if (officecfg::Office::Common::I18N::CJK::CJKFont::get())
+ return;
+
+ SvtScriptType nScriptType = SvtLanguageOptions::GetScriptTypeOfLanguage(LANGUAGE_SYSTEM);
+ //system locale is CJK
+ bool bAutoEnableCJK = bool(nScriptType & SvtScriptType::ASIAN);
+
+ if (!bAutoEnableCJK)
+ {
+ //windows secondary system locale is CJK
+ OUString sWin16SystemLocale = officecfg::System::L10N::SystemLocale::get();
+ LanguageType eSystemLanguage = LANGUAGE_NONE;
+ if( !sWin16SystemLocale.isEmpty() )
+ eSystemLanguage = LanguageTag::convertToLanguageTypeWithFallback( sWin16SystemLocale );
+ if (eSystemLanguage != LANGUAGE_SYSTEM)
+ {
+ SvtScriptType nWinScript = SvtLanguageOptions::GetScriptTypeOfLanguage( eSystemLanguage );
+ bAutoEnableCJK = bool(nWinScript & SvtScriptType::ASIAN);
+ }
+
+ //CJK keyboard is installed
+ if (!bAutoEnableCJK)
+ bAutoEnableCJK = SvtSystemLanguageOptions::isCJKKeyboardLayoutInstalled();
+ }
+
+ if (bAutoEnableCJK)
+ {
+ std::shared_ptr<comphelper::ConfigurationChanges> xChanges(comphelper::ConfigurationChanges::create());
+ officecfg::Office::Common::I18N::CJK::CJKFont::set(true, xChanges);
+ officecfg::Office::Common::I18N::CJK::VerticalText::set(true, xChanges);
+ officecfg::Office::Common::I18N::CJK::AsianTypography::set(true, xChanges);
+ officecfg::Office::Common::I18N::CJK::JapaneseFind::set(true, xChanges);
+ officecfg::Office::Common::I18N::CJK::Ruby::set(true, xChanges);
+ officecfg::Office::Common::I18N::CJK::ChangeCaseMap::set(true, xChanges);
+ officecfg::Office::Common::I18N::CJK::DoubleLines::set(true, xChanges);
+ xChanges->commit();
+ }
+ });
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/config/ctloptions.cxx b/svl/source/config/ctloptions.cxx
new file mode 100644
index 0000000000..07c9c67fd6
--- /dev/null
+++ b/svl/source/config/ctloptions.cxx
@@ -0,0 +1,446 @@
+/* -*- 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 <svl/ctloptions.hxx>
+
+#include <unotools/configitem.hxx>
+#include <unotools/configmgr.hxx>
+#include <com/sun/star/uno/Any.h>
+#include <com/sun/star/uno/Sequence.hxx>
+#include <osl/mutex.hxx>
+#include "itemholder2.hxx"
+#include <officecfg/Office/Common.hxx>
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::uno;
+
+#define CFG_READONLY_DEFAULT false
+
+class SvtCTLOptions_Impl : public utl::ConfigItem
+{
+private:
+ bool m_bIsLoaded;
+ bool m_bCTLFontEnabled;
+ bool m_bCTLSequenceChecking;
+ bool m_bCTLRestricted;
+ bool m_bCTLTypeAndReplace;
+ SvtCTLOptions::CursorMovement m_eCTLCursorMovement;
+ SvtCTLOptions::TextNumerals m_eCTLTextNumerals;
+
+ bool m_bROCTLFontEnabled;
+ bool m_bROCTLSequenceChecking;
+ bool m_bROCTLRestricted;
+ bool m_bROCTLTypeAndReplace;
+ bool m_bROCTLCursorMovement;
+ bool m_bROCTLTextNumerals;
+
+ virtual void ImplCommit() override;
+
+public:
+ SvtCTLOptions_Impl();
+ virtual ~SvtCTLOptions_Impl() override;
+
+ virtual void Notify( const Sequence< OUString >& _aPropertyNames ) override;
+ void Load();
+
+ bool IsLoaded() const { return m_bIsLoaded; }
+ void SetCTLFontEnabled( bool _bEnabled );
+
+ void SetCTLSequenceChecking( bool _bEnabled );
+
+ void SetCTLSequenceCheckingRestricted( bool _bEnable );
+
+ void SetCTLSequenceCheckingTypeAndReplace( bool _bEnable );
+
+ void SetCTLCursorMovement( SvtCTLOptions::CursorMovement _eMovement );
+
+ void SetCTLTextNumerals( SvtCTLOptions::TextNumerals _eNumerals );
+
+ bool IsReadOnly(SvtCTLOptions::EOption eOption) const;
+};
+namespace
+{
+ Sequence<OUString> & PropertyNames()
+ {
+ static Sequence<OUString> SINGLETON;
+ return SINGLETON;
+ }
+}
+bool SvtCTLOptions_Impl::IsReadOnly(SvtCTLOptions::EOption eOption) const
+{
+ bool bReadOnly = CFG_READONLY_DEFAULT;
+ switch(eOption)
+ {
+ case SvtCTLOptions::E_CTLFONT : bReadOnly = m_bROCTLFontEnabled ; break;
+ case SvtCTLOptions::E_CTLSEQUENCECHECKING : bReadOnly = m_bROCTLSequenceChecking ; break;
+ case SvtCTLOptions::E_CTLCURSORMOVEMENT : bReadOnly = m_bROCTLCursorMovement ; break;
+ case SvtCTLOptions::E_CTLTEXTNUMERALS : bReadOnly = m_bROCTLTextNumerals ; break;
+ case SvtCTLOptions::E_CTLSEQUENCECHECKINGRESTRICTED: bReadOnly = m_bROCTLRestricted ; break;
+ case SvtCTLOptions::E_CTLSEQUENCECHECKINGTYPEANDREPLACE: bReadOnly = m_bROCTLTypeAndReplace; break;
+ default: assert(false);
+ }
+ return bReadOnly;
+}
+SvtCTLOptions_Impl::SvtCTLOptions_Impl() :
+
+ utl::ConfigItem("Office.Common/I18N/CTL"),
+
+ m_bIsLoaded ( false ),
+ m_bCTLFontEnabled ( true ),
+ m_bCTLSequenceChecking ( false ),
+ m_bCTLRestricted ( false ),
+ m_bCTLTypeAndReplace ( false ),
+ m_eCTLCursorMovement ( SvtCTLOptions::MOVEMENT_LOGICAL ),
+ m_eCTLTextNumerals ( SvtCTLOptions::NUMERALS_ARABIC ),
+
+ m_bROCTLFontEnabled ( CFG_READONLY_DEFAULT ),
+ m_bROCTLSequenceChecking( CFG_READONLY_DEFAULT ),
+ m_bROCTLRestricted ( CFG_READONLY_DEFAULT ),
+ m_bROCTLTypeAndReplace ( CFG_READONLY_DEFAULT ),
+ m_bROCTLCursorMovement ( CFG_READONLY_DEFAULT ),
+ m_bROCTLTextNumerals ( CFG_READONLY_DEFAULT )
+{
+}
+SvtCTLOptions_Impl::~SvtCTLOptions_Impl()
+{
+ assert(!IsModified()); // should have been committed
+}
+
+void SvtCTLOptions_Impl::Notify( const Sequence< OUString >& )
+{
+ Load();
+ NotifyListeners(ConfigurationHints::CtlSettingsChanged);
+}
+
+void SvtCTLOptions_Impl::ImplCommit()
+{
+ Sequence< OUString > &rPropertyNames = PropertyNames();
+ OUString* pOrgNames = rPropertyNames.getArray();
+ sal_Int32 nOrgCount = rPropertyNames.getLength();
+
+ Sequence< OUString > aNames( nOrgCount );
+ Sequence< Any > aValues( nOrgCount );
+
+ OUString* pNames = aNames.getArray();
+ Any* pValues = aValues.getArray();
+ sal_Int32 nRealCount = 0;
+
+ for ( int nProp = 0; nProp < nOrgCount; nProp++ )
+ {
+ switch ( nProp )
+ {
+ case 0:
+ {
+ if (!m_bROCTLFontEnabled)
+ {
+ pNames[nRealCount] = pOrgNames[nProp];
+ pValues[nRealCount] <<= m_bCTLFontEnabled;
+ ++nRealCount;
+ }
+ }
+ break;
+
+ case 1:
+ {
+ if (!m_bROCTLSequenceChecking)
+ {
+ pNames[nRealCount] = pOrgNames[nProp];
+ pValues[nRealCount] <<= m_bCTLSequenceChecking;
+ ++nRealCount;
+ }
+ }
+ break;
+
+ case 2:
+ {
+ if (!m_bROCTLCursorMovement)
+ {
+ pNames[nRealCount] = pOrgNames[nProp];
+ pValues[nRealCount] <<= static_cast<sal_Int32>(m_eCTLCursorMovement);
+ ++nRealCount;
+ }
+ }
+ break;
+
+ case 3:
+ {
+ if (!m_bROCTLTextNumerals)
+ {
+ pNames[nRealCount] = pOrgNames[nProp];
+ pValues[nRealCount] <<= static_cast<sal_Int32>(m_eCTLTextNumerals);
+ ++nRealCount;
+ }
+ }
+ break;
+
+ case 4:
+ {
+ if (!m_bROCTLRestricted)
+ {
+ pNames[nRealCount] = pOrgNames[nProp];
+ pValues[nRealCount] <<= m_bCTLRestricted;
+ ++nRealCount;
+ }
+ }
+ break;
+ case 5:
+ {
+ if(!m_bROCTLTypeAndReplace)
+ {
+ pNames[nRealCount] = pOrgNames[nProp];
+ pValues[nRealCount] <<= m_bCTLTypeAndReplace;
+ ++nRealCount;
+ }
+ }
+ break;
+ }
+ }
+ aNames.realloc(nRealCount);
+ aValues.realloc(nRealCount);
+ PutProperties( aNames, aValues );
+ //broadcast changes
+ NotifyListeners(ConfigurationHints::CtlSettingsChanged);
+}
+
+void SvtCTLOptions_Impl::Load()
+{
+ Sequence< OUString >& rPropertyNames = PropertyNames();
+ if ( !rPropertyNames.hasElements() )
+ {
+ rPropertyNames = {
+ "CTLFont",
+ "CTLSequenceChecking",
+ "CTLCursorMovement",
+ "CTLTextNumerals",
+ "CTLSequenceCheckingRestricted",
+ "CTLSequenceCheckingTypeAndReplace" };
+ EnableNotification( rPropertyNames );
+ }
+ Sequence< Any > aValues = GetProperties( rPropertyNames );
+ Sequence< sal_Bool > aROStates = GetReadOnlyStates( rPropertyNames );
+ const Any* pValues = aValues.getConstArray();
+ const sal_Bool* pROStates = aROStates.getConstArray();
+ assert(aValues.getLength() == rPropertyNames.getLength() && "GetProperties failed");
+ assert(aROStates.getLength() == rPropertyNames.getLength() && "GetReadOnlyStates failed");
+ if ( aValues.getLength() == rPropertyNames.getLength() && aROStates.getLength() == rPropertyNames.getLength() )
+ {
+ bool bValue = false;
+ sal_Int32 nValue = 0;
+
+ for ( int nProp = 0; nProp < rPropertyNames.getLength(); nProp++ )
+ {
+ if ( pValues[nProp].hasValue() )
+ {
+ if ( pValues[nProp] >>= bValue )
+ {
+ switch ( nProp )
+ {
+ case 0: { m_bCTLFontEnabled = bValue; m_bROCTLFontEnabled = pROStates[nProp]; } break;
+ case 1: { m_bCTLSequenceChecking = bValue; m_bROCTLSequenceChecking = pROStates[nProp]; } break;
+ case 4: { m_bCTLRestricted = bValue; m_bROCTLRestricted = pROStates[nProp]; } break;
+ case 5: { m_bCTLTypeAndReplace = bValue; m_bROCTLTypeAndReplace = pROStates[nProp]; } break;
+ }
+ }
+ else if ( pValues[nProp] >>= nValue )
+ {
+ switch ( nProp )
+ {
+ case 2: { m_eCTLCursorMovement = static_cast<SvtCTLOptions::CursorMovement>(nValue); m_bROCTLCursorMovement = pROStates[nProp]; } break;
+ case 3: { m_eCTLTextNumerals = static_cast<SvtCTLOptions::TextNumerals>(nValue); m_bROCTLTextNumerals = pROStates[nProp]; } break;
+ }
+ }
+ }
+ }
+ }
+
+ m_bIsLoaded = true;
+}
+void SvtCTLOptions_Impl::SetCTLFontEnabled( bool _bEnabled )
+{
+ if(!m_bROCTLFontEnabled && m_bCTLFontEnabled != _bEnabled)
+ {
+ m_bCTLFontEnabled = _bEnabled;
+ SetModified();
+ NotifyListeners(ConfigurationHints::NONE);
+ }
+}
+void SvtCTLOptions_Impl::SetCTLSequenceChecking( bool _bEnabled )
+{
+ if(!m_bROCTLSequenceChecking && m_bCTLSequenceChecking != _bEnabled)
+ {
+ SetModified();
+ m_bCTLSequenceChecking = _bEnabled;
+ NotifyListeners(ConfigurationHints::NONE);
+ }
+}
+void SvtCTLOptions_Impl::SetCTLSequenceCheckingRestricted( bool _bEnabled )
+{
+ if(!m_bROCTLRestricted && m_bCTLRestricted != _bEnabled)
+ {
+ SetModified();
+ m_bCTLRestricted = _bEnabled;
+ NotifyListeners(ConfigurationHints::NONE);
+ }
+}
+void SvtCTLOptions_Impl::SetCTLSequenceCheckingTypeAndReplace( bool _bEnabled )
+{
+ if(!m_bROCTLTypeAndReplace && m_bCTLTypeAndReplace != _bEnabled)
+ {
+ SetModified();
+ m_bCTLTypeAndReplace = _bEnabled;
+ NotifyListeners(ConfigurationHints::NONE);
+ }
+}
+void SvtCTLOptions_Impl::SetCTLCursorMovement( SvtCTLOptions::CursorMovement _eMovement )
+{
+ if (!m_bROCTLCursorMovement && m_eCTLCursorMovement != _eMovement )
+ {
+ SetModified();
+ m_eCTLCursorMovement = _eMovement;
+ NotifyListeners(ConfigurationHints::NONE);
+ }
+}
+void SvtCTLOptions_Impl::SetCTLTextNumerals( SvtCTLOptions::TextNumerals _eNumerals )
+{
+ if (!m_bROCTLTextNumerals && m_eCTLTextNumerals != _eNumerals )
+ {
+ SetModified();
+ m_eCTLTextNumerals = _eNumerals;
+ NotifyListeners(ConfigurationHints::NONE);
+ }
+}
+
+namespace {
+
+ // global
+ std::weak_ptr<SvtCTLOptions_Impl> g_pCTLOptions;
+
+ osl::Mutex& CTLMutex()
+ {
+ static osl::Mutex aMutex;
+ return aMutex;
+ }
+}
+
+SvtCTLOptions::SvtCTLOptions( bool bDontLoad )
+{
+ // Global access, must be guarded (multithreading)
+ ::osl::MutexGuard aGuard( CTLMutex() );
+
+ m_pImpl = g_pCTLOptions.lock();
+ if ( !m_pImpl )
+ {
+ m_pImpl = std::make_shared<SvtCTLOptions_Impl>();
+ g_pCTLOptions = m_pImpl;
+ ItemHolder2::holdConfigItem(EItem::CTLOptions);
+ }
+
+ if( !bDontLoad && !m_pImpl->IsLoaded() )
+ m_pImpl->Load();
+
+ m_pImpl->AddListener(this);
+}
+
+
+SvtCTLOptions::~SvtCTLOptions()
+{
+ // Global access, must be guarded (multithreading)
+ ::osl::MutexGuard aGuard( CTLMutex() );
+
+ m_pImpl->RemoveListener(this);
+ m_pImpl.reset();
+}
+
+void SvtCTLOptions::SetCTLFontEnabled( bool _bEnabled )
+{
+ assert(m_pImpl->IsLoaded());
+ m_pImpl->SetCTLFontEnabled( _bEnabled );
+}
+
+bool SvtCTLOptions::IsCTLFontEnabled()
+{
+ return officecfg::Office::Common::I18N::CTL::CTLFont::get();
+}
+
+void SvtCTLOptions::SetCTLSequenceChecking( bool _bEnabled )
+{
+ assert(m_pImpl->IsLoaded());
+ m_pImpl->SetCTLSequenceChecking(_bEnabled);
+}
+
+bool SvtCTLOptions::IsCTLSequenceChecking()
+{
+ return officecfg::Office::Common::I18N::CTL::CTLSequenceChecking::get();
+}
+
+void SvtCTLOptions::SetCTLSequenceCheckingRestricted( bool _bEnable )
+{
+ assert(m_pImpl->IsLoaded());
+ m_pImpl->SetCTLSequenceCheckingRestricted(_bEnable);
+}
+
+bool SvtCTLOptions::IsCTLSequenceCheckingRestricted()
+{
+ return officecfg::Office::Common::I18N::CTL::CTLSequenceCheckingRestricted::get();
+}
+
+void SvtCTLOptions::SetCTLSequenceCheckingTypeAndReplace( bool _bEnable )
+{
+ assert(m_pImpl->IsLoaded());
+ m_pImpl->SetCTLSequenceCheckingTypeAndReplace(_bEnable);
+}
+
+bool SvtCTLOptions::IsCTLSequenceCheckingTypeAndReplace()
+{
+ return officecfg::Office::Common::I18N::CTL::CTLSequenceCheckingTypeAndReplace::get();
+}
+
+void SvtCTLOptions::SetCTLCursorMovement( SvtCTLOptions::CursorMovement _eMovement )
+{
+ assert(m_pImpl->IsLoaded());
+ m_pImpl->SetCTLCursorMovement( _eMovement );
+}
+
+SvtCTLOptions::CursorMovement SvtCTLOptions::GetCTLCursorMovement()
+{
+ return static_cast<SvtCTLOptions::CursorMovement>(officecfg::Office::Common::I18N::CTL::CTLCursorMovement::get());
+}
+
+void SvtCTLOptions::SetCTLTextNumerals( SvtCTLOptions::TextNumerals _eNumerals )
+{
+ assert(m_pImpl->IsLoaded());
+ m_pImpl->SetCTLTextNumerals( _eNumerals );
+}
+
+SvtCTLOptions::TextNumerals SvtCTLOptions::GetCTLTextNumerals()
+{
+ if (utl::ConfigManager::IsFuzzing())
+ return SvtCTLOptions::NUMERALS_ARABIC;
+ return static_cast<SvtCTLOptions::TextNumerals>(officecfg::Office::Common::I18N::CTL::CTLTextNumerals::get());
+}
+
+bool SvtCTLOptions::IsReadOnly(EOption eOption) const
+{
+ assert(m_pImpl->IsLoaded());
+ return m_pImpl->IsReadOnly(eOption);
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/config/itemholder2.cxx b/svl/source/config/itemholder2.cxx
new file mode 100644
index 0000000000..4dc321cdce
--- /dev/null
+++ b/svl/source/config/itemholder2.cxx
@@ -0,0 +1,117 @@
+/* -*- 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 "itemholder2.hxx"
+
+#include <osl/diagnose.h>
+#include <comphelper/processfactory.hxx>
+#include <com/sun/star/lang/XComponent.hpp>
+#include <com/sun/star/configuration/theDefaultProvider.hpp>
+#include <rtl/ref.hxx>
+#include <svl/ctloptions.hxx>
+#include <comphelper/diagnose_ex.hxx>
+#include <unotools/options.hxx>
+
+ItemHolder2::ItemHolder2()
+{
+ 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));
+ }
+ catch(const css::uno::RuntimeException&)
+ {
+ throw;
+ }
+#ifdef DBG_UTIL
+ catch(const css::uno::Exception&)
+ {
+ static bool bMessage = true;
+ if(bMessage)
+ {
+ bMessage = false;
+ TOOLS_WARN_EXCEPTION( "svl", "" );
+ }
+ }
+#else
+ catch(css::uno::Exception&){}
+#endif
+}
+
+ItemHolder2::~ItemHolder2()
+{
+ impl_releaseAllItems();
+}
+
+void ItemHolder2::holdConfigItem(EItem eItem)
+{
+ static rtl::Reference<ItemHolder2> pHolder = new ItemHolder2();
+ pHolder->impl_addItem(eItem);
+}
+
+void SAL_CALL ItemHolder2::disposing(const css::lang::EventObject&)
+{
+ impl_releaseAllItems();
+}
+
+void ItemHolder2::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 ItemHolder2::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 ItemHolder2::impl_newItem(TItemInfo& rItem)
+{
+ switch(rItem.eItem)
+ {
+ case EItem::CTLOptions :
+ rItem.pItem.reset( new SvtCTLOptions() );
+ break;
+
+ default:
+ OSL_ASSERT(false);
+ break;
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/config/itemholder2.hxx b/svl/source/config/itemholder2.hxx
new file mode 100644
index 0000000000..cdb72581e7
--- /dev/null
+++ b/svl/source/config/itemholder2.hxx
@@ -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 .
+ */
+
+#ifndef INCLUDED_SVTOOLS_ITEMHOLDER2_HXX_
+#define INCLUDED_SVTOOLS_ITEMHOLDER2_HXX_
+
+#include <unotools/itemholderbase.hxx>
+#include <cppuhelper/implbase.hxx>
+#include <com/sun/star/lang/XEventListener.hpp>
+#include <mutex>
+
+class ItemHolder2 : public ::cppu::WeakImplHelper< css::lang::XEventListener >
+{
+ // member
+private:
+
+ std::mutex m_aLock;
+ std::vector<TItemInfo> m_lItems;
+
+ // c++ interface
+ public:
+
+ ItemHolder2();
+ virtual ~ItemHolder2() 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);
+};
+
+#endif // INCLUDED_SVTOOLS_ITEMHOLDER2_HXX_
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/config/languageoptions.cxx b/svl/source/config/languageoptions.cxx
new file mode 100644
index 0000000000..6391175c85
--- /dev/null
+++ b/svl/source/config/languageoptions.cxx
@@ -0,0 +1,137 @@
+/* -*- 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 <svl/languageoptions.hxx>
+#include <i18nlangtag/mslangid.hxx>
+#include <i18nlangtag/languagetag.hxx>
+#include <com/sun/star/i18n/ScriptType.hpp>
+#include <unotools/syslocale.hxx>
+
+#ifdef _WIN32
+#if !defined WIN32_LEAN_AND_MEAN
+# define WIN32_LEAN_AND_MEAN
+#endif
+#include <windows.h>
+#endif
+
+using namespace ::com::sun::star;
+
+
+namespace SvtLanguageOptions
+{
+
+// returns for a language the scripttype
+SvtScriptType GetScriptTypeOfLanguage( LanguageType nLang )
+{
+ if( LANGUAGE_DONTKNOW == nLang )
+ nLang = LANGUAGE_ENGLISH_US;
+ else if (LANGUAGE_SYSTEM == nLang || LANGUAGE_PROCESS_OR_USER_DEFAULT == nLang)
+ nLang = SvtSysLocale().GetLanguageTag().getLanguageType();
+
+ sal_Int16 nScriptType = MsLangId::getScriptType( nLang );
+ SvtScriptType nScript;
+ switch (nScriptType)
+ {
+ case css::i18n::ScriptType::ASIAN:
+ nScript = SvtScriptType::ASIAN;
+ break;
+ case css::i18n::ScriptType::COMPLEX:
+ nScript = SvtScriptType::COMPLEX;
+ break;
+ default:
+ nScript = SvtScriptType::LATIN;
+ }
+ return nScript;
+}
+
+SvtScriptType FromI18NToSvtScriptType( sal_Int16 nI18NType )
+{
+ switch ( nI18NType )
+ {
+ case i18n::ScriptType::LATIN: return SvtScriptType::LATIN;
+ case i18n::ScriptType::ASIAN: return SvtScriptType::ASIAN;
+ case i18n::ScriptType::COMPLEX: return SvtScriptType::COMPLEX;
+ case i18n::ScriptType::WEAK: return SvtScriptType::NONE; // no mapping
+ default: assert(false && nI18NType && "Unknown i18n::ScriptType"); break;
+ }
+ return SvtScriptType::NONE;
+}
+
+sal_Int16 FromSvtScriptTypeToI18N( SvtScriptType nItemType )
+{
+ switch ( nItemType )
+ {
+ case SvtScriptType::NONE: return 0;
+ case SvtScriptType::LATIN: return i18n::ScriptType::LATIN;
+ case SvtScriptType::ASIAN: return i18n::ScriptType::ASIAN;
+ case SvtScriptType::COMPLEX: return i18n::ScriptType::COMPLEX;
+ case SvtScriptType::UNKNOWN: return 0; // no mapping
+ default: assert(false && static_cast<int>(nItemType) && "unknown SvtScriptType"); break;
+ }
+ return 0;
+}
+
+sal_Int16 GetI18NScriptTypeOfLanguage( LanguageType nLang )
+{
+ return FromSvtScriptTypeToI18N( GetScriptTypeOfLanguage( nLang ) );
+}
+
+} // namespace SvtLanguageOptions
+
+static bool isKeyboardLayoutTypeInstalled(sal_Int16 scriptType)
+{
+ bool isInstalled = false;
+#ifdef _WIN32
+ int nLayouts = GetKeyboardLayoutList(0, nullptr);
+ if (nLayouts > 0)
+ {
+ HKL *lpList = static_cast<HKL*>(LocalAlloc(LPTR, (nLayouts * sizeof(HKL))));
+ if (lpList)
+ {
+ nLayouts = GetKeyboardLayoutList(nLayouts, lpList);
+
+ for(int i = 0; i < nLayouts; ++i)
+ {
+ LCID lang = MAKELCID(LOWORD(lpList[i]), SORT_DEFAULT);
+ if (MsLangId::getScriptType(LanguageType(lang)) == scriptType)
+ {
+ isInstalled = true;
+ break;
+ }
+ }
+
+ LocalFree(lpList);
+ }
+ }
+#else
+ (void)scriptType;
+#endif
+ return isInstalled;
+}
+
+namespace SvtSystemLanguageOptions
+{
+ bool isCJKKeyboardLayoutInstalled()
+ {
+ return isKeyboardLayoutTypeInstalled(css::i18n::ScriptType::ASIAN);
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/crypto/cryptosign.cxx b/svl/source/crypto/cryptosign.cxx
new file mode 100644
index 0000000000..a234afccbc
--- /dev/null
+++ b/svl/source/crypto/cryptosign.cxx
@@ -0,0 +1,2385 @@
+/* -*- 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 <algorithm>
+
+#include <svl/cryptosign.hxx>
+#include <svl/sigstruct.hxx>
+#include <config_crypto.h>
+
+#if USE_CRYPTO_NSS
+#include <systools/curlinit.hxx>
+#endif
+
+#include <rtl/character.hxx>
+#include <rtl/strbuf.hxx>
+#include <rtl/string.hxx>
+#include <sal/log.hxx>
+#include <tools/datetime.hxx>
+#include <tools/stream.hxx>
+#include <comphelper/base64.hxx>
+#include <comphelper/hash.hxx>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/random.hxx>
+#include <comphelper/scopeguard.hxx>
+#include <comphelper/lok.hxx>
+#include <com/sun/star/security/XCertificate.hpp>
+#include <com/sun/star/uno/Sequence.hxx>
+#include <o3tl/char16_t2wchar_t.hxx>
+
+#if USE_CRYPTO_NSS
+// NSS headers for PDF signing
+#include <cert.h>
+#include <keyhi.h>
+#include <pk11pub.h>
+#include <hasht.h>
+#include <secerr.h>
+#include <sechash.h>
+#include <cms.h>
+#include <cmst.h>
+
+// We use curl for RFC3161 time stamp requests
+#include <curl/curl.h>
+
+#include <com/sun/star/xml/crypto/DigestID.hpp>
+#include <com/sun/star/xml/crypto/NSSInitializer.hpp>
+#include <mutex>
+#endif
+
+#if USE_CRYPTO_MSCAPI
+// WinCrypt headers for PDF signing
+// Note: this uses Windows 7 APIs and requires the relevant data types
+#include <prewin.h>
+#include <wincrypt.h>
+#include <postwin.h>
+#include <comphelper/windowserrorstring.hxx>
+#endif
+
+using namespace com::sun::star;
+
+namespace {
+
+#if USE_CRYPTO_ANY
+void appendHex( sal_Int8 nInt, OStringBuffer& rBuffer )
+{
+ static const char pHexDigits[] = { '0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
+ rBuffer.append( pHexDigits[ (nInt >> 4) & 15 ] );
+ rBuffer.append( pHexDigits[ nInt & 15 ] );
+}
+#endif
+
+#if USE_CRYPTO_NSS
+char *PDFSigningPKCS7PasswordCallback(PK11SlotInfo * /*slot*/, PRBool /*retry*/, void *arg)
+{
+ return PL_strdup(static_cast<char *>(arg));
+}
+
+// ASN.1 used in the (much simpler) time stamp request. From RFC3161
+// and other sources.
+
+/*
+AlgorithmIdentifier ::= SEQUENCE {
+ algorithm OBJECT IDENTIFIER,
+ parameters ANY DEFINED BY algorithm OPTIONAL }
+ -- contains a value of the type
+ -- registered for use with the
+ -- algorithm object identifier value
+
+MessageImprint ::= SEQUENCE {
+ hashAlgorithm AlgorithmIdentifier,
+ hashedMessage OCTET STRING }
+*/
+
+struct MessageImprint {
+ SECAlgorithmID hashAlgorithm;
+ SECItem hashedMessage;
+};
+
+/*
+Extension ::= SEQUENCE {
+ extnID OBJECT IDENTIFIER,
+ critical BOOLEAN DEFAULT FALSE,
+ extnValue OCTET STRING }
+*/
+
+struct Extension {
+ SECItem extnID;
+ SECItem critical;
+ SECItem extnValue;
+};
+
+/*
+Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension
+*/
+
+/*
+TSAPolicyId ::= OBJECT IDENTIFIER
+
+TimeStampReq ::= SEQUENCE {
+ version INTEGER { v1(1) },
+ messageImprint MessageImprint,
+ --a hash algorithm OID and the hash value of the data to be
+ --time-stamped
+ reqPolicy TSAPolicyId OPTIONAL,
+ nonce INTEGER OPTIONAL,
+ certReq BOOLEAN DEFAULT FALSE,
+ extensions [0] IMPLICIT Extensions OPTIONAL }
+*/
+
+struct TimeStampReq {
+ SECItem version;
+ MessageImprint messageImprint;
+ SECItem reqPolicy;
+ SECItem nonce;
+ SECItem certReq;
+ Extension *extensions;
+};
+
+/**
+ * General name, defined by RFC 3280.
+ */
+struct GeneralName
+{
+ CERTName name;
+};
+
+/**
+ * List of general names (only one for now), defined by RFC 3280.
+ */
+struct GeneralNames
+{
+ GeneralName names;
+};
+
+/**
+ * Supplies different fields to identify a certificate, defined by RFC 5035.
+ */
+struct IssuerSerial
+{
+ GeneralNames issuer;
+ SECItem serialNumber;
+};
+
+/**
+ * Supplies different fields that are used to identify certificates, defined by
+ * RFC 5035.
+ */
+struct ESSCertIDv2
+{
+ SECAlgorithmID hashAlgorithm;
+ SECItem certHash;
+ IssuerSerial issuerSerial;
+};
+
+/**
+ * This attribute uses the ESSCertIDv2 structure, defined by RFC 5035.
+ */
+struct SigningCertificateV2
+{
+ ESSCertIDv2** certs;
+
+ SigningCertificateV2()
+ : certs(nullptr)
+ {
+ }
+};
+
+/**
+ * GeneralName ::= CHOICE {
+ * otherName [0] OtherName,
+ * rfc822Name [1] IA5String,
+ * dNSName [2] IA5String,
+ * x400Address [3] ORAddress,
+ * directoryName [4] Name,
+ * ediPartyName [5] EDIPartyName,
+ * uniformResourceIdentifier [6] IA5String,
+ * iPAddress [7] OCTET STRING,
+ * registeredID [8] OBJECT IDENTIFIER
+ * }
+ */
+const SEC_ASN1Template GeneralNameTemplate[] =
+{
+ {SEC_ASN1_SEQUENCE, 0, nullptr, sizeof(GeneralName)},
+ {SEC_ASN1_INLINE, offsetof(GeneralName, name), CERT_NameTemplate, 0},
+ {0, 0, nullptr, 0}
+};
+
+/**
+ * GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName
+ */
+const SEC_ASN1Template GeneralNamesTemplate[] =
+{
+ {SEC_ASN1_SEQUENCE, 0, nullptr, sizeof(GeneralNames)},
+ {SEC_ASN1_INLINE | SEC_ASN1_CONTEXT_SPECIFIC | 4, offsetof(GeneralNames, names), GeneralNameTemplate, 0},
+ {0, 0, nullptr, 0}
+};
+
+/**
+ * IssuerSerial ::= SEQUENCE {
+ * issuer GeneralNames,
+ * serialNumber CertificateSerialNumber
+ * }
+ */
+const SEC_ASN1Template IssuerSerialTemplate[] =
+{
+ {SEC_ASN1_SEQUENCE, 0, nullptr, sizeof(IssuerSerial)},
+ {SEC_ASN1_INLINE, offsetof(IssuerSerial, issuer), GeneralNamesTemplate, 0},
+ {SEC_ASN1_INTEGER, offsetof(IssuerSerial, serialNumber), nullptr, 0},
+ {0, 0, nullptr, 0}
+};
+
+
+/**
+ * Hash ::= OCTET STRING
+ *
+ * ESSCertIDv2 ::= SEQUENCE {
+ * hashAlgorithm AlgorithmIdentifier DEFAULT {algorithm id-sha256},
+ * certHash Hash,
+ * issuerSerial IssuerSerial OPTIONAL
+ * }
+ */
+
+SEC_ASN1_MKSUB(SECOID_AlgorithmIDTemplate)
+
+const SEC_ASN1Template ESSCertIDv2Template[] =
+{
+ {SEC_ASN1_SEQUENCE, 0, nullptr, sizeof(ESSCertIDv2)},
+ {SEC_ASN1_INLINE | SEC_ASN1_XTRN, offsetof(ESSCertIDv2, hashAlgorithm), SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate), 0},
+ {SEC_ASN1_OCTET_STRING, offsetof(ESSCertIDv2, certHash), nullptr, 0},
+ {SEC_ASN1_INLINE | SEC_ASN1_XTRN, offsetof(ESSCertIDv2, issuerSerial), IssuerSerialTemplate, 0},
+ {0, 0, nullptr, 0}
+};
+
+/**
+ * SigningCertificateV2 ::= SEQUENCE {
+ * }
+ */
+const SEC_ASN1Template SigningCertificateV2Template[] =
+{
+ {SEC_ASN1_SEQUENCE, 0, nullptr, sizeof(SigningCertificateV2)},
+ {SEC_ASN1_SEQUENCE_OF, offsetof(SigningCertificateV2, certs), ESSCertIDv2Template, 0},
+ {0, 0, nullptr, 0}
+};
+
+struct PKIStatusInfo {
+ SECItem status;
+ SECItem statusString;
+ SECItem failInfo;
+};
+
+const SEC_ASN1Template PKIStatusInfo_Template[] =
+{
+ { SEC_ASN1_SEQUENCE, 0, nullptr, sizeof(PKIStatusInfo) },
+ { SEC_ASN1_INTEGER, offsetof(PKIStatusInfo, status), nullptr, 0 },
+ { SEC_ASN1_CONSTRUCTED | SEC_ASN1_SEQUENCE | SEC_ASN1_OPTIONAL, offsetof(PKIStatusInfo, statusString), nullptr, 0 },
+ { SEC_ASN1_BIT_STRING | SEC_ASN1_OPTIONAL, offsetof(PKIStatusInfo, failInfo), nullptr, 0 },
+ { 0, 0, nullptr, 0 }
+};
+
+const SEC_ASN1Template Any_Template[] =
+{
+ { SEC_ASN1_ANY, 0, nullptr, sizeof(SECItem) }
+};
+
+struct TimeStampResp {
+ PKIStatusInfo status;
+ SECItem timeStampToken;
+};
+
+const SEC_ASN1Template TimeStampResp_Template[] =
+{
+ { SEC_ASN1_SEQUENCE, 0, nullptr, sizeof(TimeStampResp) },
+ { SEC_ASN1_INLINE, offsetof(TimeStampResp, status), PKIStatusInfo_Template, 0 },
+ { SEC_ASN1_ANY | SEC_ASN1_OPTIONAL, offsetof(TimeStampResp, timeStampToken), Any_Template, 0 },
+ { 0, 0, nullptr, 0 }
+};
+
+const SEC_ASN1Template MessageImprint_Template[] =
+{
+ { SEC_ASN1_SEQUENCE, 0, nullptr, sizeof(MessageImprint) },
+ { SEC_ASN1_INLINE, offsetof(MessageImprint, hashAlgorithm), SECOID_AlgorithmIDTemplate, 0 },
+ { SEC_ASN1_OCTET_STRING, offsetof(MessageImprint, hashedMessage), nullptr, 0 },
+ { 0, 0, nullptr, 0 }
+};
+
+const SEC_ASN1Template Extension_Template[] =
+{
+ { SEC_ASN1_SEQUENCE, 0, nullptr, sizeof(Extension) },
+ { SEC_ASN1_OBJECT_ID, offsetof(Extension, extnID), nullptr, 0 },
+ { SEC_ASN1_BOOLEAN, offsetof(Extension, critical), nullptr, 0 },
+ { SEC_ASN1_OCTET_STRING, offsetof(Extension, extnValue), nullptr, 0 },
+ { 0, 0, nullptr, 0 }
+};
+
+const SEC_ASN1Template Extensions_Template[] =
+{
+ { SEC_ASN1_SEQUENCE_OF, 0, Extension_Template, 0 }
+};
+
+const SEC_ASN1Template TimeStampReq_Template[] =
+{
+ { SEC_ASN1_SEQUENCE, 0, nullptr, sizeof(TimeStampReq) },
+ { SEC_ASN1_INTEGER, offsetof(TimeStampReq, version), nullptr, 0 },
+ { SEC_ASN1_INLINE, offsetof(TimeStampReq, messageImprint), MessageImprint_Template, 0 },
+ { SEC_ASN1_OBJECT_ID | SEC_ASN1_OPTIONAL, offsetof(TimeStampReq, reqPolicy), nullptr, 0 },
+ { SEC_ASN1_INTEGER | SEC_ASN1_OPTIONAL, offsetof(TimeStampReq, nonce), nullptr, 0 },
+ { SEC_ASN1_BOOLEAN | SEC_ASN1_OPTIONAL, offsetof(TimeStampReq, certReq), nullptr, 0 },
+ { SEC_ASN1_OPTIONAL | SEC_ASN1_CONTEXT_SPECIFIC | 0, offsetof(TimeStampReq, extensions), Extensions_Template, 0 },
+ { 0, 0, nullptr, 0 }
+};
+
+size_t AppendToBuffer(char const *ptr, size_t size, size_t nmemb, void *userdata)
+{
+ OStringBuffer *pBuffer = static_cast<OStringBuffer*>(userdata);
+ pBuffer->append(ptr, size*nmemb);
+
+ return size*nmemb;
+}
+
+OUString PKIStatusToString(int n)
+{
+ switch (n)
+ {
+ case 0: return "granted";
+ case 1: return "grantedWithMods";
+ case 2: return "rejection";
+ case 3: return "waiting";
+ case 4: return "revocationWarning";
+ case 5: return "revocationNotification";
+ default: return "unknown (" + OUString::number(n) + ")";
+ }
+}
+
+OUString PKIStatusInfoToString(const PKIStatusInfo& rStatusInfo)
+{
+ OUString result = "{status=";
+ if (rStatusInfo.status.len == 1)
+ result += PKIStatusToString(rStatusInfo.status.data[0]);
+ else
+ result += "unknown (len=" + OUString::number(rStatusInfo.status.len);
+
+ // FIXME: Perhaps look at rStatusInfo.statusString.data but note
+ // that we of course can't assume it contains proper UTF-8. After
+ // all, it is data from an external source. Also, RFC3161 claims
+ // it should be a SEQUENCE (1..MAX) OF UTF8String, but another
+ // source claimed it would be a single UTF8String, hmm?
+
+ // FIXME: Worth it to decode failInfo to cleartext, probably not at least as long as this is only for a SAL_INFO
+
+ result += "}";
+
+ return result;
+}
+
+// SEC_StringToOID() and NSS_CMSSignerInfo_AddUnauthAttr() are
+// not exported from libsmime, so copy them here. Sigh.
+
+SECStatus
+my_SEC_StringToOID(SECItem *to, const char *from, PRUint32 len)
+{
+ PRUint32 decimal_numbers = 0;
+ PRUint32 result_bytes = 0;
+ SECStatus rv;
+ PRUint8 result[1024];
+
+ static const PRUint32 max_decimal = 0xffffffff / 10;
+ static const char OIDstring[] = {"OID."};
+
+ if (!from || !to) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return SECFailure;
+ }
+ if (!len) {
+ len = PL_strlen(from);
+ }
+ if (len >= 4 && !PL_strncasecmp(from, OIDstring, 4)) {
+ from += 4; /* skip leading "OID." if present */
+ len -= 4;
+ }
+ if (!len) {
+bad_data:
+ PORT_SetError(SEC_ERROR_BAD_DATA);
+ return SECFailure;
+ }
+ do {
+ PRUint32 decimal = 0;
+ while (len > 0 && rtl::isAsciiDigit(static_cast<unsigned char>(*from))) {
+ PRUint32 addend = *from++ - '0';
+ --len;
+ if (decimal > max_decimal) /* overflow */
+ goto bad_data;
+ decimal = (decimal * 10) + addend;
+ if (decimal < addend) /* overflow */
+ goto bad_data;
+ }
+ if (len != 0 && *from != '.') {
+ goto bad_data;
+ }
+ if (decimal_numbers == 0) {
+ if (decimal > 2)
+ goto bad_data;
+ result[0] = decimal * 40;
+ result_bytes = 1;
+ } else if (decimal_numbers == 1) {
+ if (decimal > 40)
+ goto bad_data;
+ result[0] += decimal;
+ } else {
+ /* encode the decimal number, */
+ PRUint8 * rp;
+ PRUint32 num_bytes = 0;
+ PRUint32 tmp = decimal;
+ while (tmp) {
+ num_bytes++;
+ tmp >>= 7;
+ }
+ if (!num_bytes )
+ ++num_bytes; /* use one byte for a zero value */
+ if (num_bytes + result_bytes > sizeof result)
+ goto bad_data;
+ tmp = num_bytes;
+ rp = result + result_bytes - 1;
+ rp[tmp] = static_cast<PRUint8>(decimal & 0x7f);
+ decimal >>= 7;
+ while (--tmp > 0) {
+ rp[tmp] = static_cast<PRUint8>(decimal | 0x80);
+ decimal >>= 7;
+ }
+ result_bytes += num_bytes;
+ }
+ ++decimal_numbers;
+ if (len > 0) { /* skip trailing '.' */
+ ++from;
+ --len;
+ }
+ } while (len > 0);
+ /* now result contains result_bytes of data */
+ if (to->data && to->len >= result_bytes) {
+ to->len = result_bytes;
+ PORT_Memcpy(to->data, result, to->len);
+ rv = SECSuccess;
+ } else {
+ SECItem result_item = {siBuffer, nullptr, 0 };
+ result_item.data = result;
+ result_item.len = result_bytes;
+ rv = SECITEM_CopyItem(nullptr, to, &result_item);
+ }
+ return rv;
+}
+
+NSSCMSAttribute *
+my_NSS_CMSAttributeArray_FindAttrByOidTag(NSSCMSAttribute **attrs, SECOidTag oidtag, PRBool only)
+{
+ SECOidData *oid;
+ NSSCMSAttribute *attr1, *attr2;
+
+ if (attrs == nullptr)
+ return nullptr;
+
+ oid = SECOID_FindOIDByTag(oidtag);
+ if (oid == nullptr)
+ return nullptr;
+
+ while ((attr1 = *attrs++) != nullptr) {
+ if (attr1->type.len == oid->oid.len && PORT_Memcmp (attr1->type.data,
+ oid->oid.data,
+ oid->oid.len) == 0)
+ break;
+ }
+
+ if (attr1 == nullptr)
+ return nullptr;
+
+ if (!only)
+ return attr1;
+
+ while ((attr2 = *attrs++) != nullptr) {
+ if (attr2->type.len == oid->oid.len && PORT_Memcmp (attr2->type.data,
+ oid->oid.data,
+ oid->oid.len) == 0)
+ break;
+ }
+
+ if (attr2 != nullptr)
+ return nullptr;
+
+ return attr1;
+}
+
+SECStatus
+my_NSS_CMSArray_Add(PLArenaPool *poolp, void ***array, void *obj)
+{
+ int n = 0;
+ void **dest;
+
+ PORT_Assert(array != NULL);
+ if (array == nullptr)
+ return SECFailure;
+
+ if (*array == nullptr) {
+ dest = static_cast<void **>(PORT_ArenaAlloc(poolp, 2 * sizeof(void *)));
+ } else {
+ void **p = *array;
+ while (*p++)
+ n++;
+ dest = static_cast<void **>(PORT_ArenaGrow (poolp,
+ *array,
+ (n + 1) * sizeof(void *),
+ (n + 2) * sizeof(void *)));
+ }
+
+ if (dest == nullptr)
+ return SECFailure;
+
+ dest[n] = obj;
+ dest[n+1] = nullptr;
+ *array = dest;
+ return SECSuccess;
+}
+
+SECOidTag
+my_NSS_CMSAttribute_GetType(const NSSCMSAttribute *attr)
+{
+ SECOidData *typetag;
+
+ typetag = SECOID_FindOID(&(attr->type));
+ if (typetag == nullptr)
+ return SEC_OID_UNKNOWN;
+
+ return typetag->offset;
+}
+
+SECStatus
+my_NSS_CMSAttributeArray_AddAttr(PLArenaPool *poolp, NSSCMSAttribute ***attrs, NSSCMSAttribute *attr)
+{
+ NSSCMSAttribute *oattr;
+ void *mark;
+ SECOidTag type;
+
+ mark = PORT_ArenaMark(poolp);
+
+ /* find oidtag of attr */
+ type = my_NSS_CMSAttribute_GetType(attr);
+
+ /* see if we have one already */
+ oattr = my_NSS_CMSAttributeArray_FindAttrByOidTag(*attrs, type, PR_FALSE);
+ PORT_Assert (oattr == NULL);
+ if (oattr != nullptr)
+ goto loser; /* XXX or would it be better to replace it? */
+
+ /* no, shove it in */
+ if (my_NSS_CMSArray_Add(poolp, reinterpret_cast<void ***>(attrs), static_cast<void *>(attr)) != SECSuccess)
+ goto loser;
+
+ PORT_ArenaUnmark(poolp, mark);
+ return SECSuccess;
+
+loser:
+ PORT_ArenaRelease(poolp, mark);
+ return SECFailure;
+}
+
+SECStatus
+my_NSS_CMSSignerInfo_AddUnauthAttr(NSSCMSSignerInfo *signerinfo, NSSCMSAttribute *attr)
+{
+ return my_NSS_CMSAttributeArray_AddAttr(signerinfo->cmsg->poolp, &(signerinfo->unAuthAttr), attr);
+}
+
+SECStatus
+my_NSS_CMSSignerInfo_AddAuthAttr(NSSCMSSignerInfo *signerinfo, NSSCMSAttribute *attr)
+{
+ return my_NSS_CMSAttributeArray_AddAttr(signerinfo->cmsg->poolp, &(signerinfo->authAttr), attr);
+}
+
+NSSCMSMessage *CreateCMSMessage(const PRTime* time,
+ NSSCMSSignedData **cms_sd,
+ NSSCMSSignerInfo **cms_signer,
+ CERTCertificate *cert,
+ SECItem *digest)
+{
+ NSSCMSMessage *result = NSS_CMSMessage_Create(nullptr);
+ if (!result)
+ {
+ SAL_WARN("svl.crypto", "NSS_CMSMessage_Create failed");
+ return nullptr;
+ }
+
+ *cms_sd = NSS_CMSSignedData_Create(result);
+ if (!*cms_sd)
+ {
+ SAL_WARN("svl.crypto", "NSS_CMSSignedData_Create failed");
+ NSS_CMSMessage_Destroy(result);
+ return nullptr;
+ }
+
+ NSSCMSContentInfo *cms_cinfo = NSS_CMSMessage_GetContentInfo(result);
+ if (NSS_CMSContentInfo_SetContent_SignedData(result, cms_cinfo, *cms_sd) != SECSuccess)
+ {
+ SAL_WARN("svl.crypto", "NSS_CMSContentInfo_SetContent_SignedData failed");
+ NSS_CMSSignedData_Destroy(*cms_sd);
+ NSS_CMSMessage_Destroy(result);
+ return nullptr;
+ }
+
+ cms_cinfo = NSS_CMSSignedData_GetContentInfo(*cms_sd);
+
+ // Attach NULL data as detached data
+ if (NSS_CMSContentInfo_SetContent_Data(result, cms_cinfo, nullptr, PR_TRUE) != SECSuccess)
+ {
+ SAL_WARN("svl.crypto", "NSS_CMSContentInfo_SetContent_Data failed");
+ NSS_CMSSignedData_Destroy(*cms_sd);
+ NSS_CMSMessage_Destroy(result);
+ return nullptr;
+ }
+
+ // workaround: with legacy "dbm:", NSS can't find the private key - try out
+ // if it works, and fallback if it doesn't.
+ if (SECKEYPrivateKey * pPrivateKey = PK11_FindKeyByAnyCert(cert, nullptr))
+ {
+ if (!comphelper::LibreOfficeKit::isActive())
+ {
+ // pPrivateKey only exists in the memory in the LOK case, don't delete it.
+ SECKEY_DestroyPrivateKey(pPrivateKey);
+ }
+ *cms_signer = NSS_CMSSignerInfo_Create(result, cert, SEC_OID_SHA256);
+ }
+ else
+ {
+ pPrivateKey = PK11_FindKeyByDERCert(cert->slot, cert, nullptr);
+ SECKEYPublicKey *const pPublicKey = CERT_ExtractPublicKey(cert);
+ if (pPublicKey && pPrivateKey)
+ {
+ *cms_signer = NSS_CMSSignerInfo_CreateWithSubjKeyID(result, &cert->subjectKeyID, pPublicKey, pPrivateKey, SEC_OID_SHA256);
+ SECKEY_DestroyPrivateKey(pPrivateKey);
+ SECKEY_DestroyPublicKey(pPublicKey);
+ if (*cms_signer)
+ {
+ // this is required in NSS_CMSSignerInfo_IncludeCerts()
+ // (and NSS_CMSSignerInfo_GetSigningCertificate() doesn't work)
+ (**cms_signer).cert = CERT_DupCertificate(cert);
+ }
+ }
+ }
+ if (!*cms_signer)
+ {
+ SAL_WARN("svl.crypto", "NSS_CMSSignerInfo_Create failed");
+ NSS_CMSSignedData_Destroy(*cms_sd);
+ NSS_CMSMessage_Destroy(result);
+ return nullptr;
+ }
+
+ if (time && NSS_CMSSignerInfo_AddSigningTime(*cms_signer, *time) != SECSuccess)
+ {
+ SAL_WARN("svl.crypto", "NSS_CMSSignerInfo_AddSigningTime failed");
+ NSS_CMSSignedData_Destroy(*cms_sd);
+ NSS_CMSMessage_Destroy(result);
+ return nullptr;
+ }
+
+ if (NSS_CMSSignerInfo_IncludeCerts(*cms_signer, NSSCMSCM_CertChain, certUsageEmailSigner) != SECSuccess)
+ {
+ SAL_WARN("svl.crypto", "NSS_CMSSignerInfo_IncludeCerts failed");
+ NSS_CMSSignedData_Destroy(*cms_sd);
+ NSS_CMSMessage_Destroy(result);
+ return nullptr;
+ }
+
+ if (NSS_CMSSignedData_AddCertificate(*cms_sd, cert) != SECSuccess)
+ {
+ SAL_WARN("svl.crypto", "NSS_CMSSignedData_AddCertificate failed");
+ NSS_CMSSignedData_Destroy(*cms_sd);
+ NSS_CMSMessage_Destroy(result);
+ return nullptr;
+ }
+
+ if (NSS_CMSSignedData_AddSignerInfo(*cms_sd, *cms_signer) != SECSuccess)
+ {
+ SAL_WARN("svl.crypto", "NSS_CMSSignedData_AddSignerInfo failed");
+ NSS_CMSSignedData_Destroy(*cms_sd);
+ NSS_CMSMessage_Destroy(result);
+ return nullptr;
+ }
+
+ if (NSS_CMSSignedData_SetDigestValue(*cms_sd, SEC_OID_SHA256, digest) != SECSuccess)
+ {
+ SAL_WARN("svl.crypto", "NSS_CMSSignedData_SetDigestValue failed");
+ NSS_CMSSignedData_Destroy(*cms_sd);
+ NSS_CMSMessage_Destroy(result);
+ return nullptr;
+ }
+
+ return result;
+}
+
+#elif USE_CRYPTO_MSCAPI // ends USE_CRYPTO_NSS
+
+/// Counts how many bytes are needed to encode a given length.
+size_t GetDERLengthOfLength(size_t nLength)
+{
+ size_t nRet = 1;
+
+ if(nLength > 127)
+ {
+ while (nLength >> (nRet * 8))
+ ++nRet;
+ // Long form means one additional byte: the length of the length and
+ // the length itself.
+ ++nRet;
+ }
+ return nRet;
+}
+
+/// Writes the length part of the header.
+void WriteDERLength(SvStream& rStream, size_t nLength)
+{
+ size_t nLengthOfLength = GetDERLengthOfLength(nLength);
+ if (nLengthOfLength == 1)
+ {
+ // We can use the short form.
+ rStream.WriteUInt8(nLength);
+ return;
+ }
+
+ // 0x80 means that the we use the long form: the first byte is the length
+ // of length with the highest bit set to 1, not the actual length.
+ rStream.WriteUInt8(0x80 | (nLengthOfLength - 1));
+ for (size_t i = 1; i < nLengthOfLength; ++i)
+ rStream.WriteUInt8(nLength >> ((nLengthOfLength - i - 1) * 8));
+}
+
+const unsigned nASN1_INTEGER = 0x02;
+const unsigned nASN1_OCTET_STRING = 0x04;
+const unsigned nASN1_NULL = 0x05;
+const unsigned nASN1_OBJECT_IDENTIFIER = 0x06;
+const unsigned nASN1_SEQUENCE = 0x10;
+/// An explicit tag on a constructed value.
+const unsigned nASN1_TAGGED_CONSTRUCTED = 0xa0;
+const unsigned nASN1_CONSTRUCTED = 0x20;
+
+/// Create payload for the 'signing-certificate' signed attribute.
+bool CreateSigningCertificateAttribute(void const * pDerEncoded, int nDerEncoded, PCCERT_CONTEXT pCertContext, SvStream& rEncodedCertificate)
+{
+ // CryptEncodeObjectEx() does not support encoding arbitrary ASN.1
+ // structures, like SigningCertificateV2 from RFC 5035, so let's build it
+ // manually.
+
+ // Count the certificate hash and put it to aHash.
+ // 2.16.840.1.101.3.4.2.1, i.e. sha256.
+ std::vector<unsigned char> aSHA256{0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01};
+
+ HCRYPTPROV hProv = 0;
+ if (!CryptAcquireContextW(&hProv, nullptr, nullptr, PROV_RSA_AES, CRYPT_VERIFYCONTEXT))
+ {
+ SAL_WARN("svl.crypto", "CryptAcquireContext() failed");
+ return false;
+ }
+
+ HCRYPTHASH hHash = 0;
+ if (!CryptCreateHash(hProv, CALG_SHA_256, 0, 0, &hHash))
+ {
+ SAL_WARN("svl.crypto", "CryptCreateHash() failed");
+ return false;
+ }
+
+ if (!CryptHashData(hHash, static_cast<const BYTE*>(pDerEncoded), nDerEncoded, 0))
+ {
+ SAL_WARN("svl.crypto", "CryptHashData() failed");
+ return false;
+ }
+
+ DWORD nHash = 0;
+ if (!CryptGetHashParam(hHash, HP_HASHVAL, nullptr, &nHash, 0))
+ {
+ SAL_WARN("svl.crypto", "CryptGetHashParam() failed to provide the hash length");
+ return false;
+ }
+
+ std::vector<unsigned char> aHash(nHash);
+ if (!CryptGetHashParam(hHash, HP_HASHVAL, aHash.data(), &nHash, 0))
+ {
+ SAL_WARN("svl.crypto", "CryptGetHashParam() failed to provide the hash");
+ return false;
+ }
+
+ CryptDestroyHash(hHash);
+ CryptReleaseContext(hProv, 0);
+
+ // Collect info for IssuerSerial.
+ BYTE* pIssuer = pCertContext->pCertInfo->Issuer.pbData;
+ DWORD nIssuer = pCertContext->pCertInfo->Issuer.cbData;
+ BYTE* pSerial = pCertContext->pCertInfo->SerialNumber.pbData;
+ DWORD nSerial = pCertContext->pCertInfo->SerialNumber.cbData;
+ // pSerial is LE, aSerial is BE.
+ std::vector<BYTE> aSerial(nSerial);
+ for (size_t i = 0; i < nSerial; ++i)
+ aSerial[i] = *(pSerial + nSerial - i - 1);
+
+ // We now have all the info to count the lengths.
+ // The layout of the payload is:
+ // SEQUENCE: SigningCertificateV2
+ // SEQUENCE: SEQUENCE OF ESSCertIDv2
+ // SEQUENCE: ESSCertIDv2
+ // SEQUENCE: AlgorithmIdentifier
+ // OBJECT: algorithm
+ // NULL: parameters
+ // OCTET STRING: certHash
+ // SEQUENCE: IssuerSerial
+ // SEQUENCE: GeneralNames
+ // cont [ 4 ]: Name
+ // SEQUENCE: Issuer blob
+ // INTEGER: CertificateSerialNumber
+
+ size_t nAlgorithm = 1 + GetDERLengthOfLength(aSHA256.size()) + aSHA256.size();
+ size_t nParameters = 1 + GetDERLengthOfLength(1);
+ size_t nAlgorithmIdentifier = 1 + GetDERLengthOfLength(nAlgorithm + nParameters) + nAlgorithm + nParameters;
+ size_t nCertHash = 1 + GetDERLengthOfLength(aHash.size()) + aHash.size();
+ size_t nName = 1 + GetDERLengthOfLength(nIssuer) + nIssuer;
+ size_t nGeneralNames = 1 + GetDERLengthOfLength(nName) + nName;
+ size_t nCertificateSerialNumber = 1 + GetDERLengthOfLength(nSerial) + nSerial;
+ size_t nIssuerSerial = 1 + GetDERLengthOfLength(nGeneralNames + nCertificateSerialNumber) + nGeneralNames + nCertificateSerialNumber;
+ size_t nESSCertIDv2 = 1 + GetDERLengthOfLength(nAlgorithmIdentifier + nCertHash + nIssuerSerial) + nAlgorithmIdentifier + nCertHash + nIssuerSerial;
+ size_t nESSCertIDv2s = 1 + GetDERLengthOfLength(nESSCertIDv2) + nESSCertIDv2;
+
+ // Write SigningCertificateV2.
+ rEncodedCertificate.WriteUInt8(nASN1_SEQUENCE | nASN1_CONSTRUCTED);
+ WriteDERLength(rEncodedCertificate, nESSCertIDv2s);
+ // Write SEQUENCE OF ESSCertIDv2.
+ rEncodedCertificate.WriteUInt8(nASN1_SEQUENCE | nASN1_CONSTRUCTED);
+ WriteDERLength(rEncodedCertificate, nESSCertIDv2);
+ // Write ESSCertIDv2.
+ rEncodedCertificate.WriteUInt8(nASN1_SEQUENCE | nASN1_CONSTRUCTED);
+ WriteDERLength(rEncodedCertificate, nAlgorithmIdentifier + nCertHash + nIssuerSerial);
+ // Write AlgorithmIdentifier.
+ rEncodedCertificate.WriteUInt8(nASN1_SEQUENCE | nASN1_CONSTRUCTED);
+ WriteDERLength(rEncodedCertificate, nAlgorithm + nParameters);
+ // Write algorithm.
+ rEncodedCertificate.WriteUInt8(nASN1_OBJECT_IDENTIFIER);
+ WriteDERLength(rEncodedCertificate, aSHA256.size());
+ rEncodedCertificate.WriteBytes(aSHA256.data(), aSHA256.size());
+ // Write parameters.
+ rEncodedCertificate.WriteUInt8(nASN1_NULL);
+ rEncodedCertificate.WriteUInt8(0);
+ // Write certHash.
+ rEncodedCertificate.WriteUInt8(nASN1_OCTET_STRING);
+ WriteDERLength(rEncodedCertificate, aHash.size());
+ rEncodedCertificate.WriteBytes(aHash.data(), aHash.size());
+ // Write IssuerSerial.
+ rEncodedCertificate.WriteUInt8(nASN1_SEQUENCE | nASN1_CONSTRUCTED);
+ WriteDERLength(rEncodedCertificate, nGeneralNames + nCertificateSerialNumber);
+ // Write GeneralNames.
+ rEncodedCertificate.WriteUInt8(nASN1_SEQUENCE | nASN1_CONSTRUCTED);
+ WriteDERLength(rEncodedCertificate, nName);
+ // Write Name.
+ rEncodedCertificate.WriteUInt8(nASN1_TAGGED_CONSTRUCTED | 4);
+ WriteDERLength(rEncodedCertificate, nIssuer);
+ rEncodedCertificate.WriteBytes(pIssuer, nIssuer);
+ // Write CertificateSerialNumber.
+ rEncodedCertificate.WriteUInt8(nASN1_INTEGER);
+ WriteDERLength(rEncodedCertificate, nSerial);
+ rEncodedCertificate.WriteBytes(aSerial.data(), aSerial.size());
+
+ return true;
+}
+#endif // USE_CRYPTO_MSCAPI
+
+} // anonymous namespace
+
+namespace svl::crypto {
+
+static int AsHex(char ch)
+{
+ int nRet = 0;
+ if (rtl::isAsciiDigit(static_cast<unsigned char>(ch)))
+ nRet = ch - '0';
+ else
+ {
+ if (ch >= 'a' && ch <= 'f')
+ nRet = ch - 'a';
+ else if (ch >= 'A' && ch <= 'F')
+ nRet = ch - 'A';
+ else
+ return -1;
+ nRet += 10;
+ }
+ return nRet;
+}
+
+std::vector<unsigned char> DecodeHexString(std::string_view rHex)
+{
+ std::vector<unsigned char> aRet;
+ size_t nHexLen = rHex.size();
+ {
+ int nByte = 0;
+ int nCount = 2;
+ for (size_t i = 0; i < nHexLen; ++i)
+ {
+ nByte = nByte << 4;
+ sal_Int8 nParsed = AsHex(rHex[i]);
+ if (nParsed == -1)
+ {
+ SAL_WARN("svl.crypto", "DecodeHexString: invalid hex value");
+ return aRet;
+ }
+ nByte += nParsed;
+ --nCount;
+ if (!nCount)
+ {
+ aRet.push_back(nByte);
+ nCount = 2;
+ nByte = 0;
+ }
+ }
+ }
+
+ return aRet;
+}
+
+bool Signing::Sign(OStringBuffer& rCMSHexBuffer)
+{
+#if !USE_CRYPTO_ANY
+ (void)rCMSHexBuffer;
+ return false;
+#else
+ // Create the PKCS#7 object.
+ css::uno::Sequence<sal_Int8> aDerEncoded = m_xCertificate->getEncoded();
+ if (!aDerEncoded.hasElements())
+ {
+ SAL_WARN("svl.crypto", "Crypto::Signing: empty certificate");
+ return false;
+ }
+
+#if USE_CRYPTO_NSS
+ CERTCertificate *cert = CERT_DecodeCertFromPackage(reinterpret_cast<char *>(aDerEncoded.getArray()), aDerEncoded.getLength());
+
+ if (!cert)
+ {
+ SAL_WARN("svl.crypto", "CERT_DecodeCertFromPackage failed");
+ return false;
+ }
+
+ std::vector<unsigned char> aHashResult;
+ {
+ comphelper::Hash aHash(comphelper::HashType::SHA256);
+
+ for (const auto& pair : m_dataBlocks)
+ aHash.update(static_cast<const unsigned char*>(pair.first), pair.second);
+
+ aHashResult = aHash.finalize();
+ }
+ SECItem digest;
+ digest.data = aHashResult.data();
+ digest.len = aHashResult.size();
+
+ PRTime now = PR_Now();
+ NSSCMSSignedData *cms_sd(nullptr);
+ NSSCMSSignerInfo *cms_signer(nullptr);
+ NSSCMSMessage *cms_msg = CreateCMSMessage(nullptr, &cms_sd, &cms_signer, cert, &digest);
+ if (!cms_msg)
+ return false;
+
+ OString pass(OUStringToOString( m_aSignPassword, RTL_TEXTENCODING_UTF8 ));
+
+ TimeStampReq src;
+ OStringBuffer response_buffer;
+ TimeStampResp response;
+ SECItem response_item;
+ NSSCMSAttribute timestamp;
+ SECItem values[2];
+ SECItem *valuesp[2];
+ valuesp[0] = values;
+ valuesp[1] = nullptr;
+ SECOidData typetag;
+
+ if( !m_aSignTSA.isEmpty() )
+ {
+ // Create another CMS message with the same contents as cms_msg, because it doesn't seem
+ // possible to encode a message twice (once to get something to timestamp, and then after
+ // adding the timestamp attribute).
+
+ NSSCMSSignedData *ts_cms_sd;
+ NSSCMSSignerInfo *ts_cms_signer;
+ NSSCMSMessage *ts_cms_msg = CreateCMSMessage(&now, &ts_cms_sd, &ts_cms_signer, cert, &digest);
+ if (!ts_cms_msg)
+ {
+ return false;
+ }
+
+ SECItem ts_cms_output;
+ ts_cms_output.data = nullptr;
+ ts_cms_output.len = 0;
+ PLArenaPool *ts_arena = PORT_NewArena(10000);
+ NSSCMSEncoderContext *ts_cms_ecx;
+ ts_cms_ecx = NSS_CMSEncoder_Start(ts_cms_msg, nullptr, nullptr, &ts_cms_output, ts_arena, PDFSigningPKCS7PasswordCallback,
+ const_cast<char*>(pass.getStr()), nullptr, nullptr, nullptr, nullptr);
+
+ if (NSS_CMSEncoder_Finish(ts_cms_ecx) != SECSuccess)
+ {
+ SAL_WARN("svl.crypto", "NSS_CMSEncoder_Finish failed");
+ return false;
+ }
+
+ // I have compared the ts_cms_output produced here with the cms_output produced below when
+ // not actually calling my_NSS_CMSSignerInfo_AddUnauthAttr()), and they are identical.
+
+ std::vector<unsigned char> aTsHashResult = comphelper::Hash::calculateHash(ts_cms_signer->encDigest.data, ts_cms_signer->encDigest.len, comphelper::HashType::SHA256);
+ SECItem ts_digest;
+ ts_digest.type = siBuffer;
+ ts_digest.data = aTsHashResult.data();
+ ts_digest.len = aTsHashResult.size();
+
+ unsigned char cOne = 1;
+ src.version.type = siUnsignedInteger;
+ src.version.data = &cOne;
+ src.version.len = sizeof(cOne);
+
+ src.messageImprint.hashAlgorithm.algorithm.data = nullptr;
+ src.messageImprint.hashAlgorithm.parameters.data = nullptr;
+ SECOID_SetAlgorithmID(nullptr, &src.messageImprint.hashAlgorithm, SEC_OID_SHA256, nullptr);
+ src.messageImprint.hashedMessage = ts_digest;
+
+ src.reqPolicy.type = siBuffer;
+ src.reqPolicy.data = nullptr;
+ src.reqPolicy.len = 0;
+
+ unsigned int nNonce = comphelper::rng::uniform_uint_distribution(0, SAL_MAX_UINT32);
+ src.nonce.type = siUnsignedInteger;
+ src.nonce.data = reinterpret_cast<unsigned char*>(&nNonce);
+ src.nonce.len = sizeof(nNonce);
+
+ src.certReq.type = siUnsignedInteger;
+ src.certReq.data = &cOne;
+ src.certReq.len = sizeof(cOne);
+
+ src.extensions = nullptr;
+
+ SECItem* timestamp_request = SEC_ASN1EncodeItem(nullptr, nullptr, &src, TimeStampReq_Template);
+ if (timestamp_request == nullptr)
+ {
+ SAL_WARN("svl.crypto", "SEC_ASN1EncodeItem failed");
+ return false;
+ }
+
+ if (timestamp_request->data == nullptr)
+ {
+ SAL_WARN("svl.crypto", "SEC_ASN1EncodeItem succeeded but got NULL data");
+ SECITEM_FreeItem(timestamp_request, PR_TRUE);
+ return false;
+ }
+
+ SAL_INFO("svl.crypto", "request length=" << timestamp_request->len);
+
+ // Send time stamp request to TSA server, receive response
+
+ CURL* curl = curl_easy_init();
+ CURLcode rc;
+ struct curl_slist* slist = nullptr;
+
+ if (!curl)
+ {
+ SAL_WARN("svl.crypto", "curl_easy_init failed");
+ SECITEM_FreeItem(timestamp_request, PR_TRUE);
+ return false;
+ }
+
+ ::InitCurl_easy(curl);
+
+ SAL_INFO("svl.crypto", "Setting curl to verbose: " << (curl_easy_setopt(curl, CURLOPT_VERBOSE, 1) == CURLE_OK ? "OK" : "FAIL"));
+
+ if ((rc = curl_easy_setopt(curl, CURLOPT_URL, OUStringToOString(m_aSignTSA, RTL_TEXTENCODING_UTF8).getStr())) != CURLE_OK)
+ {
+ SAL_WARN("svl.crypto", "curl_easy_setopt(CURLOPT_URL) failed: " << curl_easy_strerror(rc));
+ curl_easy_cleanup(curl);
+ SECITEM_FreeItem(timestamp_request, PR_TRUE);
+ return false;
+ }
+
+ slist = curl_slist_append(slist, "Content-Type: application/timestamp-query");
+ slist = curl_slist_append(slist, "Accept: application/timestamp-reply");
+
+ if ((rc = curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist)) != CURLE_OK)
+ {
+ SAL_WARN("svl.crypto", "curl_easy_setopt(CURLOPT_HTTPHEADER) failed: " << curl_easy_strerror(rc));
+ curl_slist_free_all(slist);
+ curl_easy_cleanup(curl);
+ SECITEM_FreeItem(timestamp_request, PR_TRUE);
+ return false;
+ }
+
+ if ((rc = curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, static_cast<tools::Long>(timestamp_request->len))) != CURLE_OK ||
+ (rc = curl_easy_setopt(curl, CURLOPT_POSTFIELDS, timestamp_request->data)) != CURLE_OK)
+ {
+ SAL_WARN("svl.crypto", "curl_easy_setopt(CURLOPT_POSTFIELDSIZE or CURLOPT_POSTFIELDS) failed: " << curl_easy_strerror(rc));
+ curl_easy_cleanup(curl);
+ SECITEM_FreeItem(timestamp_request, PR_TRUE);
+ return false;
+ }
+
+ if ((rc = curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response_buffer)) != CURLE_OK ||
+ (rc = curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, AppendToBuffer)) != CURLE_OK)
+ {
+ SAL_WARN("svl.crypto", "curl_easy_setopt(CURLOPT_WRITEDATA or CURLOPT_WRITEFUNCTION) failed: " << curl_easy_strerror(rc));
+ curl_easy_cleanup(curl);
+ SECITEM_FreeItem(timestamp_request, PR_TRUE);
+ return false;
+ }
+
+ if ((rc = curl_easy_setopt(curl, CURLOPT_POST, 1)) != CURLE_OK)
+ {
+ SAL_WARN("svl.crypto", "curl_easy_setopt(CURLOPT_POST) failed: " << curl_easy_strerror(rc));
+ curl_easy_cleanup(curl);
+ SECITEM_FreeItem(timestamp_request, PR_TRUE);
+ return false;
+ }
+
+ char error_buffer[CURL_ERROR_SIZE];
+ if ((rc = curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, error_buffer)) != CURLE_OK)
+ {
+ SAL_WARN("svl.crypto", "curl_easy_setopt(CURLOPT_ERRORBUFFER) failed: " << curl_easy_strerror(rc));
+ curl_easy_cleanup(curl);
+ SECITEM_FreeItem(timestamp_request, PR_TRUE);
+ return false;
+ }
+
+ // Use a ten second timeout
+ if ((rc = curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10)) != CURLE_OK ||
+ (rc = curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 10)) != CURLE_OK)
+ {
+ SAL_WARN("svl.crypto", "curl_easy_setopt(CURLOPT_TIMEOUT or CURLOPT_CONNECTTIMEOUT) failed: " << curl_easy_strerror(rc));
+ curl_easy_cleanup(curl);
+ SECITEM_FreeItem(timestamp_request, PR_TRUE);
+ return false;
+ }
+
+ if (curl_easy_perform(curl) != CURLE_OK)
+ {
+ SAL_WARN("svl.crypto", "curl_easy_perform failed: " << error_buffer);
+ curl_easy_cleanup(curl);
+ SECITEM_FreeItem(timestamp_request, PR_TRUE);
+ return false;
+ }
+
+ SAL_INFO("svl.crypto", "PDF signing: got response, length=" << response_buffer.getLength());
+
+ curl_slist_free_all(slist);
+ curl_easy_cleanup(curl);
+ SECITEM_FreeItem(timestamp_request, PR_TRUE);
+
+ memset(&response, 0, sizeof(response));
+
+ response_item.type = siBuffer;
+ response_item.data = reinterpret_cast<unsigned char*>(const_cast<char*>(response_buffer.getStr()));
+ response_item.len = response_buffer.getLength();
+
+ if (SEC_ASN1DecodeItem(nullptr, &response, TimeStampResp_Template, &response_item) != SECSuccess)
+ {
+ SAL_WARN("svl.crypto", "SEC_ASN1DecodeItem failed");
+ return false;
+ }
+
+ SAL_INFO("svl.crypto", "TimeStampResp received and decoded, status=" << PKIStatusInfoToString(response.status));
+
+ if (response.status.status.len != 1 ||
+ (response.status.status.data[0] != 0 && response.status.status.data[0] != 1))
+ {
+ SAL_WARN("svl.crypto", "Timestamp request was not granted");
+ return false;
+ }
+
+ // timestamp.type filled in below
+
+ // Not sure if we actually need two entries in the values array, now when valuesp is an
+ // array too, the pointer to the values array followed by a null pointer. But I don't feel
+ // like experimenting.
+ values[0] = response.timeStampToken;
+ values[1].type = siBuffer;
+ values[1].data = nullptr;
+ values[1].len = 0;
+
+ timestamp.values = valuesp;
+
+ typetag.oid.data = nullptr;
+ // id-aa-timeStampToken OBJECT IDENTIFIER ::= { iso(1)
+ // member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs-9(9)
+ // smime(16) aa(2) 14 }
+ if (my_SEC_StringToOID(&typetag.oid, "1.2.840.113549.1.9.16.2.14", 0) != SECSuccess)
+ {
+ SAL_WARN("svl.crypto", "SEC_StringToOID failed");
+ return false;
+ }
+ typetag.offset = SEC_OID_UNKNOWN; // ???
+ typetag.desc = "id-aa-timeStampToken";
+ typetag.mechanism = CKM_SHA_1; // ???
+ typetag.supportedExtension = UNSUPPORTED_CERT_EXTENSION; // ???
+ timestamp.typeTag = &typetag;
+
+ timestamp.type = typetag.oid; // ???
+
+ timestamp.encoded = PR_TRUE; // ???
+
+ if (my_NSS_CMSSignerInfo_AddUnauthAttr(cms_signer, &timestamp) != SECSuccess)
+ {
+ SAL_WARN("svl.crypto", "NSS_CMSSignerInfo_AddUnauthAttr failed");
+ return false;
+ }
+ }
+
+ // Add the signing certificate as a signed attribute.
+ ESSCertIDv2* aCertIDs[2];
+ ESSCertIDv2 aCertID;
+ // Write ESSCertIDv2.hashAlgorithm.
+ aCertID.hashAlgorithm.algorithm.data = nullptr;
+ aCertID.hashAlgorithm.parameters.data = nullptr;
+ SECOID_SetAlgorithmID(nullptr, &aCertID.hashAlgorithm, SEC_OID_SHA256, nullptr);
+ comphelper::ScopeGuard aAlgoGuard(
+ [&aCertID] () { SECOID_DestroyAlgorithmID(&aCertID.hashAlgorithm, false); } );
+ // Write ESSCertIDv2.certHash.
+ SECItem aCertHashItem;
+ auto pDerEncoded = reinterpret_cast<const unsigned char *>(aDerEncoded.getArray());
+ std::vector<unsigned char> aCertHashResult = comphelper::Hash::calculateHash(pDerEncoded, aDerEncoded.getLength(), comphelper::HashType::SHA256);
+ aCertHashItem.type = siBuffer;
+ aCertHashItem.data = aCertHashResult.data();
+ aCertHashItem.len = aCertHashResult.size();
+ aCertID.certHash = aCertHashItem;
+ // Write ESSCertIDv2.issuerSerial.
+ IssuerSerial aSerial;
+ GeneralName aName;
+ aName.name = cert->issuer;
+ aSerial.issuer.names = aName;
+ aSerial.serialNumber = cert->serialNumber;
+ aCertID.issuerSerial = aSerial;
+ // Write SigningCertificateV2.certs.
+ aCertIDs[0] = &aCertID;
+ aCertIDs[1] = nullptr;
+ SigningCertificateV2 aCertificate;
+ aCertificate.certs = &aCertIDs[0];
+ SECItem* pEncodedCertificate = SEC_ASN1EncodeItem(nullptr, nullptr, &aCertificate, SigningCertificateV2Template);
+ if (!pEncodedCertificate)
+ {
+ SAL_WARN("svl.crypto", "SEC_ASN1EncodeItem() failed");
+ return false;
+ }
+
+ NSSCMSAttribute aAttribute;
+ SECItem aAttributeValues[2];
+ SECItem* pAttributeValues[2];
+ pAttributeValues[0] = aAttributeValues;
+ pAttributeValues[1] = nullptr;
+ aAttributeValues[0] = *pEncodedCertificate;
+ aAttributeValues[1].type = siBuffer;
+ aAttributeValues[1].data = nullptr;
+ aAttributeValues[1].len = 0;
+ aAttribute.values = pAttributeValues;
+
+ SECOidData aOidData;
+ aOidData.oid.data = nullptr;
+ /*
+ * id-aa-signingCertificateV2 OBJECT IDENTIFIER ::=
+ * { iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs9(9)
+ * smime(16) id-aa(2) 47 }
+ */
+ if (my_SEC_StringToOID(&aOidData.oid, "1.2.840.113549.1.9.16.2.47", 0) != SECSuccess)
+ {
+ SAL_WARN("svl.crypto", "my_SEC_StringToOID() failed");
+ return false;
+ }
+ comphelper::ScopeGuard aGuard(
+ [&aOidData] () { SECITEM_FreeItem(&aOidData.oid, false); } );
+ aOidData.offset = SEC_OID_UNKNOWN;
+ aOidData.desc = "id-aa-signingCertificateV2";
+ aOidData.mechanism = CKM_SHA_1;
+ aOidData.supportedExtension = UNSUPPORTED_CERT_EXTENSION;
+ aAttribute.typeTag = &aOidData;
+ aAttribute.type = aOidData.oid;
+ aAttribute.encoded = PR_TRUE;
+
+ if (my_NSS_CMSSignerInfo_AddAuthAttr(cms_signer, &aAttribute) != SECSuccess)
+ {
+ SAL_WARN("svl.crypto", "my_NSS_CMSSignerInfo_AddAuthAttr() failed");
+ return false;
+ }
+
+ SECItem cms_output;
+ cms_output.data = nullptr;
+ cms_output.len = 0;
+ PLArenaPool *arena = PORT_NewArena(10000);
+ const ::comphelper::ScopeGuard aScopeGuard(
+ [&arena]() mutable { PORT_FreeArena(arena, true); } );
+ NSSCMSEncoderContext *cms_ecx;
+
+ // Possibly it would work to even just pass NULL for the password callback function and its
+ // argument here. After all, at least with the hardware token and associated software I tested
+ // with, the software itself pops up a dialog asking for the PIN (password). But I am not going
+ // to test it and risk locking up my token...
+
+ cms_ecx = NSS_CMSEncoder_Start(cms_msg, nullptr, nullptr, &cms_output, arena, PDFSigningPKCS7PasswordCallback,
+ const_cast<char*>(pass.getStr()), nullptr, nullptr, nullptr, nullptr);
+
+ if (!cms_ecx)
+ {
+ SAL_WARN("svl.crypto", "NSS_CMSEncoder_Start failed");
+ return false;
+ }
+
+ if (NSS_CMSEncoder_Finish(cms_ecx) != SECSuccess)
+ {
+ SAL_WARN("svl.crypto", "NSS_CMSEncoder_Finish failed");
+ return false;
+ }
+
+ if (cms_output.len*2 > MAX_SIGNATURE_CONTENT_LENGTH)
+ {
+ SAL_WARN("svl.crypto", "Signature requires more space (" << cms_output.len*2 << ") than we reserved (" << MAX_SIGNATURE_CONTENT_LENGTH << ")");
+ NSS_CMSMessage_Destroy(cms_msg);
+ return false;
+ }
+
+ for (unsigned int i = 0; i < cms_output.len ; i++)
+ appendHex(cms_output.data[i], rCMSHexBuffer);
+
+ SECITEM_FreeItem(pEncodedCertificate, PR_TRUE);
+ NSS_CMSMessage_Destroy(cms_msg);
+
+ return true;
+
+#elif USE_CRYPTO_MSCAPI // ends USE_CRYPTO_NSS
+
+ PCCERT_CONTEXT pCertContext = CertCreateCertificateContext(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, reinterpret_cast<const BYTE*>(aDerEncoded.getArray()), aDerEncoded.getLength());
+ if (pCertContext == nullptr)
+ {
+ SAL_WARN("svl.crypto", "CertCreateCertificateContext failed: " << WindowsErrorString(GetLastError()));
+ return false;
+ }
+
+ CRYPT_SIGN_MESSAGE_PARA aPara = {};
+ aPara.cbSize = sizeof(aPara);
+ aPara.dwMsgEncodingType = PKCS_7_ASN_ENCODING | X509_ASN_ENCODING;
+ aPara.pSigningCert = pCertContext;
+ aPara.HashAlgorithm.pszObjId = const_cast<LPSTR>(szOID_NIST_sha256);
+ aPara.HashAlgorithm.Parameters.cbData = 0;
+ aPara.cMsgCert = 1;
+ aPara.rgpMsgCert = &pCertContext;
+
+ NCRYPT_KEY_HANDLE hCryptKey = 0;
+ DWORD dwFlags = CRYPT_ACQUIRE_CACHE_FLAG | CRYPT_ACQUIRE_ONLY_NCRYPT_KEY_FLAG;
+ HCRYPTPROV_OR_NCRYPT_KEY_HANDLE* phCryptProvOrNCryptKey = &hCryptKey;
+ DWORD nKeySpec;
+ BOOL bFreeNeeded;
+
+ if (!CryptAcquireCertificatePrivateKey(pCertContext,
+ dwFlags,
+ nullptr,
+ phCryptProvOrNCryptKey,
+ &nKeySpec,
+ &bFreeNeeded))
+ {
+ SAL_WARN("svl.crypto", "CryptAcquireCertificatePrivateKey failed: " << WindowsErrorString(GetLastError()));
+ CertFreeCertificateContext(pCertContext);
+ return false;
+ }
+ assert(!bFreeNeeded);
+
+ CMSG_SIGNER_ENCODE_INFO aSignerInfo = {};
+ aSignerInfo.cbSize = sizeof(aSignerInfo);
+ aSignerInfo.pCertInfo = pCertContext->pCertInfo;
+ aSignerInfo.hNCryptKey = hCryptKey;
+ aSignerInfo.dwKeySpec = nKeySpec;
+ aSignerInfo.HashAlgorithm.pszObjId = const_cast<LPSTR>(szOID_NIST_sha256);
+ aSignerInfo.HashAlgorithm.Parameters.cbData = 0;
+
+ // Add the signing certificate as a signed attribute.
+ CRYPT_INTEGER_BLOB aCertificateBlob;
+ SvMemoryStream aEncodedCertificate;
+ if (!CreateSigningCertificateAttribute(aDerEncoded.getArray(), aDerEncoded.getLength(), pCertContext, aEncodedCertificate))
+ {
+ SAL_WARN("svl.crypto", "CreateSigningCertificateAttribute() failed");
+ return false;
+ }
+ aCertificateBlob.pbData = const_cast<BYTE*>(static_cast<const BYTE*>(aEncodedCertificate.GetData()));
+ aCertificateBlob.cbData = aEncodedCertificate.GetSize();
+ CRYPT_ATTRIBUTE aCertificateAttribute;
+ /*
+ * id-aa-signingCertificateV2 OBJECT IDENTIFIER ::=
+ * { iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs9(9)
+ * smime(16) id-aa(2) 47 }
+ */
+ aCertificateAttribute.pszObjId = const_cast<LPSTR>("1.2.840.113549.1.9.16.2.47");
+ aCertificateAttribute.cValue = 1;
+ aCertificateAttribute.rgValue = &aCertificateBlob;
+ aSignerInfo.cAuthAttr = 1;
+ aSignerInfo.rgAuthAttr = &aCertificateAttribute;
+
+ CMSG_SIGNED_ENCODE_INFO aSignedInfo = {};
+ aSignedInfo.cbSize = sizeof(aSignedInfo);
+ aSignedInfo.cSigners = 1;
+ aSignedInfo.rgSigners = &aSignerInfo;
+
+ CERT_BLOB aCertBlob;
+
+ aCertBlob.cbData = pCertContext->cbCertEncoded;
+ aCertBlob.pbData = pCertContext->pbCertEncoded;
+
+ aSignedInfo.cCertEncoded = 1;
+ aSignedInfo.rgCertEncoded = &aCertBlob;
+
+ HCRYPTMSG hMsg = CryptMsgOpenToEncode(PKCS_7_ASN_ENCODING | X509_ASN_ENCODING,
+ CMSG_DETACHED_FLAG,
+ CMSG_SIGNED,
+ &aSignedInfo,
+ nullptr,
+ nullptr);
+ if (!hMsg)
+ {
+ SAL_WARN("svl.crypto", "CryptMsgOpenToEncode failed: " << WindowsErrorString(GetLastError()));
+ CertFreeCertificateContext(pCertContext);
+ return false;
+ }
+
+ for (size_t i = 0; i < m_dataBlocks.size(); ++i)
+ {
+ const bool last = (i == m_dataBlocks.size() - 1);
+ if (!CryptMsgUpdate(hMsg, static_cast<const BYTE *>(m_dataBlocks[i].first), m_dataBlocks[i].second, last))
+ {
+ SAL_WARN("svl.crypto", "CryptMsgUpdate failed: " << WindowsErrorString(GetLastError()));
+ CryptMsgClose(hMsg);
+ CertFreeCertificateContext(pCertContext);
+ return false;
+ }
+ }
+
+ PCRYPT_TIMESTAMP_CONTEXT pTsContext = nullptr;
+
+ if( !m_aSignTSA.isEmpty() )
+ {
+ HCRYPTMSG hDecodedMsg = CryptMsgOpenToDecode(PKCS_7_ASN_ENCODING | X509_ASN_ENCODING,
+ CMSG_DETACHED_FLAG,
+ CMSG_SIGNED,
+ 0,
+ nullptr,
+ nullptr);
+ if (!hDecodedMsg)
+ {
+ SAL_WARN("svl.crypto", "CryptMsgOpenToDecode failed: " << WindowsErrorString(GetLastError()));
+ CryptMsgClose(hMsg);
+ CertFreeCertificateContext(pCertContext);
+ return false;
+ }
+
+ DWORD nTsSigLen = 0;
+
+ if (!CryptMsgGetParam(hMsg, CMSG_BARE_CONTENT_PARAM, 0, nullptr, &nTsSigLen))
+ {
+ SAL_WARN("svl.crypto", "CryptMsgGetParam(CMSG_BARE_CONTENT_PARAM) failed: " << WindowsErrorString(GetLastError()));
+ CryptMsgClose(hDecodedMsg);
+ CryptMsgClose(hMsg);
+ CertFreeCertificateContext(pCertContext);
+ return false;
+ }
+
+ SAL_INFO("svl.crypto", "nTsSigLen=" << nTsSigLen);
+
+ std::unique_ptr<BYTE[]> pTsSig(new BYTE[nTsSigLen]);
+
+ if (!CryptMsgGetParam(hMsg, CMSG_BARE_CONTENT_PARAM, 0, pTsSig.get(), &nTsSigLen))
+ {
+ SAL_WARN("svl.crypto", "CryptMsgGetParam(CMSG_BARE_CONTENT_PARAM) failed: " << WindowsErrorString(GetLastError()));
+ CryptMsgClose(hDecodedMsg);
+ CryptMsgClose(hMsg);
+ CertFreeCertificateContext(pCertContext);
+ return false;
+ }
+
+ if (!CryptMsgUpdate(hDecodedMsg, pTsSig.get(), nTsSigLen, TRUE))
+ {
+ SAL_WARN("svl.crypto", "CryptMsgUpdate failed: " << WindowsErrorString(GetLastError()));
+ CryptMsgClose(hDecodedMsg);
+ CryptMsgClose(hMsg);
+ CertFreeCertificateContext(pCertContext);
+ return false;
+ }
+
+ DWORD nDecodedSignerInfoLen = 0;
+ if (!CryptMsgGetParam(hDecodedMsg, CMSG_SIGNER_INFO_PARAM, 0, nullptr, &nDecodedSignerInfoLen))
+ {
+ SAL_WARN("svl.crypto", "CryptMsgGetParam(CMSG_SIGNER_INFO_PARAM) failed: " << WindowsErrorString(GetLastError()));
+ CryptMsgClose(hDecodedMsg);
+ CryptMsgClose(hMsg);
+ CertFreeCertificateContext(pCertContext);
+ return false;
+ }
+
+ std::unique_ptr<BYTE[]> pDecodedSignerInfoBuf(new BYTE[nDecodedSignerInfoLen]);
+
+ if (!CryptMsgGetParam(hDecodedMsg, CMSG_SIGNER_INFO_PARAM, 0, pDecodedSignerInfoBuf.get(), &nDecodedSignerInfoLen))
+ {
+ SAL_WARN("svl.crypto", "CryptMsgGetParam(CMSG_SIGNER_INFO_PARAM) failed: " << WindowsErrorString(GetLastError()));
+ CryptMsgClose(hDecodedMsg);
+ CryptMsgClose(hMsg);
+ CertFreeCertificateContext(pCertContext);
+ return false;
+ }
+
+ CMSG_SIGNER_INFO *pDecodedSignerInfo = reinterpret_cast<CMSG_SIGNER_INFO *>(pDecodedSignerInfoBuf.get());
+
+ CRYPT_TIMESTAMP_PARA aTsPara;
+ unsigned int nNonce = comphelper::rng::uniform_uint_distribution(0, SAL_MAX_UINT32);
+
+ aTsPara.pszTSAPolicyId = nullptr;
+ aTsPara.fRequestCerts = TRUE;
+ aTsPara.Nonce.cbData = sizeof(nNonce);
+ aTsPara.Nonce.pbData = reinterpret_cast<BYTE *>(&nNonce);
+ aTsPara.cExtension = 0;
+ aTsPara.rgExtension = nullptr;
+
+ if (!CryptRetrieveTimeStamp(o3tl::toW(m_aSignTSA.getStr()),
+ 0,
+ 10000,
+ szOID_NIST_sha256,
+ &aTsPara,
+ pDecodedSignerInfo->EncryptedHash.pbData,
+ pDecodedSignerInfo->EncryptedHash.cbData,
+ &pTsContext,
+ nullptr,
+ nullptr))
+ {
+ SAL_WARN("svl.crypto", "CryptRetrieveTimeStamp failed: " << WindowsErrorString(GetLastError()));
+ CryptMsgClose(hDecodedMsg);
+ CryptMsgClose(hMsg);
+ CertFreeCertificateContext(pCertContext);
+ return false;
+ }
+
+ SAL_INFO("svl.crypto", "Time stamp size is " << pTsContext->cbEncoded << " bytes");
+
+ // I tried to use CryptMsgControl() with CMSG_CTRL_ADD_SIGNER_UNAUTH_ATTR to add the
+ // timestamp, but that failed with "The parameter is incorrect". Probably it is too late to
+ // modify the message once its data has already been encoded as part of the
+ // CryptMsgGetParam() with CMSG_BARE_CONTENT_PARAM above. So close the message and re-do its
+ // creation steps, but now with an amended aSignerInfo.
+
+ CRYPT_INTEGER_BLOB aTimestampBlob;
+ aTimestampBlob.cbData = pTsContext->cbEncoded;
+ aTimestampBlob.pbData = pTsContext->pbEncoded;
+
+ CRYPT_ATTRIBUTE aTimestampAttribute;
+ aTimestampAttribute.pszObjId = const_cast<LPSTR>(
+ "1.2.840.113549.1.9.16.2.14");
+ aTimestampAttribute.cValue = 1;
+ aTimestampAttribute.rgValue = &aTimestampBlob;
+
+ aSignerInfo.cUnauthAttr = 1;
+ aSignerInfo.rgUnauthAttr = &aTimestampAttribute;
+
+ CryptMsgClose(hMsg);
+
+ hMsg = CryptMsgOpenToEncode(PKCS_7_ASN_ENCODING | X509_ASN_ENCODING,
+ CMSG_DETACHED_FLAG,
+ CMSG_SIGNED,
+ &aSignedInfo,
+ nullptr,
+ nullptr);
+
+ for (size_t i = 0; i < m_dataBlocks.size(); ++i)
+ {
+ const bool last = (i == m_dataBlocks.size() - 1);
+ if (!hMsg ||
+ !CryptMsgUpdate(hMsg, static_cast<const BYTE *>(m_dataBlocks[i].first), m_dataBlocks[i].second, last))
+ {
+ SAL_WARN("svl.crypto", "Re-creating the message failed: " << WindowsErrorString(GetLastError()));
+ CryptMemFree(pTsContext);
+ CryptMsgClose(hDecodedMsg);
+ CryptMsgClose(hMsg);
+ CertFreeCertificateContext(pCertContext);
+ return false;
+ }
+ }
+
+ CryptMsgClose(hDecodedMsg);
+ }
+
+ DWORD nSigLen = 0;
+
+ if (!CryptMsgGetParam(hMsg, CMSG_CONTENT_PARAM, 0, nullptr, &nSigLen))
+ {
+ SAL_WARN("svl.crypto", "CryptMsgGetParam(CMSG_CONTENT_PARAM) failed: " << WindowsErrorString(GetLastError()));
+ if (pTsContext)
+ CryptMemFree(pTsContext);
+ CryptMsgClose(hMsg);
+ CertFreeCertificateContext(pCertContext);
+ return false;
+ }
+
+ if (nSigLen*2 > MAX_SIGNATURE_CONTENT_LENGTH)
+ {
+ SAL_WARN("svl.crypto", "Signature requires more space (" << nSigLen*2 << ") than we reserved (" << MAX_SIGNATURE_CONTENT_LENGTH << ")");
+ if (pTsContext)
+ CryptMemFree(pTsContext);
+ CryptMsgClose(hMsg);
+ CertFreeCertificateContext(pCertContext);
+ return false;
+ }
+
+ SAL_INFO("svl.crypto", "Signature size is " << nSigLen << " bytes");
+ std::unique_ptr<BYTE[]> pSig(new BYTE[nSigLen]);
+
+ if (!CryptMsgGetParam(hMsg, CMSG_CONTENT_PARAM, 0, pSig.get(), &nSigLen))
+ {
+ SAL_WARN("svl.crypto", "CryptMsgGetParam(CMSG_CONTENT_PARAM) failed: " << WindowsErrorString(GetLastError()));
+ if (pTsContext)
+ CryptMemFree(pTsContext);
+ CryptMsgClose(hMsg);
+ CertFreeCertificateContext(pCertContext);
+ return false;
+ }
+
+ // Release resources
+ if (pTsContext)
+ CryptMemFree(pTsContext);
+ CryptMsgClose(hMsg);
+ CertFreeCertificateContext(pCertContext);
+
+ for (unsigned int i = 0; i < nSigLen ; i++)
+ appendHex(pSig[i], rCMSHexBuffer);
+
+ return true;
+#endif // USE_CRYPTO_MSCAPI
+#endif // USE_CRYPTO_ANY
+}
+
+namespace
+{
+#if USE_CRYPTO_NSS
+/// Similar to NSS_CMSAttributeArray_FindAttrByOidTag(), but works directly with a SECOidData.
+NSSCMSAttribute* CMSAttributeArray_FindAttrByOidData(NSSCMSAttribute** attrs, SECOidData const * oid, PRBool only)
+{
+ NSSCMSAttribute* attr1, *attr2;
+
+ if (attrs == nullptr)
+ return nullptr;
+
+ if (oid == nullptr)
+ return nullptr;
+
+ while ((attr1 = *attrs++) != nullptr)
+ {
+ if (attr1->type.len == oid->oid.len && PORT_Memcmp(attr1->type.data,
+ oid->oid.data,
+ oid->oid.len) == 0)
+ break;
+ }
+
+ if (attr1 == nullptr)
+ return nullptr;
+
+ if (!only)
+ return attr1;
+
+ while ((attr2 = *attrs++) != nullptr)
+ {
+ if (attr2->type.len == oid->oid.len && PORT_Memcmp(attr2->type.data,
+ oid->oid.data,
+ oid->oid.len) == 0)
+ break;
+ }
+
+ if (attr2 != nullptr)
+ return nullptr;
+
+ return attr1;
+}
+
+/// Same as SEC_StringToOID(), which is private to us.
+SECStatus StringToOID(SECItem* to, const char* from, PRUint32 len)
+{
+ PRUint32 decimal_numbers = 0;
+ PRUint32 result_bytes = 0;
+ SECStatus rv;
+ PRUint8 result[1024];
+
+ static const PRUint32 max_decimal = 0xffffffff / 10;
+ static const char OIDstring[] = {"OID."};
+
+ if (!from || !to)
+ {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return SECFailure;
+ }
+ if (!len)
+ {
+ len = PL_strlen(from);
+ }
+ if (len >= 4 && !PL_strncasecmp(from, OIDstring, 4))
+ {
+ from += 4; /* skip leading "OID." if present */
+ len -= 4;
+ }
+ if (!len)
+ {
+bad_data:
+ PORT_SetError(SEC_ERROR_BAD_DATA);
+ return SECFailure;
+ }
+ do
+ {
+ PRUint32 decimal = 0;
+ while (len > 0 && rtl::isAsciiDigit(static_cast<unsigned char>(*from)))
+ {
+ PRUint32 addend = *from++ - '0';
+ --len;
+ if (decimal > max_decimal) /* overflow */
+ goto bad_data;
+ decimal = (decimal * 10) + addend;
+ if (decimal < addend) /* overflow */
+ goto bad_data;
+ }
+ if (len != 0 && *from != '.')
+ {
+ goto bad_data;
+ }
+ if (decimal_numbers == 0)
+ {
+ if (decimal > 2)
+ goto bad_data;
+ result[0] = decimal * 40;
+ result_bytes = 1;
+ }
+ else if (decimal_numbers == 1)
+ {
+ if (decimal > 40)
+ goto bad_data;
+ result[0] += decimal;
+ }
+ else
+ {
+ /* encode the decimal number, */
+ PRUint8* rp;
+ PRUint32 num_bytes = 0;
+ PRUint32 tmp = decimal;
+ while (tmp)
+ {
+ num_bytes++;
+ tmp >>= 7;
+ }
+ if (!num_bytes)
+ ++num_bytes; /* use one byte for a zero value */
+ if (static_cast<size_t>(num_bytes) + result_bytes > sizeof result)
+ goto bad_data;
+ tmp = num_bytes;
+ rp = result + result_bytes - 1;
+ rp[tmp] = static_cast<PRUint8>(decimal & 0x7f);
+ decimal >>= 7;
+ while (--tmp > 0)
+ {
+ rp[tmp] = static_cast<PRUint8>(decimal | 0x80);
+ decimal >>= 7;
+ }
+ result_bytes += num_bytes;
+ }
+ ++decimal_numbers;
+ if (len > 0) /* skip trailing '.' */
+ {
+ ++from;
+ --len;
+ }
+ }
+ while (len > 0);
+ /* now result contains result_bytes of data */
+ if (to->data && to->len >= result_bytes)
+ {
+ to->len = result_bytes;
+ PORT_Memcpy(to->data, result, to->len);
+ rv = SECSuccess;
+ }
+ else
+ {
+ SECItem result_item = {siBuffer, nullptr, 0 };
+ result_item.data = result;
+ result_item.len = result_bytes;
+ rv = SECITEM_CopyItem(nullptr, to, &result_item);
+ }
+ return rv;
+}
+
+#elif USE_CRYPTO_MSCAPI // ends USE_CRYPTO_NSS
+
+/// Verifies a non-detached signature using CryptoAPI.
+bool VerifyNonDetachedSignature(const std::vector<unsigned char>& aData, const std::vector<BYTE>& rExpectedHash)
+{
+ HCRYPTPROV hProv = 0;
+ if (!CryptAcquireContextW(&hProv, nullptr, nullptr, PROV_RSA_AES, CRYPT_VERIFYCONTEXT))
+ {
+ SAL_WARN("svl.crypto", "CryptAcquireContext() failed");
+ return false;
+ }
+
+ HCRYPTHASH hHash = 0;
+ if (!CryptCreateHash(hProv, CALG_SHA1, 0, 0, &hHash))
+ {
+ SAL_WARN("svl.crypto", "CryptCreateHash() failed");
+ return false;
+ }
+
+ if (!CryptHashData(hHash, aData.data(), aData.size(), 0))
+ {
+ SAL_WARN("svl.crypto", "CryptHashData() failed");
+ return false;
+ }
+
+ DWORD nActualHash = 0;
+ if (!CryptGetHashParam(hHash, HP_HASHVAL, nullptr, &nActualHash, 0))
+ {
+ SAL_WARN("svl.crypto", "CryptGetHashParam() failed to provide the hash length");
+ return false;
+ }
+
+ std::vector<unsigned char> aActualHash(nActualHash);
+ if (!CryptGetHashParam(hHash, HP_HASHVAL, aActualHash.data(), &nActualHash, 0))
+ {
+ SAL_WARN("svl.crypto", "CryptGetHashParam() failed to provide the hash");
+ return false;
+ }
+
+ CryptDestroyHash(hHash);
+ CryptReleaseContext(hProv, 0);
+
+ return aActualHash.size() == rExpectedHash.size() &&
+ !std::memcmp(aActualHash.data(), rExpectedHash.data(), aActualHash.size());
+}
+
+OUString GetSubjectName(PCCERT_CONTEXT pCertContext)
+{
+ OUString subjectName;
+
+ // Get Subject name size.
+ DWORD dwData = CertGetNameStringW(pCertContext,
+ CERT_NAME_SIMPLE_DISPLAY_TYPE,
+ 0,
+ nullptr,
+ nullptr,
+ 0);
+ if (!dwData)
+ {
+ SAL_WARN("svl.crypto", "ValidateSignature: CertGetNameString failed");
+ return subjectName;
+ }
+
+ // Allocate memory for subject name.
+ LPWSTR szName = static_cast<LPWSTR>(
+ LocalAlloc(LPTR, dwData * sizeof(WCHAR)));
+ if (!szName)
+ {
+ SAL_WARN("svl.crypto", "ValidateSignature: Unable to allocate memory for subject name");
+ return subjectName;
+ }
+
+ // Get subject name.
+ if (!CertGetNameStringW(pCertContext,
+ CERT_NAME_SIMPLE_DISPLAY_TYPE,
+ 0,
+ nullptr,
+ szName,
+ dwData))
+ {
+ LocalFree(szName);
+ SAL_WARN("svl.crypto", "ValidateSignature: CertGetNameString failed");
+ return subjectName;
+ }
+
+ subjectName = o3tl::toU(szName);
+ LocalFree(szName);
+
+ return subjectName;
+}
+#endif // USE_CRYPTO_MSCAPI
+
+#if USE_CRYPTO_NSS
+ void ensureNssInit()
+ {
+ // e.g. tdf#122599 ensure NSS library is initialized for NSS_CMSMessage_CreateFromDER
+ css::uno::Reference<css::xml::crypto::XNSSInitializer>
+ xNSSInitializer = css::xml::crypto::NSSInitializer::create(comphelper::getProcessComponentContext());
+
+ // this calls NSS_Init
+ xNSSInitializer->getDigestContext(css::xml::crypto::DigestID::SHA256,
+ uno::Sequence<beans::NamedValue>());
+ }
+#endif
+} // anonymous namespace
+
+bool Signing::Verify(const std::vector<unsigned char>& aData,
+ const bool bNonDetached,
+ const std::vector<unsigned char>& aSignature,
+ SignatureInformation& rInformation)
+{
+#if USE_CRYPTO_NSS
+ // ensure NSS_Init() is called before using NSS_CMSMessage_CreateFromDER
+ static std::once_flag aInitOnce;
+ std::call_once(aInitOnce, ensureNssInit);
+
+ // Validate the signature.
+ SECItem aSignatureItem;
+ aSignatureItem.data = const_cast<unsigned char*>(aSignature.data());
+ aSignatureItem.len = aSignature.size();
+ NSSCMSMessage* pCMSMessage = NSS_CMSMessage_CreateFromDER(&aSignatureItem,
+ /*cb=*/nullptr,
+ /*cb_arg=*/nullptr,
+ /*pwfn=*/nullptr,
+ /*pwfn_arg=*/nullptr,
+ /*decrypt_key_cb=*/nullptr,
+ /*decrypt_key_cb_arg=*/nullptr);
+ if (!NSS_CMSMessage_IsSigned(pCMSMessage))
+ {
+ SAL_WARN("svl.crypto", "ValidateSignature: message is not signed");
+ return false;
+ }
+
+ NSSCMSContentInfo* pCMSContentInfo = NSS_CMSMessage_ContentLevel(pCMSMessage, 0);
+ if (!pCMSContentInfo)
+ {
+ SAL_WARN("svl.crypto", "ValidateSignature: NSS_CMSMessage_ContentLevel() failed");
+ return false;
+ }
+
+ auto pCMSSignedData = static_cast<NSSCMSSignedData*>(NSS_CMSContentInfo_GetContent(pCMSContentInfo));
+ if (!pCMSSignedData)
+ {
+ SAL_WARN("svl.crypto", "ValidateSignature: NSS_CMSContentInfo_GetContent() failed");
+ return false;
+ }
+
+ // Import certificates from the signed data temporarily, so it'll be
+ // possible to verify the signature, even if we didn't have the certificate
+ // previously.
+ std::vector<CERTCertificate*> aDocumentCertificates;
+ for (size_t i = 0; pCMSSignedData->rawCerts[i]; ++i)
+ aDocumentCertificates.push_back(CERT_NewTempCertificate(CERT_GetDefaultCertDB(), pCMSSignedData->rawCerts[i], nullptr, 0, 0));
+
+ NSSCMSSignerInfo* pCMSSignerInfo = NSS_CMSSignedData_GetSignerInfo(pCMSSignedData, 0);
+ if (!pCMSSignerInfo)
+ {
+ SAL_WARN("svl.crypto", "ValidateSignature: NSS_CMSSignedData_GetSignerInfo() failed");
+ return false;
+ }
+
+ SECItem aAlgorithm = NSS_CMSSignedData_GetDigestAlgs(pCMSSignedData)[0]->algorithm;
+ SECOidTag eOidTag = SECOID_FindOIDTag(&aAlgorithm);
+
+ // Map a sign algorithm to a digest algorithm.
+ // See NSS_CMSUtil_MapSignAlgs(), which is private to us.
+ switch (eOidTag)
+ {
+ case SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION:
+ eOidTag = SEC_OID_SHA1;
+ break;
+ case SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION:
+ eOidTag = SEC_OID_SHA256;
+ break;
+ case SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION:
+ eOidTag = SEC_OID_SHA512;
+ break;
+ default:
+ break;
+ }
+
+ HASH_HashType eHashType = HASH_GetHashTypeByOidTag(eOidTag);
+ HASHContext* pHASHContext = HASH_Create(eHashType);
+ if (!pHASHContext)
+ {
+ SAL_WARN("svl.crypto", "ValidateSignature: HASH_Create() failed");
+ return false;
+ }
+
+ // We have a hash, update it with the byte ranges.
+ HASH_Update(pHASHContext, aData.data(), aData.size());
+
+ // Find out what is the expected length of the hash.
+ unsigned int nMaxResultLen = 0;
+ switch (eOidTag)
+ {
+ case SEC_OID_SHA1:
+ nMaxResultLen = comphelper::SHA1_HASH_LENGTH;
+ rInformation.nDigestID = xml::crypto::DigestID::SHA1;
+ break;
+ case SEC_OID_SHA256:
+ nMaxResultLen = comphelper::SHA256_HASH_LENGTH;
+ rInformation.nDigestID = xml::crypto::DigestID::SHA256;
+ break;
+ case SEC_OID_SHA512:
+ nMaxResultLen = comphelper::SHA512_HASH_LENGTH;
+ rInformation.nDigestID = xml::crypto::DigestID::SHA512;
+ break;
+ default:
+ SAL_WARN("svl.crypto", "ValidateSignature: unrecognized algorithm");
+ return false;
+ }
+
+ auto pActualResultBuffer = static_cast<unsigned char*>(PORT_Alloc(nMaxResultLen));
+ unsigned int nActualResultLen;
+ HASH_End(pHASHContext, pActualResultBuffer, &nActualResultLen, nMaxResultLen);
+
+ CERTCertificate* pCertificate = NSS_CMSSignerInfo_GetSigningCertificate(pCMSSignerInfo, CERT_GetDefaultCertDB());
+ if (!pCertificate)
+ {
+ SAL_WARN("svl.crypto", "ValidateSignature: NSS_CMSSignerInfo_GetSigningCertificate() failed");
+ return false;
+ }
+ else
+ {
+ uno::Sequence<sal_Int8> aDerCert(pCertificate->derCert.len);
+ auto aDerCertRange = asNonConstRange(aDerCert);
+ for (size_t i = 0; i < pCertificate->derCert.len; ++i)
+ aDerCertRange[i] = pCertificate->derCert.data[i];
+ OUStringBuffer aBuffer;
+ comphelper::Base64::encode(aBuffer, aDerCert);
+ SignatureInformation::X509Data temp;
+ temp.emplace_back();
+ temp.back().X509Certificate = aBuffer.makeStringAndClear();
+ temp.back().X509Subject = OUString(pCertificate->subjectName, PL_strlen(pCertificate->subjectName), RTL_TEXTENCODING_UTF8);
+ rInformation.X509Datas.clear();
+ rInformation.X509Datas.emplace_back(temp);
+ }
+
+ PRTime nSigningTime;
+ // This may fail, in which case the date should be taken from the PDF's dictionary's "M" key,
+ // so not critical for PDF at least.
+ if (NSS_CMSSignerInfo_GetSigningTime(pCMSSignerInfo, &nSigningTime) == SECSuccess)
+ {
+ // First convert the UTC UNIX timestamp to a tools::DateTime.
+ // nSigningTime is in microseconds.
+ DateTime aDateTime = DateTime::CreateFromUnixTime(static_cast<double>(nSigningTime) / 1000000);
+
+ // Then convert to a local UNO DateTime.
+ aDateTime.ConvertToLocalTime();
+ rInformation.stDateTime = aDateTime.GetUNODateTime();
+ if (rInformation.ouDateTime.isEmpty())
+ {
+ OUStringBuffer rBuffer;
+ rBuffer.append(static_cast<sal_Int32>(aDateTime.GetYear()));
+ rBuffer.append('-');
+ if (aDateTime.GetMonth() < 10)
+ rBuffer.append('0');
+ rBuffer.append(static_cast<sal_Int32>(aDateTime.GetMonth()));
+ rBuffer.append('-');
+ if (aDateTime.GetDay() < 10)
+ rBuffer.append('0');
+ rBuffer.append(static_cast<sal_Int32>(aDateTime.GetDay()));
+ rInformation.ouDateTime = rBuffer.makeStringAndClear();
+ }
+ }
+
+ // Check if we have a signing certificate attribute.
+ SECOidData aOidData;
+ aOidData.oid.data = nullptr;
+ /*
+ * id-aa-signingCertificateV2 OBJECT IDENTIFIER ::=
+ * { iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs9(9)
+ * smime(16) id-aa(2) 47 }
+ */
+ if (StringToOID(&aOidData.oid, "1.2.840.113549.1.9.16.2.47", 0) != SECSuccess)
+ {
+ SAL_WARN("svl.crypto", "StringToOID() failed");
+ return false;
+ }
+ aOidData.offset = SEC_OID_UNKNOWN;
+ aOidData.desc = "id-aa-signingCertificateV2";
+ aOidData.mechanism = CKM_SHA_1;
+ aOidData.supportedExtension = UNSUPPORTED_CERT_EXTENSION;
+ NSSCMSAttribute* pAttribute = CMSAttributeArray_FindAttrByOidData(pCMSSignerInfo->authAttr, &aOidData, PR_TRUE);
+ if (pAttribute)
+ rInformation.bHasSigningCertificate = true;
+
+ SECItem* pContentInfoContentData = pCMSSignedData->contentInfo.content.data;
+ if (bNonDetached && pContentInfoContentData && pContentInfoContentData->data)
+ {
+ // Not a detached signature.
+ if (!std::memcmp(pActualResultBuffer, pContentInfoContentData->data, nMaxResultLen) && nActualResultLen == pContentInfoContentData->len)
+ rInformation.nStatus = xml::crypto::SecurityOperationStatus_OPERATION_SUCCEEDED;
+ }
+ else
+ {
+ // Detached, the usual case.
+ SECItem aActualResultItem;
+ aActualResultItem.data = pActualResultBuffer;
+ aActualResultItem.len = nActualResultLen;
+ if (NSS_CMSSignerInfo_Verify(pCMSSignerInfo, &aActualResultItem, nullptr) == SECSuccess)
+ rInformation.nStatus = xml::crypto::SecurityOperationStatus_OPERATION_SUCCEEDED;
+ }
+
+ // Everything went fine
+ SECITEM_FreeItem(&aOidData.oid, false);
+ PORT_Free(pActualResultBuffer);
+ HASH_Destroy(pHASHContext);
+ NSS_CMSSignerInfo_Destroy(pCMSSignerInfo);
+ for (auto pDocumentCertificate : aDocumentCertificates)
+ CERT_DestroyCertificate(pDocumentCertificate);
+
+ return true;
+
+#elif USE_CRYPTO_MSCAPI // ends USE_CRYPTO_NSS
+
+ // Open a message for decoding.
+ HCRYPTMSG hMsg = CryptMsgOpenToDecode(PKCS_7_ASN_ENCODING | X509_ASN_ENCODING,
+ CMSG_DETACHED_FLAG,
+ 0,
+ 0,
+ nullptr,
+ nullptr);
+ if (!hMsg)
+ {
+ SAL_WARN("svl.crypto", "ValidateSignature: CryptMsgOpenToDecode() failed");
+ return false;
+ }
+
+ // Update the message with the encoded header blob.
+ if (!CryptMsgUpdate(hMsg, aSignature.data(), aSignature.size(), TRUE))
+ {
+ SAL_WARN("svl.crypto", "ValidateSignature, CryptMsgUpdate() for the header failed: " << WindowsErrorString(GetLastError()));
+ return false;
+ }
+
+ // Update the message with the content blob.
+ if (!CryptMsgUpdate(hMsg, aData.data(), aData.size(), FALSE))
+ {
+ SAL_WARN("svl.crypto", "ValidateSignature, CryptMsgUpdate() for the content failed: " << WindowsErrorString(GetLastError()));
+ return false;
+ }
+
+ if (!CryptMsgUpdate(hMsg, nullptr, 0, TRUE))
+ {
+ SAL_WARN("svl.crypto", "ValidateSignature, CryptMsgUpdate() for the last content failed: " << WindowsErrorString(GetLastError()));
+ return false;
+ }
+
+ // Get the CRYPT_ALGORITHM_IDENTIFIER from the message.
+ DWORD nDigestID = 0;
+ if (!CryptMsgGetParam(hMsg, CMSG_SIGNER_HASH_ALGORITHM_PARAM, 0, nullptr, &nDigestID))
+ {
+ SAL_WARN("svl.crypto", "ValidateSignature: CryptMsgGetParam() failed: " << WindowsErrorString(GetLastError()));
+ return false;
+ }
+ std::unique_ptr<BYTE[]> pDigestBytes(new BYTE[nDigestID]);
+ if (!CryptMsgGetParam(hMsg, CMSG_SIGNER_HASH_ALGORITHM_PARAM, 0, pDigestBytes.get(), &nDigestID))
+ {
+ SAL_WARN("svl.crypto", "ValidateSignature: CryptMsgGetParam() failed: " << WindowsErrorString(GetLastError()));
+ return false;
+ }
+ auto pDigestID = reinterpret_cast<CRYPT_ALGORITHM_IDENTIFIER*>(pDigestBytes.get());
+ if (std::string_view(szOID_NIST_sha256) == pDigestID->pszObjId)
+ rInformation.nDigestID = xml::crypto::DigestID::SHA256;
+ else if (std::string_view(szOID_RSA_SHA1RSA) == pDigestID->pszObjId || std::string_view(szOID_OIWSEC_sha1) == pDigestID->pszObjId)
+ rInformation.nDigestID = xml::crypto::DigestID::SHA1;
+ else
+ // Don't error out here, we can still verify the message digest correctly, just the digest ID won't be set.
+ SAL_WARN("svl.crypto", "ValidateSignature: unhandled algorithm identifier '"<<pDigestID->pszObjId<<"'");
+
+ // Get the signer CERT_INFO from the message.
+ DWORD nSignerCertInfo = 0;
+ if (!CryptMsgGetParam(hMsg, CMSG_SIGNER_CERT_INFO_PARAM, 0, nullptr, &nSignerCertInfo))
+ {
+ SAL_WARN("svl.crypto", "ValidateSignature: CryptMsgGetParam() failed");
+ return false;
+ }
+ std::unique_ptr<BYTE[]> pSignerCertInfoBuf(new BYTE[nSignerCertInfo]);
+ if (!CryptMsgGetParam(hMsg, CMSG_SIGNER_CERT_INFO_PARAM, 0, pSignerCertInfoBuf.get(), &nSignerCertInfo))
+ {
+ SAL_WARN("svl.crypto", "ValidateSignature: CryptMsgGetParam() failed");
+ return false;
+ }
+ PCERT_INFO pSignerCertInfo = reinterpret_cast<PCERT_INFO>(pSignerCertInfoBuf.get());
+
+ // Open a certificate store in memory using CERT_STORE_PROV_MSG, which
+ // initializes it with the certificates from the message.
+ HCERTSTORE hStoreHandle = CertOpenStore(CERT_STORE_PROV_MSG,
+ PKCS_7_ASN_ENCODING | X509_ASN_ENCODING,
+ 0,
+ 0,
+ hMsg);
+ if (!hStoreHandle)
+ {
+ SAL_WARN("svl.crypto", "ValidateSignature: CertOpenStore() failed");
+ return false;
+ }
+
+ // Find the signer's certificate in the store.
+ PCCERT_CONTEXT pSignerCertContext = CertGetSubjectCertificateFromStore(hStoreHandle,
+ PKCS_7_ASN_ENCODING | X509_ASN_ENCODING,
+ pSignerCertInfo);
+ if (!pSignerCertContext)
+ {
+ SAL_WARN("svl.crypto", "ValidateSignature: CertGetSubjectCertificateFromStore() failed");
+ return false;
+ }
+ else
+ {
+ // Write rInformation.ouX509Certificate.
+ uno::Sequence<sal_Int8> aDerCert(pSignerCertContext->cbCertEncoded);
+ std::copy_n(pSignerCertContext->pbCertEncoded, pSignerCertContext->cbCertEncoded,
+ aDerCert.getArray());
+ OUStringBuffer aBuffer;
+ comphelper::Base64::encode(aBuffer, aDerCert);
+ SignatureInformation::X509Data temp;
+ temp.emplace_back();
+ temp.back().X509Certificate = aBuffer.makeStringAndClear();
+ temp.back().X509Subject = GetSubjectName(pSignerCertContext);
+ rInformation.X509Datas.clear();
+ rInformation.X509Datas.emplace_back(temp);
+ }
+
+ if (bNonDetached)
+ {
+ // Not a detached signature.
+ DWORD nContentParam = 0;
+ if (!CryptMsgGetParam(hMsg, CMSG_CONTENT_PARAM, 0, nullptr, &nContentParam))
+ {
+ SAL_WARN("svl.crypto", "ValidateSignature: CryptMsgGetParam() failed");
+ return false;
+ }
+
+ std::vector<BYTE> aContentParam(nContentParam);
+ if (!CryptMsgGetParam(hMsg, CMSG_CONTENT_PARAM, 0, aContentParam.data(), &nContentParam))
+ {
+ SAL_WARN("svl.crypto", "ValidateSignature: CryptMsgGetParam() failed");
+ return false;
+ }
+
+ if (VerifyNonDetachedSignature(aData, aContentParam))
+ rInformation.nStatus = xml::crypto::SecurityOperationStatus_OPERATION_SUCCEEDED;
+ }
+ else
+ {
+ // Detached, the usual case.
+ // Use the CERT_INFO from the signer certificate to verify the signature.
+ if (CryptMsgControl(hMsg, 0, CMSG_CTRL_VERIFY_SIGNATURE, pSignerCertContext->pCertInfo))
+ rInformation.nStatus = xml::crypto::SecurityOperationStatus_OPERATION_SUCCEEDED;
+ }
+
+ // Check if we have a signing certificate attribute.
+ DWORD nSignedAttributes = 0;
+ if (CryptMsgGetParam(hMsg, CMSG_SIGNER_AUTH_ATTR_PARAM, 0, nullptr, &nSignedAttributes))
+ {
+ std::unique_ptr<BYTE[]> pSignedAttributesBuf(new BYTE[nSignedAttributes]);
+ if (!CryptMsgGetParam(hMsg, CMSG_SIGNER_AUTH_ATTR_PARAM, 0, pSignedAttributesBuf.get(), &nSignedAttributes))
+ {
+ SAL_WARN("svl.crypto", "ValidateSignature: CryptMsgGetParam() authenticated failed");
+ return false;
+ }
+ auto pSignedAttributes = reinterpret_cast<PCRYPT_ATTRIBUTES>(pSignedAttributesBuf.get());
+ for (size_t nAttr = 0; nAttr < pSignedAttributes->cAttr; ++nAttr)
+ {
+ CRYPT_ATTRIBUTE& rAttr = pSignedAttributes->rgAttr[nAttr];
+ /*
+ * id-aa-signingCertificateV2 OBJECT IDENTIFIER ::=
+ * { iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs9(9)
+ * smime(16) id-aa(2) 47 }
+ */
+ if (std::string_view("1.2.840.113549.1.9.16.2.47") == rAttr.pszObjId)
+ {
+ rInformation.bHasSigningCertificate = true;
+ break;
+ }
+ }
+ }
+
+ // Get the unauthorized attributes.
+ nSignedAttributes = 0;
+ if (CryptMsgGetParam(hMsg, CMSG_SIGNER_UNAUTH_ATTR_PARAM, 0, nullptr, &nSignedAttributes))
+ {
+ std::unique_ptr<BYTE[]> pSignedAttributesBuf(new BYTE[nSignedAttributes]);
+ if (!CryptMsgGetParam(hMsg, CMSG_SIGNER_UNAUTH_ATTR_PARAM, 0, pSignedAttributesBuf.get(), &nSignedAttributes))
+ {
+ SAL_WARN("svl.crypto", "ValidateSignature: CryptMsgGetParam() unauthenticated failed");
+ return false;
+ }
+ auto pSignedAttributes = reinterpret_cast<PCRYPT_ATTRIBUTES>(pSignedAttributesBuf.get());
+ for (size_t nAttr = 0; nAttr < pSignedAttributes->cAttr; ++nAttr)
+ {
+ CRYPT_ATTRIBUTE& rAttr = pSignedAttributes->rgAttr[nAttr];
+ // Timestamp blob
+ if (std::string_view("1.2.840.113549.1.9.16.2.14") == rAttr.pszObjId)
+ {
+ PCRYPT_TIMESTAMP_CONTEXT pTsContext;
+ if (!CryptVerifyTimeStampSignature(rAttr.rgValue->pbData, rAttr.rgValue->cbData, nullptr, 0, nullptr, &pTsContext, nullptr, nullptr))
+ {
+ SAL_WARN("svl.crypto", "CryptMsgUpdate failed: " << WindowsErrorString(GetLastError()));
+ break;
+ }
+
+ DateTime aDateTime = DateTime::CreateFromWin32FileDateTime(pTsContext->pTimeStamp->ftTime.dwLowDateTime, pTsContext->pTimeStamp->ftTime.dwHighDateTime);
+
+ // Then convert to a local UNO DateTime.
+ aDateTime.ConvertToLocalTime();
+ rInformation.stDateTime = aDateTime.GetUNODateTime();
+ if (rInformation.ouDateTime.isEmpty())
+ {
+ OUStringBuffer rBuffer;
+ rBuffer.append(static_cast<sal_Int32>(aDateTime.GetYear()));
+ rBuffer.append('-');
+ if (aDateTime.GetMonth() < 10)
+ rBuffer.append('0');
+ rBuffer.append(static_cast<sal_Int32>(aDateTime.GetMonth()));
+ rBuffer.append('-');
+ if (aDateTime.GetDay() < 10)
+ rBuffer.append('0');
+ rBuffer.append(static_cast<sal_Int32>(aDateTime.GetDay()));
+ rInformation.ouDateTime = rBuffer.makeStringAndClear();
+ }
+ break;
+ }
+ }
+ }
+
+ CertCloseStore(hStoreHandle, CERT_CLOSE_STORE_FORCE_FLAG);
+ CryptMsgClose(hMsg);
+ return true;
+#else
+ // Not implemented.
+ (void)aData;
+ (void)bNonDetached;
+ (void)aSignature;
+ (void)rInformation;
+ return false;
+#endif
+}
+
+bool Signing::Verify(SvStream& rStream,
+ const std::vector<std::pair<size_t, size_t>>& aByteRanges,
+ const bool bNonDetached,
+ const std::vector<unsigned char>& aSignature,
+ SignatureInformation& rInformation)
+{
+#if USE_CRYPTO_ANY
+ std::vector<unsigned char> buffer;
+
+ // Copy the byte ranges into a single buffer.
+ for (const auto& rByteRange : aByteRanges)
+ {
+ rStream.Seek(rByteRange.first);
+ const size_t size = buffer.size();
+ buffer.resize(size + rByteRange.second);
+ rStream.ReadBytes(buffer.data() + size, rByteRange.second);
+ }
+
+ return Verify(buffer, bNonDetached, aSignature, rInformation);
+
+#else
+ // Not implemented.
+ (void)rStream;
+ (void)aByteRanges;
+ (void)bNonDetached;
+ (void)aSignature;
+ (void)rInformation;
+ return false;
+#endif
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/filepicker/pickerhistory.cxx b/svl/source/filepicker/pickerhistory.cxx
new file mode 100644
index 0000000000..4f15847607
--- /dev/null
+++ b/svl/source/filepicker/pickerhistory.cxx
@@ -0,0 +1,72 @@
+/* -*- 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 <svl/pickerhistoryaccess.hxx>
+#include <cppuhelper/weakref.hxx>
+#include <vector>
+
+namespace svt
+{
+ using namespace ::com::sun::star::uno;
+
+ namespace
+ {
+ typedef ::std::vector< css::uno::WeakReference< XInterface > > InterfaceArray;
+
+
+ InterfaceArray& getFolderPickerHistory()
+ {
+ static InterfaceArray s_aHistory;
+ return s_aHistory;
+ }
+
+
+ InterfaceArray& getFilePickerHistory()
+ {
+ static InterfaceArray s_aHistory;
+ return s_aHistory;
+ }
+
+
+ void implPushBackPicker( InterfaceArray& _rHistory, const Reference< XInterface >& _rxPicker )
+ {
+ if ( !_rxPicker.is() )
+ return;
+
+ // first, check which of the objects we hold in s_aHistory can be removed
+ std::erase_if(_rHistory, [](const css::uno::WeakReference< XInterface > & x) { return !x.get().is(); });
+
+ // then push_back the picker
+ _rHistory.emplace_back( _rxPicker );
+ }
+ }
+
+ void addFolderPicker( const Reference< XInterface >& _rxPicker )
+ {
+ implPushBackPicker( getFolderPickerHistory(), _rxPicker );
+ }
+
+ void addFilePicker( const Reference< XInterface >& _rxPicker )
+ {
+ implPushBackPicker( getFilePickerHistory(), _rxPicker );
+ }
+
+} // namespace svt
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/fsstor/fsfactory.cxx b/svl/source/fsstor/fsfactory.cxx
new file mode 100644
index 0000000000..ed88fe789b
--- /dev/null
+++ b/svl/source/fsstor/fsfactory.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 <fsfactory.hxx>
+#include <com/sun/star/embed/ElementModes.hpp>
+#include <com/sun/star/io/IOException.hpp>
+#include <comphelper/processfactory.hxx>
+#include <cppuhelper/supportsservice.hxx>
+#include <cppuhelper/weak.hxx>
+#include <ucbhelper/content.hxx>
+
+#include <unotools/tempfile.hxx>
+#include <unotools/ucbhelper.hxx>
+
+#include "fsstorage.hxx"
+
+
+using namespace ::com::sun::star;
+
+
+uno::Reference< uno::XInterface > SAL_CALL FSStorageFactory::createInstance()
+{
+ OUString aTempURL = ::utl::CreateTempURL( nullptr, true );
+ if ( aTempURL.isEmpty() )
+ throw uno::RuntimeException("Cannot create tempfile.");
+
+ ::ucbhelper::Content aResultContent(
+ aTempURL, uno::Reference< ucb::XCommandEnvironment >(),
+ comphelper::getProcessComponentContext() );
+
+ return cppu::getXWeak(
+ new FSStorage( aResultContent,
+ embed::ElementModes::READWRITE,
+ m_xContext ) );
+}
+
+/**
+ * The request for storage can be done with up to three arguments.
+ * The first argument specifies a source for the storage it must be URL.
+ * The second value is a mode the storage should be open in.
+ * The third value is a media descriptor.
+ */
+uno::Reference< uno::XInterface > SAL_CALL FSStorageFactory::createInstanceWithArguments(
+ const uno::Sequence< uno::Any >& aArguments )
+{
+ sal_Int32 nArgNum = aArguments.getLength();
+ OSL_ENSURE( nArgNum < 4, "Wrong parameter number" );
+
+ if ( !nArgNum )
+ return createInstance();
+
+ // first try to retrieve storage open mode if any
+ // by default the storage will be open in readonly mode
+ sal_Int32 nStorageMode = embed::ElementModes::READ;
+ if ( nArgNum >= 2 )
+ {
+ if( !( aArguments[1] >>= nStorageMode ) )
+ {
+ throw lang::IllegalArgumentException(
+ ("second argument to css.embed.FileSystemStorageFactory."
+ "createInstanceWithArguments must be a"
+ " css.embed.ElementModes"),
+ getXWeak(), -1);
+ }
+ // it's always possible to read written storage in this implementation
+ nStorageMode |= embed::ElementModes::READ;
+ }
+
+ // retrieve storage source URL
+ OUString aURL;
+
+ if ( !( aArguments[0] >>= aURL ) || aURL.isEmpty() )
+ {
+ throw lang::IllegalArgumentException(
+ ("first argument to"
+ " css.embed.FileSystemStorageFactory.createInstanceWithArguments"
+ " must be a (non-empty) URL"),
+ getXWeak(), -1);
+ }
+
+ // allow to use other ucp's
+ // if ( !isLocalNotFile_Impl( aURL ) )
+ if ( aURL.startsWithIgnoreAsciiCase("vnd.sun.star.pkg:")
+ || aURL.startsWithIgnoreAsciiCase("vnd.sun.star.zip:")
+ || ::utl::UCBContentHelper::IsDocument( aURL ) )
+ {
+ throw lang::IllegalArgumentException(
+ ("URL \"" + aURL + "\" passed as first argument to"
+ " css.embed.FileSystemStorageFactory.createInstanceWithArguments"
+ " must be a file URL denoting a directory"),
+ getXWeak(), -1);
+ }
+
+ if ( ( nStorageMode & embed::ElementModes::WRITE ) && !( nStorageMode & embed::ElementModes::NOCREATE ) )
+ FSStorage::MakeFolderNoUI( aURL );
+ else if ( !::utl::UCBContentHelper::IsFolder( aURL ) )
+ throw io::IOException(
+ ("URL \"" + aURL + "\" passed to"
+ " css.embed.FileSystemStorageFactory.createInstanceWithArguments"
+ " does not denote an existing directory"),
+ getXWeak());
+
+ ::ucbhelper::Content aResultContent(
+ aURL, uno::Reference< ucb::XCommandEnvironment >(),
+ comphelper::getProcessComponentContext() );
+
+ // create storage based on source
+ return cppu::getXWeak( new FSStorage( aResultContent,
+ nStorageMode,
+ m_xContext ) );
+}
+
+OUString SAL_CALL FSStorageFactory::getImplementationName()
+{
+ return "com.sun.star.comp.embed.FileSystemStorageFactory";
+}
+
+sal_Bool SAL_CALL FSStorageFactory::supportsService( const OUString& ServiceName )
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+uno::Sequence< OUString > SAL_CALL FSStorageFactory::getSupportedServiceNames()
+{
+ return { "com.sun.star.embed.FileSystemStorageFactory",
+ "com.sun.star.comp.embed.FileSystemStorageFactory" };
+}
+
+
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
+svl_FSStorageFactory_get_implementation(
+ css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const&)
+{
+ return cppu::acquire(new FSStorageFactory(context));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/fsstor/fsstorage.component b/svl/source/fsstor/fsstorage.component
new file mode 100644
index 0000000000..7d20474b36
--- /dev/null
+++ b/svl/source/fsstor/fsstorage.component
@@ -0,0 +1,27 @@
+<?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.comp.embed.FileSystemStorageFactory"
+ constructor="svl_FSStorageFactory_get_implementation" single-instance="true">
+ <service name="com.sun.star.comp.embed.FileSystemStorageFactory"/>
+ <service name="com.sun.star.embed.FileSystemStorageFactory"/>
+ </implementation>
+</component>
diff --git a/svl/source/fsstor/fsstorage.cxx b/svl/source/fsstor/fsstorage.cxx
new file mode 100644
index 0000000000..501dd0fd7d
--- /dev/null
+++ b/svl/source/fsstor/fsstorage.cxx
@@ -0,0 +1,1112 @@
+/* -*- 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/embed/ElementModes.hpp>
+#include <com/sun/star/embed/InvalidStorageException.hpp>
+#include <com/sun/star/embed/StorageWrappedTargetException.hpp>
+#include <com/sun/star/embed/XTransactedObject.hpp>
+#include <com/sun/star/packages/NoEncryptionException.hpp>
+#include <com/sun/star/packages/WrongPasswordException.hpp>
+#include <com/sun/star/ucb/NameClash.hpp>
+#include <com/sun/star/ucb/SimpleFileAccess.hpp>
+
+#include <com/sun/star/ucb/InteractiveIOException.hpp>
+#include <com/sun/star/ucb/IOErrorCode.hpp>
+#include <com/sun/star/container/ElementExistException.hpp>
+#include <com/sun/star/lang/XComponent.hpp>
+#include <com/sun/star/lang/WrappedTargetRuntimeException.hpp>
+#include <com/sun/star/io/IOException.hpp>
+#include <com/sun/star/io/XTruncate.hpp>
+#include <com/sun/star/io/TempFile.hpp>
+#include <com/sun/star/sdbc/XResultSet.hpp>
+#include <com/sun/star/sdbc/XRow.hpp>
+
+#include <comphelper/fileurl.hxx>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/storagehelper.hxx>
+#include <cppuhelper/queryinterface.hxx>
+#include <cppuhelper/exc_hlp.hxx>
+
+#include <osl/diagnose.h>
+#include <tools/urlobj.hxx>
+#include <unotools/ucbhelper.hxx>
+#include <unotools/ucbstreamhelper.hxx>
+#include <unotools/streamwrap.hxx>
+#include <unotools/tempfile.hxx>
+#include <ucbhelper/content.hxx>
+
+#include "fsstorage.hxx"
+#include "oinputstreamcontainer.hxx"
+#include "ostreamcontainer.hxx"
+
+using namespace ::com::sun::star;
+
+FSStorage::FSStorage( const ::ucbhelper::Content& aContent,
+ sal_Int32 nMode,
+ uno::Reference< uno::XComponentContext > const & xContext )
+: m_aURL( aContent.getURL() )
+, m_aContent( aContent )
+, m_nMode( nMode )
+, m_xContext( xContext )
+{
+ OSL_ENSURE( !m_aURL.isEmpty(), "The URL must not be empty" );
+ // TODO: use properties
+ if ( !xContext.is() )
+ throw uno::RuntimeException();
+
+ GetContent();
+}
+
+FSStorage::~FSStorage()
+{
+ std::unique_lock aGuard( m_aMutex );
+ osl_atomic_increment(&m_refCount); // to call dispose
+ try {
+ disposeImpl(aGuard);
+ }
+ catch( uno::RuntimeException& )
+ {}
+}
+
+bool FSStorage::MakeFolderNoUI( std::u16string_view rFolder )
+{
+ INetURLObject aURL( rFolder );
+ OUString aTitle = aURL.getName( INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::WithCharset );
+ aURL.removeSegment();
+ ::ucbhelper::Content aParent;
+ ::ucbhelper::Content aResultContent;
+
+ if ( ::ucbhelper::Content::create( aURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ),
+ uno::Reference< ucb::XCommandEnvironment >(),
+ comphelper::getProcessComponentContext(),
+ aParent ) )
+ return ::utl::UCBContentHelper::MakeFolder( aParent, aTitle, aResultContent );
+
+ return false;
+}
+
+ucbhelper::Content& FSStorage::GetContent()
+{
+ std::unique_lock aGuard( m_aMutex );
+ return m_aContent;
+}
+
+void FSStorage::CopyStreamToSubStream( const OUString& aSourceURL,
+ const uno::Reference< embed::XStorage >& xDest,
+ const OUString& aNewEntryName )
+{
+ if ( !xDest.is() )
+ throw uno::RuntimeException();
+
+ uno::Reference< ucb::XCommandEnvironment > xDummyEnv;
+ ::ucbhelper::Content aSourceContent( aSourceURL, xDummyEnv, comphelper::getProcessComponentContext() );
+ uno::Reference< io::XInputStream > xSourceInput = aSourceContent.openStream();
+
+ if ( !xSourceInput.is() )
+ throw io::IOException(); // TODO: error handling
+
+ uno::Reference< io::XStream > xSubStream = xDest->openStreamElement(
+ aNewEntryName,
+ embed::ElementModes::READWRITE | embed::ElementModes::TRUNCATE );
+ if ( !xSubStream.is() )
+ throw uno::RuntimeException();
+
+ uno::Reference< io::XOutputStream > xDestOutput = xSubStream->getOutputStream();
+ if ( !xDestOutput.is() )
+ throw uno::RuntimeException();
+
+ ::comphelper::OStorageHelper::CopyInputToOutput( xSourceInput, xDestOutput );
+ xDestOutput->closeOutput();
+}
+
+void FSStorage::CopyContentToStorage_Impl(ucbhelper::Content& rContent,
+ const uno::Reference<embed::XStorage>& xDest)
+{
+ // get list of contents of the Content
+ // create cursor for access to children
+ uno::Sequence< OUString > aProps( 2 );
+ OUString* pProps = aProps.getArray();
+ pProps[0] = "TargetURL";
+ pProps[1] = "IsFolder";
+
+ try
+ {
+ uno::Reference<sdbc::XResultSet> xResultSet
+ = rContent.createCursor(aProps, ::ucbhelper::INCLUDE_FOLDERS_AND_DOCUMENTS);
+ uno::Reference< sdbc::XRow > xRow( xResultSet, uno::UNO_QUERY );
+ if ( xResultSet.is() )
+ {
+ // go through the list: insert files as streams, insert folders as substorages using recursion
+ while ( xResultSet->next() )
+ {
+ OUString aSourceURL( xRow->getString( 1 ) );
+ bool bIsFolder( xRow->getBoolean(2) );
+
+ // TODO/LATER: not sure whether the entry name must be encoded
+ OUString aNewEntryName( INetURLObject( aSourceURL ).getName( INetURLObject::LAST_SEGMENT,
+ true,
+ INetURLObject::DecodeMechanism::NONE ) );
+ if ( bIsFolder )
+ {
+ uno::Reference< embed::XStorage > xSubStorage = xDest->openStorageElement( aNewEntryName,
+ embed::ElementModes::READWRITE );
+ if ( !xSubStorage.is() )
+ throw uno::RuntimeException();
+
+ uno::Reference< ucb::XCommandEnvironment > xDummyEnv;
+ ::ucbhelper::Content aSourceContent( aSourceURL, xDummyEnv, comphelper::getProcessComponentContext() );
+ CopyContentToStorage_Impl( aSourceContent, xSubStorage );
+ }
+ else
+ {
+ CopyStreamToSubStream( aSourceURL, xDest, aNewEntryName );
+ }
+ }
+ }
+
+ uno::Reference< embed::XTransactedObject > xTransact( xDest, uno::UNO_QUERY );
+ if ( xTransact.is() )
+ xTransact->commit();
+ }
+ catch( ucb::InteractiveIOException& r )
+ {
+ if ( r.Code == ucb::IOErrorCode_NOT_EXISTING )
+ OSL_FAIL( "The folder does not exist!" );
+ else
+ throw;
+ }
+}
+
+// XInterface
+
+uno::Any SAL_CALL FSStorage::queryInterface( const uno::Type& rType )
+{
+ uno::Any aReturn = ::cppu::queryInterface
+ ( rType
+ , static_cast<lang::XTypeProvider*> ( this )
+ , static_cast<embed::XStorage*> ( this )
+ , static_cast<embed::XHierarchicalStorageAccess*> ( this )
+ , static_cast<container::XNameAccess*> ( this )
+ , static_cast<container::XElementAccess*> ( this )
+ , static_cast<lang::XComponent*> ( this )
+ , static_cast<beans::XPropertySet*> ( this ) );
+
+ if ( aReturn.hasValue() )
+ return aReturn ;
+
+ return OWeakObject::queryInterface( rType );
+}
+
+void SAL_CALL FSStorage::acquire() noexcept
+{
+ OWeakObject::acquire();
+}
+
+void SAL_CALL FSStorage::release() noexcept
+{
+ OWeakObject::release();
+}
+
+// XTypeProvider
+
+uno::Sequence< uno::Type > SAL_CALL FSStorage::getTypes()
+{
+ static const uno::Sequence<uno::Type> aTypes {
+ cppu::UnoType<lang::XTypeProvider>::get(),
+ cppu::UnoType<embed::XStorage>::get(),
+ cppu::UnoType<embed::XHierarchicalStorageAccess>::get(),
+ cppu::UnoType<beans::XPropertySet>::get() };
+ return aTypes;
+}
+
+uno::Sequence< sal_Int8 > SAL_CALL FSStorage::getImplementationId()
+{
+ return css::uno::Sequence<sal_Int8>();
+}
+
+// XStorage
+
+void SAL_CALL FSStorage::copyToStorage( const uno::Reference< embed::XStorage >& xDest )
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ if ( !xDest.is() || xDest == getXWeak() )
+ throw lang::IllegalArgumentException(); // TODO:
+
+ try
+ {
+ CopyContentToStorage_Impl( m_aContent, xDest );
+ }
+ catch( embed::InvalidStorageException& )
+ {
+ throw;
+ }
+ catch( lang::IllegalArgumentException& )
+ {
+ throw;
+ }
+ catch( embed::StorageWrappedTargetException& )
+ {
+ throw;
+ }
+ catch( io::IOException& )
+ {
+ throw;
+ }
+ catch( uno::RuntimeException& )
+ {
+ throw;
+ }
+ catch( uno::Exception& )
+ {
+ uno::Any aCaught( ::cppu::getCaughtException() );
+ throw embed::StorageWrappedTargetException("Can't copy raw stream",
+ uno::Reference< io::XInputStream >(),
+ aCaught );
+ }
+}
+
+uno::Reference< io::XStream > SAL_CALL FSStorage::openStreamElement(
+ const OUString& aStreamName, sal_Int32 nOpenMode )
+{
+ std::unique_lock aGuard( m_aMutex );
+ return openStreamElementImpl(aGuard, aStreamName, nOpenMode);
+}
+
+uno::Reference< io::XStream > FSStorage::openStreamElementImpl(
+ std::unique_lock<std::mutex>& /*rGuard*/,
+ std::u16string_view aStreamName, sal_Int32 nOpenMode )
+{
+ // TODO/LATER: may need possibility to create folder if it was removed, since the folder can not be locked
+ INetURLObject aFileURL( m_aURL );
+ aFileURL.Append( aStreamName );
+
+ if ( ::utl::UCBContentHelper::IsFolder( aFileURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ) ) )
+ throw io::IOException();
+
+ if ( ( nOpenMode & embed::ElementModes::NOCREATE )
+ && !::utl::UCBContentHelper::IsDocument( aFileURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ) ) )
+ throw io::IOException(); // TODO:
+
+ uno::Reference< ucb::XCommandEnvironment > xDummyEnv; // TODO: provide InteractionHandler if any
+ uno::Reference< io::XStream > xResult;
+ try
+ {
+ if ( nOpenMode & embed::ElementModes::WRITE )
+ {
+ if ( aFileURL.GetProtocol() == INetProtocol::File )
+ {
+ uno::Reference<ucb::XSimpleFileAccess3> xSimpleFileAccess(
+ ucb::SimpleFileAccess::create( m_xContext ) );
+ xResult = xSimpleFileAccess->openFileReadWrite( aFileURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ) );
+ }
+ else
+ {
+ // TODO: test whether it really works for http and fwp
+ std::unique_ptr<SvStream> pStream = ::utl::UcbStreamHelper::CreateStream( aFileURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ),
+ StreamMode::STD_WRITE );
+ if ( pStream && !pStream->GetError() )
+ xResult.set( new ::utl::OStreamWrapper( std::move(pStream) ) );
+ }
+
+ if ( !xResult.is() )
+ throw io::IOException();
+
+ if ( nOpenMode & embed::ElementModes::TRUNCATE )
+ {
+ uno::Reference< io::XTruncate > xTrunc( xResult->getOutputStream(), uno::UNO_QUERY_THROW );
+ xTrunc->truncate();
+ }
+ }
+ else
+ {
+ if ( ( nOpenMode & embed::ElementModes::TRUNCATE )
+ || !::utl::UCBContentHelper::IsDocument( aFileURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ) ) )
+ throw io::IOException(); // TODO: access denied
+
+ ::ucbhelper::Content aResultContent( aFileURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ), xDummyEnv, comphelper::getProcessComponentContext() );
+ uno::Reference< io::XInputStream > xInStream = aResultContent.openStream();
+ xResult = new OFSInputStreamContainer(xInStream);
+ }
+ }
+ catch( embed::InvalidStorageException& )
+ {
+ throw;
+ }
+ catch( lang::IllegalArgumentException& )
+ {
+ throw;
+ }
+ catch( packages::WrongPasswordException& )
+ {
+ throw;
+ }
+ catch( embed::StorageWrappedTargetException& )
+ {
+ throw;
+ }
+ catch( io::IOException& )
+ {
+ throw;
+ }
+ catch( uno::RuntimeException& )
+ {
+ throw;
+ }
+ catch( uno::Exception& )
+ {
+ uno::Any aCaught( ::cppu::getCaughtException() );
+ throw embed::StorageWrappedTargetException("Can't copy raw stream",
+ uno::Reference< io::XInputStream >(),
+ aCaught );
+ }
+
+ return xResult;
+}
+
+uno::Reference< io::XStream > SAL_CALL FSStorage::openEncryptedStreamElement(
+ const OUString&, sal_Int32, const OUString& )
+{
+ throw packages::NoEncryptionException();
+}
+
+uno::Reference< embed::XStorage > SAL_CALL FSStorage::openStorageElement(
+ const OUString& aStorName, sal_Int32 nStorageMode )
+{
+ std::unique_lock aGuard( m_aMutex );
+ return openStorageElementImpl(aGuard, aStorName, nStorageMode);
+}
+
+uno::Reference< embed::XStorage > FSStorage::openStorageElementImpl(
+ std::unique_lock<std::mutex>& /*rGuard*/,
+ std::u16string_view aStorName, sal_Int32 nStorageMode )
+{
+ if ( ( nStorageMode & embed::ElementModes::WRITE )
+ && !( m_nMode & embed::ElementModes::WRITE ) )
+ throw io::IOException(); // TODO: error handling
+
+ // TODO/LATER: may need possibility to create folder if it was removed, since the folder can not be locked
+ INetURLObject aFolderURL( m_aURL );
+ aFolderURL.Append( aStorName );
+
+ bool bFolderExists = ::utl::UCBContentHelper::IsFolder( aFolderURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ) );
+ if ( !bFolderExists && ::utl::UCBContentHelper::IsDocument( aFolderURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ) ) )
+ throw io::IOException(); // TODO:
+
+ if ( ( nStorageMode & embed::ElementModes::NOCREATE ) && !bFolderExists )
+ throw io::IOException(); // TODO:
+
+ uno::Reference< ucb::XCommandEnvironment > xDummyEnv; // TODO: provide InteractionHandler if any
+ uno::Reference< embed::XStorage > xResult;
+ try
+ {
+ if ( nStorageMode & embed::ElementModes::WRITE )
+ {
+ if ( ( nStorageMode & embed::ElementModes::TRUNCATE ) && bFolderExists )
+ {
+ ::utl::UCBContentHelper::Kill( aFolderURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ) );
+ bFolderExists =
+ MakeFolderNoUI( aFolderURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ) ); // TODO: not atomic :(
+ }
+ else if ( !bFolderExists )
+ {
+ bFolderExists =
+ MakeFolderNoUI( aFolderURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ) ); // TODO: not atomic :(
+ }
+ }
+ else if ( nStorageMode & embed::ElementModes::TRUNCATE )
+ throw io::IOException(); // TODO: access denied
+
+ if ( !bFolderExists )
+ throw io::IOException(); // there is no such folder
+
+ ::ucbhelper::Content aResultContent( aFolderURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ), xDummyEnv, comphelper::getProcessComponentContext() );
+ xResult = new FSStorage( aResultContent, nStorageMode, m_xContext );
+ }
+ catch( embed::InvalidStorageException& )
+ {
+ throw;
+ }
+ catch( lang::IllegalArgumentException& )
+ {
+ throw;
+ }
+ catch( embed::StorageWrappedTargetException& )
+ {
+ throw;
+ }
+ catch( io::IOException& )
+ {
+ throw;
+ }
+ catch( uno::RuntimeException& )
+ {
+ throw;
+ }
+ catch( uno::Exception& )
+ {
+ uno::Any aCaught( ::cppu::getCaughtException() );
+ throw embed::StorageWrappedTargetException("Can't copy raw stream",
+ uno::Reference< io::XInputStream >(),
+ aCaught );
+ }
+
+ return xResult;
+}
+
+uno::Reference< io::XStream > SAL_CALL FSStorage::cloneStreamElement( const OUString& aStreamName )
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ // TODO/LATER: may need possibility to create folder if it was removed, since the folder can not be locked
+ INetURLObject aFileURL( m_aURL );
+ aFileURL.Append( aStreamName );
+
+ uno::Reference < io::XStream > xTempResult;
+ try
+ {
+ uno::Reference< ucb::XCommandEnvironment > xDummyEnv;
+ ::ucbhelper::Content aResultContent( aFileURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ), xDummyEnv, comphelper::getProcessComponentContext() );
+ uno::Reference< io::XInputStream > xInStream = aResultContent.openStream();
+
+ xTempResult = new utl::TempFileFastService;
+ uno::Reference < io::XOutputStream > xTempOut = xTempResult->getOutputStream();
+ uno::Reference < io::XInputStream > xTempIn = xTempResult->getInputStream();
+
+ ::comphelper::OStorageHelper::CopyInputToOutput( xInStream, xTempOut );
+ xTempOut->closeOutput();
+ }
+ catch( embed::InvalidStorageException& )
+ {
+ throw;
+ }
+ catch( lang::IllegalArgumentException& )
+ {
+ throw;
+ }
+ catch( packages::WrongPasswordException& )
+ {
+ throw;
+ }
+ catch( io::IOException& )
+ {
+ throw;
+ }
+ catch( embed::StorageWrappedTargetException& )
+ {
+ throw;
+ }
+ catch( uno::RuntimeException& )
+ {
+ throw;
+ }
+ catch( uno::Exception& )
+ {
+ uno::Any aCaught( ::cppu::getCaughtException() );
+ throw embed::StorageWrappedTargetException("Can't copy raw stream",
+ uno::Reference< io::XInputStream >(),
+ aCaught );
+ }
+
+ return xTempResult;
+}
+
+uno::Reference< io::XStream > SAL_CALL FSStorage::cloneEncryptedStreamElement(
+ const OUString&,
+ const OUString& )
+{
+ throw packages::NoEncryptionException();
+}
+
+void SAL_CALL FSStorage::copyLastCommitTo(
+ const uno::Reference< embed::XStorage >& xTargetStorage )
+{
+ copyToStorage( xTargetStorage );
+}
+
+void SAL_CALL FSStorage::copyStorageElementLastCommitTo(
+ const OUString& aStorName,
+ const uno::Reference< embed::XStorage >& xTargetStorage )
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ uno::Reference< embed::XStorage > xSourceStor( openStorageElement( aStorName, embed::ElementModes::READ ),
+ uno::UNO_SET_THROW );
+ xSourceStor->copyToStorage( xTargetStorage );
+}
+
+sal_Bool SAL_CALL FSStorage::isStreamElement( const OUString& aElementName )
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ INetURLObject aURL( m_aURL );
+ aURL.Append( aElementName );
+
+ return !::utl::UCBContentHelper::IsFolder( aURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ) );
+}
+
+sal_Bool SAL_CALL FSStorage::isStorageElement( const OUString& aElementName )
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ INetURLObject aURL( m_aURL );
+ aURL.Append( aElementName );
+
+ return ::utl::UCBContentHelper::IsFolder( aURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ) );
+}
+
+void SAL_CALL FSStorage::removeElement( const OUString& aElementName )
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ INetURLObject aURL( m_aURL );
+ aURL.Append( aElementName );
+
+ if ( !::utl::UCBContentHelper::IsFolder( aURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ) )
+ && !::utl::UCBContentHelper::IsDocument( aURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ) ) )
+ throw container::NoSuchElementException(); // TODO:
+
+ ::utl::UCBContentHelper::Kill( aURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ) );
+}
+
+void SAL_CALL FSStorage::renameElement( const OUString& aElementName, const OUString& aNewName )
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ INetURLObject aOldURL( m_aURL );
+ aOldURL.Append( aElementName );
+
+ INetURLObject aNewURL( m_aURL );
+ aNewURL.Append( aNewName );
+
+ if ( !::utl::UCBContentHelper::IsFolder( aOldURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ) )
+ && !::utl::UCBContentHelper::IsDocument( aOldURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ) ) )
+ throw container::NoSuchElementException(); // TODO:
+
+ if ( ::utl::UCBContentHelper::IsFolder( aNewURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ) )
+ || ::utl::UCBContentHelper::IsDocument( aNewURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ) ) )
+ throw container::ElementExistException(); // TODO:
+
+ try
+ {
+ uno::Reference< ucb::XCommandEnvironment > xDummyEnv;
+ ::ucbhelper::Content aSourceContent( aOldURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ), xDummyEnv, comphelper::getProcessComponentContext() );
+
+ m_aContent.transferContent(aSourceContent, ::ucbhelper::InsertOperation::Move, aNewName,
+ ucb::NameClash::ERROR);
+ }
+ catch( embed::InvalidStorageException& )
+ {
+ throw;
+ }
+ catch( lang::IllegalArgumentException& )
+ {
+ throw;
+ }
+ catch( container::NoSuchElementException& )
+ {
+ throw;
+ }
+ catch( container::ElementExistException& )
+ {
+ throw;
+ }
+ catch( io::IOException& )
+ {
+ throw;
+ }
+ catch( embed::StorageWrappedTargetException& )
+ {
+ throw;
+ }
+ catch( uno::RuntimeException& )
+ {
+ throw;
+ }
+ catch( uno::Exception& )
+ {
+ uno::Any aCaught( ::cppu::getCaughtException() );
+ throw embed::StorageWrappedTargetException("Can't copy raw stream",
+ uno::Reference< io::XInputStream >(),
+ aCaught );
+ }
+}
+
+void SAL_CALL FSStorage::copyElementTo( const OUString& aElementName,
+ const uno::Reference< embed::XStorage >& xDest,
+ const OUString& aNewName )
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ if ( !xDest.is() )
+ throw uno::RuntimeException();
+
+ INetURLObject aOwnURL( m_aURL );
+ aOwnURL.Append( aElementName );
+
+ if ( xDest->hasByName( aNewName ) )
+ throw container::ElementExistException(); // TODO:
+
+ try
+ {
+ uno::Reference< ucb::XCommandEnvironment > xDummyEnv;
+ if ( ::utl::UCBContentHelper::IsFolder( aOwnURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ) ) )
+ {
+ ::ucbhelper::Content aSourceContent( aOwnURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ), xDummyEnv, comphelper::getProcessComponentContext() );
+ uno::Reference< embed::XStorage > xDestSubStor(
+ xDest->openStorageElement( aNewName, embed::ElementModes::READWRITE ),
+ uno::UNO_SET_THROW );
+
+ CopyContentToStorage_Impl( aSourceContent, xDestSubStor );
+ }
+ else if ( ::utl::UCBContentHelper::IsDocument( aOwnURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ) ) )
+ {
+ CopyStreamToSubStream( aOwnURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ), xDest, aNewName );
+ }
+ else
+ throw container::NoSuchElementException(); // TODO:
+ }
+ catch( embed::InvalidStorageException& )
+ {
+ throw;
+ }
+ catch( lang::IllegalArgumentException& )
+ {
+ throw;
+ }
+ catch( container::NoSuchElementException& )
+ {
+ throw;
+ }
+ catch( container::ElementExistException& )
+ {
+ throw;
+ }
+ catch( embed::StorageWrappedTargetException& )
+ {
+ throw;
+ }
+ catch( io::IOException& )
+ {
+ throw;
+ }
+ catch( uno::RuntimeException& )
+ {
+ throw;
+ }
+ catch( uno::Exception& )
+ {
+ uno::Any aCaught( ::cppu::getCaughtException() );
+ throw embed::StorageWrappedTargetException("Can't copy raw stream",
+ uno::Reference< io::XInputStream >(),
+ aCaught );
+ }
+}
+
+void SAL_CALL FSStorage::moveElementTo( const OUString& aElementName,
+ const uno::Reference< embed::XStorage >& xDest,
+ const OUString& aNewName )
+{
+ std::unique_lock aGuard( m_aMutex );
+ copyElementTo( aElementName, xDest, aNewName );
+
+ INetURLObject aOwnURL( m_aURL );
+ aOwnURL.Append( aElementName );
+ if ( !::utl::UCBContentHelper::Kill( aOwnURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ) ) )
+ throw io::IOException(); // TODO: error handling
+}
+
+// XNameAccess
+
+uno::Any SAL_CALL FSStorage::getByName( const OUString& aName )
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ if ( aName.isEmpty() )
+ throw lang::IllegalArgumentException();
+
+ uno::Any aResult;
+ try
+ {
+ INetURLObject aURL( m_aURL );
+ aURL.Append( aName );
+
+
+ if ( ::utl::UCBContentHelper::IsFolder( aURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ) ) )
+ {
+ aResult <<= openStorageElementImpl( aGuard, aName, embed::ElementModes::READ );
+ }
+ else if ( ::utl::UCBContentHelper::IsDocument( aURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ) ) )
+ {
+ aResult <<= openStreamElementImpl( aGuard, aName, embed::ElementModes::READ );
+ }
+ else
+ throw container::NoSuchElementException(); // TODO:
+ }
+ catch (const container::NoSuchElementException&)
+ {
+ throw;
+ }
+ catch (const lang::WrappedTargetException&)
+ {
+ throw;
+ }
+ catch (const uno::RuntimeException&)
+ {
+ throw;
+ }
+ catch (const uno::Exception&)
+ {
+ uno::Any aCaught( ::cppu::getCaughtException() );
+ throw lang::WrappedTargetException( "Can not open element!",
+ getXWeak(),
+ aCaught );
+ }
+
+ return aResult;
+}
+
+
+uno::Sequence< OUString > SAL_CALL FSStorage::getElementNames()
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ uno::Sequence< OUString > aResult;
+
+ try
+ {
+ uno::Sequence<OUString> aProps { "Title" };
+
+ sal_Int32 nSize = 0;
+ uno::Reference<sdbc::XResultSet> xResultSet
+ = m_aContent.createCursor(aProps, ::ucbhelper::INCLUDE_FOLDERS_AND_DOCUMENTS);
+ uno::Reference< sdbc::XRow > xRow( xResultSet, uno::UNO_QUERY );
+ if ( xResultSet.is() )
+ {
+ // go through the list
+ while ( xResultSet->next() )
+ {
+ OUString aName( xRow->getString( 1 ) );
+ aResult.realloc( ++nSize );
+ aResult.getArray()[nSize-1] = aName;
+ }
+ }
+ }
+ catch( const ucb::InteractiveIOException& r )
+ {
+ if ( r.Code == ucb::IOErrorCode_NOT_EXISTING )
+ OSL_FAIL( "The folder does not exist!" );
+ else
+ {
+ uno::Any aCaught( ::cppu::getCaughtException() );
+ throw lang::WrappedTargetRuntimeException( "Can not open storage!",
+ getXWeak(),
+ aCaught );
+ }
+ }
+ catch (const uno::RuntimeException&)
+ {
+ throw;
+ }
+ catch (const uno::Exception&)
+ {
+ uno::Any aCaught( ::cppu::getCaughtException() );
+ throw lang::WrappedTargetRuntimeException( "Can not open storage!",
+ getXWeak(),
+ aCaught );
+ }
+
+ return aResult;
+}
+
+sal_Bool SAL_CALL FSStorage::hasByName( const OUString& aName )
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ if ( aName.isEmpty() )
+ throw lang::IllegalArgumentException();
+
+ INetURLObject aURL( m_aURL );
+ aURL.Append( aName );
+
+ return ( ::utl::UCBContentHelper::IsFolder( aURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ) )
+ || ::utl::UCBContentHelper::IsDocument( aURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ) ) );
+}
+
+uno::Type SAL_CALL FSStorage::getElementType()
+{
+ // it is a multitype container
+ return uno::Type();
+}
+
+sal_Bool SAL_CALL FSStorage::hasElements()
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ try
+ {
+ uno::Sequence<OUString> aProps { "TargetURL" };
+
+ uno::Reference<sdbc::XResultSet> xResultSet
+ = m_aContent.createCursor(aProps, ::ucbhelper::INCLUDE_FOLDERS_AND_DOCUMENTS);
+ return ( xResultSet.is() && xResultSet->next() );
+ }
+ catch (const uno::RuntimeException&)
+ {
+ throw;
+ }
+ catch (const uno::Exception& ex)
+ {
+ css::uno::Any anyEx = cppu::getCaughtException();
+ throw lang::WrappedTargetRuntimeException( ex.Message,
+ nullptr, anyEx );
+ }
+}
+
+// XDisposable
+void SAL_CALL FSStorage::dispose()
+{
+ std::unique_lock aGuard( m_aMutex );
+ disposeImpl(aGuard);
+}
+
+void FSStorage::disposeImpl(std::unique_lock<std::mutex>& rGuard)
+{
+ if ( m_aListenersContainer.getLength(rGuard) )
+ {
+ lang::EventObject aSource( getXWeak() );
+ m_aListenersContainer.disposeAndClear( rGuard, aSource );
+ }
+}
+
+void SAL_CALL FSStorage::addEventListener(
+ const uno::Reference< lang::XEventListener >& xListener )
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ m_aListenersContainer.addInterface( aGuard, xListener );
+}
+
+void SAL_CALL FSStorage::removeEventListener(
+ const uno::Reference< lang::XEventListener >& xListener )
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ m_aListenersContainer.removeInterface( aGuard, xListener );
+}
+
+// XPropertySet
+
+uno::Reference< beans::XPropertySetInfo > SAL_CALL FSStorage::getPropertySetInfo()
+{
+ //TODO:
+ return uno::Reference< beans::XPropertySetInfo >();
+}
+
+
+void SAL_CALL FSStorage::setPropertyValue( const OUString& aPropertyName, const uno::Any& )
+{
+ if ( aPropertyName == "URL" || aPropertyName == "OpenMode" )
+ throw beans::PropertyVetoException(); // TODO
+ else
+ throw beans::UnknownPropertyException(aPropertyName); // TODO
+}
+
+
+uno::Any SAL_CALL FSStorage::getPropertyValue( const OUString& aPropertyName )
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ if ( aPropertyName == "URL" )
+ return uno::Any( m_aURL );
+ else if ( aPropertyName == "OpenMode" )
+ return uno::Any( m_nMode );
+
+ throw beans::UnknownPropertyException(aPropertyName); // TODO
+}
+
+
+void SAL_CALL FSStorage::addPropertyChangeListener(
+ const OUString& /*aPropertyName*/,
+ const uno::Reference< beans::XPropertyChangeListener >& /*xListener*/ )
+{
+ //TODO:
+}
+
+
+void SAL_CALL FSStorage::removePropertyChangeListener(
+ const OUString& /*aPropertyName*/,
+ const uno::Reference< beans::XPropertyChangeListener >& /*aListener*/ )
+{
+ //TODO:
+}
+
+
+void SAL_CALL FSStorage::addVetoableChangeListener(
+ const OUString& /*PropertyName*/,
+ const uno::Reference< beans::XVetoableChangeListener >& /*aListener*/ )
+{
+ //TODO:
+}
+
+
+void SAL_CALL FSStorage::removeVetoableChangeListener(
+ const OUString& /*PropertyName*/,
+ const uno::Reference< beans::XVetoableChangeListener >& /*aListener*/ )
+{
+ //TODO:
+}
+
+// XHierarchicalStorageAccess
+uno::Reference< embed::XExtendedStorageStream > SAL_CALL FSStorage::openStreamElementByHierarchicalName( const OUString& sStreamPath, ::sal_Int32 nOpenMode )
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ if ( sStreamPath.toChar() == '/' )
+ throw lang::IllegalArgumentException();
+
+ INetURLObject aBaseURL( m_aURL );
+ if ( !aBaseURL.setFinalSlash() )
+ throw uno::RuntimeException();
+
+ OUString aFileURL = INetURLObject::GetAbsURL(
+ aBaseURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ),
+ sStreamPath );
+
+ if ( ::utl::UCBContentHelper::IsFolder( aFileURL ) )
+ throw io::IOException();
+
+ if ( ( nOpenMode & embed::ElementModes::NOCREATE )
+ && !::utl::UCBContentHelper::IsDocument( aFileURL ) )
+ throw io::IOException(); // TODO:
+
+ uno::Reference< ucb::XCommandEnvironment > xDummyEnv; // TODO: provide InteractionHandler if any
+ uno::Reference< io::XStream > xResult;
+ try
+ {
+ if ( nOpenMode & embed::ElementModes::WRITE )
+ {
+ if ( comphelper::isFileUrl( aFileURL ) )
+ {
+ uno::Reference<ucb::XSimpleFileAccess3> xSimpleFileAccess(
+ ucb::SimpleFileAccess::create( m_xContext ) );
+ uno::Reference< io::XStream > xStream =
+ xSimpleFileAccess->openFileReadWrite( aFileURL );
+
+ xResult = new OFSStreamContainer(xStream);
+ }
+ else
+ {
+ // TODO: test whether it really works for http and fwp
+ std::unique_ptr<SvStream> pStream = ::utl::UcbStreamHelper::CreateStream( aFileURL,
+ StreamMode::STD_WRITE );
+ if ( pStream && !pStream->GetError() )
+ {
+ uno::Reference< io::XStream > xStream( new ::utl::OStreamWrapper( std::move(pStream) ) );
+ xResult = new OFSStreamContainer(xStream);
+ }
+ }
+
+ if ( !xResult.is() )
+ throw io::IOException();
+
+ if ( nOpenMode & embed::ElementModes::TRUNCATE )
+ {
+ uno::Reference< io::XTruncate > xTrunc( xResult->getOutputStream(), uno::UNO_QUERY_THROW );
+ xTrunc->truncate();
+ }
+ }
+ else
+ {
+ if ( ( nOpenMode & embed::ElementModes::TRUNCATE )
+ || !::utl::UCBContentHelper::IsDocument( aFileURL ) )
+ throw io::IOException(); // TODO: access denied
+
+ ::ucbhelper::Content aResultContent( aFileURL, xDummyEnv, comphelper::getProcessComponentContext() );
+ uno::Reference< io::XInputStream > xInStream = aResultContent.openStream();
+ xResult = new OFSInputStreamContainer(xInStream);
+ }
+ }
+ catch( embed::InvalidStorageException& )
+ {
+ throw;
+ }
+ catch( lang::IllegalArgumentException& )
+ {
+ throw;
+ }
+ catch( packages::WrongPasswordException& )
+ {
+ throw;
+ }
+ catch( embed::StorageWrappedTargetException& )
+ {
+ throw;
+ }
+ catch( io::IOException& )
+ {
+ throw;
+ }
+ catch( uno::RuntimeException& )
+ {
+ throw;
+ }
+ catch( uno::Exception& )
+ {
+ uno::Any aCaught( ::cppu::getCaughtException() );
+ throw embed::StorageWrappedTargetException("Can't copy raw stream",
+ uno::Reference< io::XInputStream >(),
+ aCaught );
+ }
+
+ return uno::Reference< embed::XExtendedStorageStream >( xResult, uno::UNO_QUERY_THROW );
+}
+
+uno::Reference< embed::XExtendedStorageStream > SAL_CALL FSStorage::openEncryptedStreamElementByHierarchicalName( const OUString& /*sStreamName*/, ::sal_Int32 /*nOpenMode*/, const OUString& /*sPassword*/ )
+{
+ throw packages::NoEncryptionException();
+}
+
+void SAL_CALL FSStorage::removeStreamElementByHierarchicalName( const OUString& sStreamPath )
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ // TODO/LATER: may need possibility to create folder if it was removed, since the folder can not be locked
+ INetURLObject aBaseURL( m_aURL );
+ if ( !aBaseURL.setFinalSlash() )
+ throw uno::RuntimeException();
+
+ OUString aFileURL = INetURLObject::GetAbsURL(
+ aBaseURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ),
+ sStreamPath );
+
+ if ( !::utl::UCBContentHelper::IsDocument( aFileURL ) )
+ {
+ if ( ::utl::UCBContentHelper::IsFolder( aFileURL ) )
+ throw lang::IllegalArgumentException();
+ else
+ throw container::NoSuchElementException(); // TODO:
+ }
+
+ if ( !::utl::UCBContentHelper::Kill( aFileURL ) )
+ throw io::IOException(); // TODO: error handling
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/fsstor/fsstorage.hxx b/svl/source/fsstor/fsstorage.hxx
new file mode 100644
index 0000000000..6b02a9a61b
--- /dev/null
+++ b/svl/source/fsstor/fsstorage.hxx
@@ -0,0 +1,187 @@
+/* -*- 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 .
+ */
+
+#ifndef INCLUDED_SVL_SOURCE_FSSTOR_FSSTORAGE_HXX
+#define INCLUDED_SVL_SOURCE_FSSTOR_FSSTORAGE_HXX
+
+#include <com/sun/star/uno/Sequence.hxx>
+#include <com/sun/star/embed/XStorage.hpp>
+#include <com/sun/star/embed/XHierarchicalStorageAccess.hpp>
+#include <com/sun/star/io/XStream.hpp>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/lang/XTypeProvider.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+#include <comphelper/interfacecontainer4.hxx>
+#include <cppuhelper/weak.hxx>
+
+#include <ucbhelper/content.hxx>
+
+class FSStorage final : public css::lang::XTypeProvider
+ , public css::embed::XStorage
+ , public css::embed::XHierarchicalStorageAccess
+ , public css::beans::XPropertySet
+ , public ::cppu::OWeakObject
+{
+ std::mutex m_aMutex;
+ OUString m_aURL;
+ ::ucbhelper::Content m_aContent;
+ sal_Int32 m_nMode;
+ ::comphelper::OInterfaceContainerHelper4<css::lang::XEventListener> m_aListenersContainer; // list of listeners
+ css::uno::Reference< css::uno::XComponentContext > m_xContext;
+
+public:
+
+ FSStorage( const ::ucbhelper::Content& aContent,
+ sal_Int32 nMode,
+ css::uno::Reference< css::uno::XComponentContext > const & xContext );
+
+ virtual ~FSStorage() override;
+
+ ucbhelper::Content& GetContent();
+
+ static void CopyStreamToSubStream( const OUString& aSourceURL,
+ const css::uno::Reference< css::embed::XStorage >& xDest,
+ const OUString& aNewEntryName );
+
+ void CopyContentToStorage_Impl(ucbhelper::Content& rContent,
+ const css::uno::Reference<css::embed::XStorage>& xDest);
+
+ static bool MakeFolderNoUI( std::u16string_view rFolder );
+
+ // XInterface
+
+ virtual css::uno::Any SAL_CALL queryInterface( const css::uno::Type& rType ) override;
+
+ virtual void SAL_CALL acquire() noexcept override;
+
+ virtual void SAL_CALL release() noexcept override;
+
+ // XTypeProvider
+
+ virtual css::uno::Sequence< css::uno::Type > SAL_CALL getTypes() override;
+
+ virtual css::uno::Sequence< sal_Int8 > SAL_CALL getImplementationId() override;
+
+ // XStorage
+
+ virtual void SAL_CALL copyToStorage( const css::uno::Reference< css::embed::XStorage >& xDest ) override;
+
+ virtual css::uno::Reference< css::io::XStream > SAL_CALL openStreamElement(
+ const OUString& aStreamName, sal_Int32 nOpenMode ) override;
+
+ virtual css::uno::Reference< css::io::XStream > SAL_CALL openEncryptedStreamElement(
+ const OUString& aStreamName, sal_Int32 nOpenMode, const OUString& aPass ) override;
+
+ virtual css::uno::Reference< css::embed::XStorage > SAL_CALL openStorageElement(
+ const OUString& aStorName, sal_Int32 nStorageMode ) override;
+
+ virtual css::uno::Reference< css::io::XStream > SAL_CALL cloneStreamElement(
+ const OUString& aStreamName ) override;
+
+ virtual css::uno::Reference< css::io::XStream > SAL_CALL cloneEncryptedStreamElement(
+ const OUString& aStreamName, const OUString& aPass ) override;
+
+ virtual void SAL_CALL copyLastCommitTo(
+ const css::uno::Reference< css::embed::XStorage >& xTargetStorage ) override;
+
+ virtual void SAL_CALL copyStorageElementLastCommitTo(
+ const OUString& aStorName,
+ const css::uno::Reference< css::embed::XStorage >& xTargetStorage ) override;
+
+ virtual sal_Bool SAL_CALL isStreamElement( const OUString& aElementName ) override;
+
+ virtual sal_Bool SAL_CALL isStorageElement( const OUString& aElementName ) override;
+
+ virtual void SAL_CALL removeElement( const OUString& aElementName ) override;
+
+ virtual void SAL_CALL renameElement( const OUString& rEleName, const OUString& rNewName ) override;
+
+ virtual void SAL_CALL copyElementTo( const OUString& aElementName,
+ const css::uno::Reference< css::embed::XStorage >& xDest,
+ const OUString& aNewName ) override;
+
+ virtual void SAL_CALL moveElementTo( const OUString& aElementName,
+ const css::uno::Reference< css::embed::XStorage >& xDest,
+ const OUString& rNewName ) override;
+
+ // XNameAccess
+
+ virtual css::uno::Any SAL_CALL getByName( const OUString& aName ) override;
+
+ virtual css::uno::Sequence< OUString > SAL_CALL getElementNames() override;
+
+ virtual sal_Bool SAL_CALL hasByName( const OUString& aName ) override;
+
+ virtual css::uno::Type SAL_CALL getElementType() override;
+
+ virtual sal_Bool SAL_CALL hasElements() override;
+
+ // XComponent
+
+ virtual void SAL_CALL dispose() override;
+
+ virtual void SAL_CALL addEventListener(
+ const css::uno::Reference< css::lang::XEventListener >& xListener ) override;
+
+ virtual void SAL_CALL removeEventListener(
+ const css::uno::Reference< css::lang::XEventListener >& xListener ) override;
+
+ // XPropertySet
+
+ virtual css::uno::Reference< css::beans::XPropertySetInfo > SAL_CALL getPropertySetInfo() override;
+
+ virtual void SAL_CALL setPropertyValue( const OUString& aPropertyName, const css::uno::Any& aValue ) override;
+
+ virtual css::uno::Any SAL_CALL getPropertyValue( const OUString& PropertyName ) override;
+
+ virtual void SAL_CALL addPropertyChangeListener(
+ const OUString& aPropertyName,
+ const css::uno::Reference< css::beans::XPropertyChangeListener >& xListener ) override;
+
+ virtual void SAL_CALL removePropertyChangeListener(
+ const OUString& aPropertyName,
+ const css::uno::Reference< css::beans::XPropertyChangeListener >& aListener ) override;
+
+ virtual void SAL_CALL addVetoableChangeListener(
+ const OUString& PropertyName,
+ const css::uno::Reference< css::beans::XVetoableChangeListener >& aListener ) override;
+
+ virtual void SAL_CALL removeVetoableChangeListener( const OUString& PropertyName, const css::uno::Reference< css::beans::XVetoableChangeListener >& aListener ) override;
+
+ // XHierarchicalStorageAccess
+
+ virtual css::uno::Reference< css::embed::XExtendedStorageStream > SAL_CALL openStreamElementByHierarchicalName( const OUString& sStreamPath, ::sal_Int32 nOpenMode ) override;
+
+ virtual css::uno::Reference< css::embed::XExtendedStorageStream > SAL_CALL openEncryptedStreamElementByHierarchicalName( const OUString& sStreamName, ::sal_Int32 nOpenMode, const OUString& sPassword ) override;
+
+ virtual void SAL_CALL removeStreamElementByHierarchicalName( const OUString& sElementPath ) override;
+
+private:
+ css::uno::Reference< css::embed::XStorage > openStorageElementImpl(
+ std::unique_lock<std::mutex>& rGuard,
+ std::u16string_view aStorName, sal_Int32 nStorageMode );
+ css::uno::Reference< css::io::XStream > openStreamElementImpl(
+ std::unique_lock<std::mutex>& rGuard,
+ std::u16string_view aStreamName, sal_Int32 nOpenMode );
+ void disposeImpl(std::unique_lock<std::mutex>& rGuard);
+};
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/fsstor/oinputstreamcontainer.cxx b/svl/source/fsstor/oinputstreamcontainer.cxx
new file mode 100644
index 0000000000..f3a3a68c8f
--- /dev/null
+++ b/svl/source/fsstor/oinputstreamcontainer.cxx
@@ -0,0 +1,260 @@
+/* -*- 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 "oinputstreamcontainer.hxx"
+#include <cppuhelper/typeprovider.hxx>
+#include <cppuhelper/queryinterface.hxx>
+
+using namespace ::com::sun::star;
+
+OFSInputStreamContainer::OFSInputStreamContainer( const uno::Reference< io::XInputStream >& xStream )
+: m_xInputStream( xStream )
+, m_xSeekable( xStream, uno::UNO_QUERY )
+, m_bSeekable( false )
+, m_bDisposed( false )
+{
+ m_bSeekable = m_xSeekable.is();
+}
+
+OFSInputStreamContainer::~OFSInputStreamContainer()
+{
+}
+
+uno::Sequence< uno::Type > SAL_CALL OFSInputStreamContainer::getTypes()
+{
+ if (m_bSeekable)
+ {
+ static cppu::OTypeCollection aTypeCollection(cppu::UnoType<io::XStream>::get(),
+ cppu::UnoType<io::XInputStream>::get(),
+ cppu::UnoType<io::XSeekable>::get());
+
+ return aTypeCollection.getTypes();
+ }
+ else
+ {
+ static cppu::OTypeCollection aTypeCollection(cppu::UnoType<io::XStream>::get(),
+ cppu::UnoType<io::XInputStream>::get());
+
+ return aTypeCollection.getTypes();
+ }
+}
+
+uno::Any SAL_CALL OFSInputStreamContainer::queryInterface( const uno::Type& rType )
+{
+ // Attention:
+ // Don't use mutex or guard in this method!!! Is a method of XInterface.
+
+ uno::Any aReturn;
+ if ( m_bSeekable )
+ aReturn = ::cppu::queryInterface( rType,
+ static_cast< io::XStream* >( this ),
+ static_cast< io::XInputStream* >( this ),
+ static_cast< io::XSeekable* >( this ) );
+ else
+ aReturn = ::cppu::queryInterface( rType,
+ static_cast< io::XStream* >( this ),
+ static_cast< io::XInputStream* >( this ) );
+
+ if ( aReturn.hasValue() )
+ return aReturn ;
+
+ return ::cppu::OWeakObject::queryInterface( rType ) ;
+}
+
+void SAL_CALL OFSInputStreamContainer::acquire()
+ noexcept
+{
+ ::cppu::OWeakObject::acquire();
+}
+
+void SAL_CALL OFSInputStreamContainer::release()
+ noexcept
+{
+ ::cppu::OWeakObject::release();
+}
+
+sal_Int32 SAL_CALL OFSInputStreamContainer::readBytes( uno::Sequence< sal_Int8 >& aData, sal_Int32 nBytesToRead )
+{
+ std::scoped_lock aGuard( m_aMutex );
+
+ if ( m_bDisposed )
+ throw lang::DisposedException();
+
+ if ( !m_xInputStream.is() )
+ throw uno::RuntimeException();
+
+ return m_xInputStream->readBytes( aData, nBytesToRead );
+}
+
+sal_Int32 SAL_CALL OFSInputStreamContainer::readSomeBytes( uno::Sequence< sal_Int8 >& aData, sal_Int32 nMaxBytesToRead )
+{
+ std::scoped_lock aGuard( m_aMutex );
+
+ if ( m_bDisposed )
+ throw lang::DisposedException();
+
+ if ( !m_xInputStream.is() )
+ throw uno::RuntimeException();
+
+ return m_xInputStream->readSomeBytes( aData, nMaxBytesToRead );
+}
+
+void SAL_CALL OFSInputStreamContainer::skipBytes( sal_Int32 nBytesToSkip )
+{
+ std::scoped_lock aGuard( m_aMutex );
+
+ if ( m_bDisposed )
+ throw lang::DisposedException();
+
+ if ( !m_xInputStream.is() )
+ throw uno::RuntimeException();
+
+ m_xInputStream->skipBytes( nBytesToSkip );
+}
+
+sal_Int32 SAL_CALL OFSInputStreamContainer::available( )
+{
+ std::scoped_lock aGuard( m_aMutex );
+
+ if ( m_bDisposed )
+ throw lang::DisposedException();
+
+ if ( !m_xInputStream.is() )
+ throw uno::RuntimeException();
+
+ return m_xInputStream->available();
+}
+
+void SAL_CALL OFSInputStreamContainer::closeInput( )
+{
+ {
+ std::scoped_lock aGuard( m_aMutex );
+
+ if ( m_bDisposed )
+ throw lang::DisposedException();
+
+ if ( !m_xInputStream.is() )
+ throw uno::RuntimeException();
+ }
+ dispose();
+}
+
+uno::Reference< io::XInputStream > SAL_CALL OFSInputStreamContainer::getInputStream()
+{
+ std::scoped_lock aGuard( m_aMutex );
+
+ if ( m_bDisposed )
+ throw lang::DisposedException();
+
+ if ( !m_xInputStream.is() )
+ return uno::Reference< io::XInputStream >();
+
+ return this;
+}
+
+uno::Reference< io::XOutputStream > SAL_CALL OFSInputStreamContainer::getOutputStream()
+{
+ std::scoped_lock aGuard( m_aMutex );
+
+ if ( m_bDisposed )
+ throw lang::DisposedException();
+
+ return uno::Reference< io::XOutputStream >();
+}
+
+void SAL_CALL OFSInputStreamContainer::seek( sal_Int64 location )
+{
+ std::scoped_lock aGuard( m_aMutex );
+
+ if ( m_bDisposed )
+ throw lang::DisposedException();
+
+ if ( !m_xSeekable.is() )
+ throw uno::RuntimeException();
+
+ m_xSeekable->seek( location );
+}
+
+sal_Int64 SAL_CALL OFSInputStreamContainer::getPosition()
+{
+ std::scoped_lock aGuard( m_aMutex );
+
+ if ( m_bDisposed )
+ throw lang::DisposedException();
+
+ if ( !m_xSeekable.is() )
+ throw uno::RuntimeException();
+
+ return m_xSeekable->getPosition();
+}
+
+sal_Int64 SAL_CALL OFSInputStreamContainer::getLength()
+{
+ std::scoped_lock aGuard( m_aMutex );
+
+ if ( m_bDisposed )
+ throw lang::DisposedException();
+
+ if ( !m_xSeekable.is() )
+ throw uno::RuntimeException();
+
+ return m_xSeekable->getLength();
+}
+
+void SAL_CALL OFSInputStreamContainer::dispose( )
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ if ( m_bDisposed )
+ return;
+
+ if ( !m_xInputStream.is() )
+ throw uno::RuntimeException();
+
+ m_xInputStream->closeInput();
+
+ lang::EventObject aSource( getXWeak() );
+ m_aListenersContainer.disposeAndClear( aGuard, aSource );
+
+ m_bDisposed = true;
+}
+
+void SAL_CALL OFSInputStreamContainer::addEventListener( const uno::Reference< lang::XEventListener >& xListener )
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ if ( m_bDisposed )
+ throw lang::DisposedException();
+
+ m_aListenersContainer.addInterface( aGuard, xListener );
+}
+
+void SAL_CALL OFSInputStreamContainer::removeEventListener( const uno::Reference< lang::XEventListener >& xListener )
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ if ( m_bDisposed )
+ throw lang::DisposedException();
+
+ m_aListenersContainer.removeInterface( aGuard, xListener );
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/fsstor/oinputstreamcontainer.hxx b/svl/source/fsstor/oinputstreamcontainer.hxx
new file mode 100644
index 0000000000..7228bcde50
--- /dev/null
+++ b/svl/source/fsstor/oinputstreamcontainer.hxx
@@ -0,0 +1,83 @@
+/* -*- 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 .
+ */
+
+#ifndef INCLUDED_SVL_SOURCE_FSSTOR_OINPUTSTREAMCONTAINER_HXX
+#define INCLUDED_SVL_SOURCE_FSSTOR_OINPUTSTREAMCONTAINER_HXX
+
+#include <com/sun/star/io/XInputStream.hpp>
+#include <com/sun/star/embed/XExtendedStorageStream.hpp>
+#include <com/sun/star/io/XSeekable.hpp>
+
+
+#include <cppuhelper/implbase.hxx>
+#include <comphelper/interfacecontainer4.hxx>
+
+#include <mutex>
+
+class OFSInputStreamContainer : public cppu::WeakImplHelper < css::io::XInputStream
+ ,css::embed::XExtendedStorageStream >
+ , public css::io::XSeekable
+{
+ std::mutex m_aMutex;
+
+ css::uno::Reference < css::io::XInputStream > m_xInputStream;
+ css::uno::Reference < css::io::XSeekable > m_xSeekable;
+
+ bool m_bSeekable;
+
+ bool m_bDisposed;
+
+ ::comphelper::OInterfaceContainerHelper4<css::lang::XEventListener> m_aListenersContainer; // list of listeners
+
+public:
+ explicit OFSInputStreamContainer( const css::uno::Reference < css::io::XInputStream >& xStream );
+
+ virtual ~OFSInputStreamContainer() override;
+
+ virtual css::uno::Sequence< css::uno::Type > SAL_CALL getTypes() override;
+ virtual css::uno::Any SAL_CALL queryInterface( const css::uno::Type& rType ) override;
+ virtual void SAL_CALL acquire() noexcept override;
+ virtual void SAL_CALL release() noexcept 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;
+
+ //XStream
+ virtual css::uno::Reference< css::io::XInputStream > SAL_CALL getInputStream( ) override;
+ virtual css::uno::Reference< css::io::XOutputStream > SAL_CALL getOutputStream( ) 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;
+
+ //XComponent
+ virtual void SAL_CALL dispose() override;
+ virtual void SAL_CALL addEventListener( const css::uno::Reference< css::lang::XEventListener >& xListener ) override;
+ virtual void SAL_CALL removeEventListener( const css::uno::Reference< css::lang::XEventListener >& aListener ) override;
+
+};
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/fsstor/ostreamcontainer.cxx b/svl/source/fsstor/ostreamcontainer.cxx
new file mode 100644
index 0000000000..5381428588
--- /dev/null
+++ b/svl/source/fsstor/ostreamcontainer.cxx
@@ -0,0 +1,451 @@
+/* -*- 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 "ostreamcontainer.hxx"
+
+#include <cppuhelper/queryinterface.hxx>
+#include <comphelper/sequence.hxx>
+
+
+using namespace ::com::sun::star;
+
+OFSStreamContainer::OFSStreamContainer( const uno::Reference < io::XStream >& xStream )
+: m_bDisposed( false )
+, m_bInputClosed( false )
+, m_bOutputClosed( false )
+{
+ try
+ {
+ m_xStream = xStream;
+ if ( !m_xStream.is() )
+ throw uno::RuntimeException();
+
+ m_xSeekable.set( xStream, uno::UNO_QUERY );
+ m_xInputStream = xStream->getInputStream();
+ m_xOutputStream = xStream->getOutputStream();
+ m_xTruncate.set( m_xOutputStream, uno::UNO_QUERY );
+ m_xAsyncOutputMonitor.set( m_xOutputStream, uno::UNO_QUERY );
+ }
+ catch( uno::Exception& )
+ {
+ m_xStream.clear();
+ m_xSeekable.clear();
+ m_xInputStream.clear();
+ m_xOutputStream.clear();
+ m_xTruncate.clear();
+ m_xAsyncOutputMonitor.clear();
+ }
+}
+
+OFSStreamContainer::~OFSStreamContainer()
+{
+}
+
+// XInterface
+uno::Any SAL_CALL OFSStreamContainer::queryInterface( const uno::Type& rType )
+{
+ uno::Any aReturn = ::cppu::queryInterface
+ ( rType
+ , static_cast<lang::XTypeProvider*> ( this )
+ , static_cast<io::XStream*> ( this )
+ , static_cast<embed::XExtendedStorageStream*> ( this )
+ , static_cast<lang::XComponent*> ( this ) );
+
+ if ( aReturn.hasValue() )
+ return aReturn ;
+
+ if ( m_xSeekable.is() )
+ {
+ aReturn = ::cppu::queryInterface
+ ( rType
+ , static_cast<io::XSeekable*> ( this ) );
+
+ if ( aReturn.hasValue() )
+ return aReturn ;
+ }
+
+ if ( m_xInputStream.is() )
+ {
+ aReturn = ::cppu::queryInterface
+ ( rType
+ , static_cast<io::XInputStream*> ( this ) );
+
+ if ( aReturn.hasValue() )
+ return aReturn ;
+ }
+ if ( m_xOutputStream.is() )
+ {
+ aReturn = ::cppu::queryInterface
+ ( rType
+ , static_cast<io::XOutputStream*> ( this ) );
+
+ if ( aReturn.hasValue() )
+ return aReturn ;
+ }
+ if ( m_xTruncate.is() )
+ {
+ aReturn = ::cppu::queryInterface
+ ( rType
+ , static_cast<io::XTruncate*> ( this ) );
+
+ if ( aReturn.hasValue() )
+ return aReturn ;
+ }
+ if ( m_xAsyncOutputMonitor.is() )
+ {
+ aReturn = ::cppu::queryInterface
+ ( rType
+ , static_cast<io::XAsyncOutputMonitor*> ( this ) );
+
+ if ( aReturn.hasValue() )
+ return aReturn ;
+ }
+
+ return OWeakObject::queryInterface( rType );
+}
+
+void SAL_CALL OFSStreamContainer::acquire()
+ noexcept
+{
+ OWeakObject::acquire();
+}
+
+void SAL_CALL OFSStreamContainer::release()
+ noexcept
+{
+ OWeakObject::release();
+}
+
+// XTypeProvider
+uno::Sequence< uno::Type > SAL_CALL OFSStreamContainer::getTypes()
+{
+ if ( !m_aTypes.hasElements() )
+ {
+ std::scoped_lock aGuard( m_aMutex );
+
+ if ( !m_aTypes.hasElements() )
+ {
+ std::vector<uno::Type> tmp
+ {
+ cppu::UnoType<lang::XTypeProvider>::get(),
+ cppu::UnoType<embed::XExtendedStorageStream>::get()
+ };
+
+ if ( m_xSeekable.is() )
+ tmp.push_back(cppu::UnoType<io::XSeekable>::get());
+ if ( m_xInputStream.is() )
+ tmp.push_back(cppu::UnoType<io::XInputStream>::get());
+ if ( m_xOutputStream.is() )
+ tmp.push_back(cppu::UnoType<io::XOutputStream>::get());
+ if ( m_xTruncate.is() )
+ tmp.push_back(cppu::UnoType<io::XTruncate>::get());
+ if ( m_xAsyncOutputMonitor.is() )
+ tmp.push_back(cppu::UnoType<io::XAsyncOutputMonitor>::get());
+
+ m_aTypes = comphelper::containerToSequence(tmp);
+ }
+ }
+ return m_aTypes;
+}
+
+uno::Sequence< sal_Int8 > SAL_CALL OFSStreamContainer::getImplementationId()
+{
+ return css::uno::Sequence<sal_Int8>();
+}
+
+// XStream
+uno::Reference< io::XInputStream > SAL_CALL OFSStreamContainer::getInputStream()
+{
+ std::scoped_lock aGuard( m_aMutex );
+
+ if ( m_bDisposed )
+ throw lang::DisposedException();
+
+ if ( !m_xStream.is() )
+ throw uno::RuntimeException();
+
+ if ( m_xInputStream.is() )
+ return uno::Reference< io::XInputStream >( static_cast< io::XInputStream* >( this ) );
+
+ return uno::Reference< io::XInputStream >();
+}
+
+uno::Reference< io::XOutputStream > SAL_CALL OFSStreamContainer::getOutputStream()
+{
+ std::scoped_lock aGuard( m_aMutex );
+
+ if ( m_bDisposed )
+ throw lang::DisposedException();
+
+ if ( !m_xStream.is() )
+ throw uno::RuntimeException();
+
+ if ( m_xOutputStream.is() )
+ return uno::Reference< io::XOutputStream >( static_cast< io::XOutputStream* >( this ) );
+
+ return uno::Reference< io::XOutputStream >();
+}
+
+// XComponent
+void SAL_CALL OFSStreamContainer::dispose()
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ if ( m_bDisposed )
+ return;
+
+ if ( !m_xStream.is() )
+ throw uno::RuntimeException();
+
+ if ( m_xInputStream.is() && !m_bInputClosed )
+ {
+ m_xInputStream->closeInput();
+ m_bInputClosed = true;
+ }
+
+ if ( m_xOutputStream.is() && !m_bOutputClosed )
+ {
+ m_xOutputStream->closeOutput();
+ m_bOutputClosed = true;
+ }
+
+ lang::EventObject aSource( getXWeak() );
+ m_aListenersContainer.disposeAndClear( aGuard, aSource );
+ m_bDisposed = true;
+}
+
+void SAL_CALL OFSStreamContainer::addEventListener( const uno::Reference< lang::XEventListener >& xListener )
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ if ( m_bDisposed )
+ throw lang::DisposedException();
+
+ m_aListenersContainer.addInterface( aGuard, xListener );
+}
+
+void SAL_CALL OFSStreamContainer::removeEventListener( const uno::Reference< lang::XEventListener >& xListener )
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ if ( m_bDisposed )
+ throw lang::DisposedException();
+
+ m_aListenersContainer.removeInterface( aGuard, xListener );
+}
+
+
+// XSeekable
+void SAL_CALL OFSStreamContainer::seek( sal_Int64 location )
+{
+ std::scoped_lock aGuard( m_aMutex );
+
+ if ( m_bDisposed )
+ throw lang::DisposedException();
+
+ if ( !m_xStream.is() || !m_xSeekable.is() )
+ throw uno::RuntimeException();
+
+ m_xSeekable->seek( location );
+}
+
+sal_Int64 SAL_CALL OFSStreamContainer::getPosition()
+{
+ std::scoped_lock aGuard( m_aMutex );
+
+ if ( m_bDisposed )
+ throw lang::DisposedException();
+
+ if ( !m_xStream.is() || !m_xSeekable.is() )
+ throw uno::RuntimeException();
+
+ return m_xSeekable->getPosition();
+}
+
+sal_Int64 SAL_CALL OFSStreamContainer::getLength()
+{
+ std::scoped_lock aGuard( m_aMutex );
+
+ if ( m_bDisposed )
+ throw lang::DisposedException();
+
+ if ( !m_xStream.is() || !m_xSeekable.is() )
+ throw uno::RuntimeException();
+
+ return m_xSeekable->getLength();
+}
+
+
+// XInputStream
+sal_Int32 SAL_CALL OFSStreamContainer::readBytes( uno::Sequence< sal_Int8 >& aData, sal_Int32 nBytesToRead )
+{
+ std::scoped_lock aGuard( m_aMutex );
+
+ if ( m_bDisposed )
+ throw lang::DisposedException();
+
+ if ( !m_xStream.is() || !m_xInputStream.is() )
+ throw uno::RuntimeException();
+
+ return m_xInputStream->readBytes( aData, nBytesToRead );
+}
+
+sal_Int32 SAL_CALL OFSStreamContainer::readSomeBytes( uno::Sequence< sal_Int8 >& aData, sal_Int32 nMaxBytesToRead )
+{
+ std::scoped_lock aGuard( m_aMutex );
+
+ if ( m_bDisposed )
+ throw lang::DisposedException();
+
+ if ( !m_xStream.is() || !m_xInputStream.is() )
+ throw uno::RuntimeException();
+
+ return m_xInputStream->readSomeBytes( aData, nMaxBytesToRead );
+}
+
+void SAL_CALL OFSStreamContainer::skipBytes( sal_Int32 nBytesToSkip )
+{
+ std::scoped_lock aGuard( m_aMutex );
+
+ if ( m_bDisposed )
+ throw lang::DisposedException();
+
+ if ( !m_xStream.is() || !m_xInputStream.is() )
+ throw uno::RuntimeException();
+
+ m_xInputStream->skipBytes( nBytesToSkip );
+}
+
+sal_Int32 SAL_CALL OFSStreamContainer::available()
+{
+ std::scoped_lock aGuard( m_aMutex );
+
+ if ( m_bDisposed )
+ throw lang::DisposedException();
+
+ if ( !m_xStream.is() || !m_xInputStream.is() )
+ throw uno::RuntimeException();
+
+ return m_xInputStream->available();
+}
+
+void SAL_CALL OFSStreamContainer::closeInput()
+{
+ {
+ std::scoped_lock aGuard( m_aMutex );
+
+ if ( m_bDisposed )
+ throw lang::DisposedException();
+
+ if ( !m_xStream.is() || !m_xInputStream.is() )
+ throw uno::RuntimeException();
+
+ if ( m_xInputStream.is() )
+ {
+ m_xInputStream->closeInput();
+ m_bInputClosed = true;
+ }
+ if ( !m_bOutputClosed )
+ return;
+ }
+
+ dispose();
+}
+
+// XOutputStream
+void SAL_CALL OFSStreamContainer::writeBytes( const uno::Sequence< sal_Int8 >& aData )
+{
+ std::scoped_lock aGuard( m_aMutex );
+
+ if ( m_bDisposed )
+ throw lang::DisposedException();
+
+ if ( !m_xStream.is() || !m_xOutputStream.is() )
+ throw uno::RuntimeException();
+
+ return m_xOutputStream->writeBytes( aData );
+}
+
+void SAL_CALL OFSStreamContainer::flush()
+{
+ std::scoped_lock aGuard( m_aMutex );
+
+ if ( m_bDisposed )
+ throw lang::DisposedException();
+
+ if ( !m_xStream.is() || !m_xOutputStream.is() )
+ throw uno::RuntimeException();
+
+ return m_xOutputStream->flush();
+}
+
+void SAL_CALL OFSStreamContainer::closeOutput()
+{
+ {
+ std::scoped_lock aGuard( m_aMutex );
+
+ if ( m_bDisposed )
+ throw lang::DisposedException();
+
+ if ( !m_xStream.is() || !m_xOutputStream.is() )
+ throw uno::RuntimeException();
+
+ if ( m_xOutputStream.is() )
+ {
+ m_xOutputStream->closeOutput();
+ m_bOutputClosed = true;
+ }
+ if ( !m_bInputClosed )
+ return;
+ }
+ dispose();
+}
+
+
+// XTruncate
+void SAL_CALL OFSStreamContainer::truncate()
+{
+ std::scoped_lock aGuard( m_aMutex );
+
+ if ( m_bDisposed )
+ throw lang::DisposedException();
+
+ if ( !m_xStream.is() || !m_xTruncate.is() )
+ throw uno::RuntimeException();
+
+ m_xTruncate->truncate();
+}
+
+
+// XAsyncOutputMonitor
+void SAL_CALL OFSStreamContainer::waitForCompletion()
+{
+ std::scoped_lock aGuard( m_aMutex );
+
+ if ( m_bDisposed )
+ throw lang::DisposedException();
+
+ if ( !m_xStream.is() || !m_xAsyncOutputMonitor.is() )
+ throw uno::RuntimeException();
+
+ m_xAsyncOutputMonitor->waitForCompletion();
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/fsstor/ostreamcontainer.hxx b/svl/source/fsstor/ostreamcontainer.hxx
new file mode 100644
index 0000000000..7dd8219050
--- /dev/null
+++ b/svl/source/fsstor/ostreamcontainer.hxx
@@ -0,0 +1,109 @@
+/* -*- 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 .
+ */
+
+#ifndef INCLUDED_SVL_SOURCE_FSSTOR_OSTREAMCONTAINER_HXX
+#define INCLUDED_SVL_SOURCE_FSSTOR_OSTREAMCONTAINER_HXX
+
+#include <com/sun/star/lang/XTypeProvider.hpp>
+#include <com/sun/star/embed/XExtendedStorageStream.hpp>
+#include <com/sun/star/io/XSeekable.hpp>
+#include <com/sun/star/io/XTruncate.hpp>
+#include <com/sun/star/io/XInputStream.hpp>
+#include <com/sun/star/io/XOutputStream.hpp>
+#include <com/sun/star/io/XStream.hpp>
+#include <com/sun/star/io/XAsyncOutputMonitor.hpp>
+#include <cppuhelper/weak.hxx>
+#include <comphelper/interfacecontainer4.hxx>
+#include <mutex>
+
+class OFSStreamContainer : public cppu::OWeakObject,
+ public css::lang::XTypeProvider,
+ public css::embed::XExtendedStorageStream,
+ public css::io::XSeekable,
+ public css::io::XInputStream,
+ public css::io::XOutputStream,
+ public css::io::XTruncate,
+ public css::io::XAsyncOutputMonitor
+{
+ std::mutex m_aMutex;
+
+ css::uno::Reference< css::io::XStream > m_xStream;
+ css::uno::Reference< css::io::XSeekable > m_xSeekable;
+ css::uno::Reference< css::io::XInputStream > m_xInputStream;
+ css::uno::Reference< css::io::XOutputStream > m_xOutputStream;
+ css::uno::Reference< css::io::XTruncate > m_xTruncate;
+ css::uno::Reference< css::io::XAsyncOutputMonitor > m_xAsyncOutputMonitor;
+
+ bool m_bDisposed;
+ bool m_bInputClosed;
+ bool m_bOutputClosed;
+
+ ::comphelper::OInterfaceContainerHelper4<css::lang::XEventListener> m_aListenersContainer; // list of listeners
+ css::uno::Sequence<css::uno::Type> m_aTypes;
+
+public:
+ explicit OFSStreamContainer( const css::uno::Reference < css::io::XStream >& xStream );
+ virtual ~OFSStreamContainer() override;
+
+ // XInterface
+ virtual css::uno::Any SAL_CALL queryInterface( const css::uno::Type& rType ) override;
+ virtual void SAL_CALL acquire() noexcept override;
+ virtual void SAL_CALL release() noexcept override;
+
+ // XTypeProvider
+ virtual css::uno::Sequence< css::uno::Type > SAL_CALL getTypes() override;
+ virtual css::uno::Sequence< sal_Int8 > SAL_CALL getImplementationId() override;
+
+ // XStream
+ virtual css::uno::Reference< css::io::XInputStream > SAL_CALL getInputStream( ) override;
+ virtual css::uno::Reference< css::io::XOutputStream > SAL_CALL getOutputStream( ) override;
+
+ // XComponent
+ virtual void SAL_CALL dispose() override;
+ virtual void SAL_CALL addEventListener( const css::uno::Reference< css::lang::XEventListener >& xListener ) override;
+ virtual void SAL_CALL removeEventListener( const css::uno::Reference< css::lang::XEventListener >& aListener ) 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;
+
+ // 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;
+
+ // XTruncate
+ virtual void SAL_CALL truncate() override;
+
+ // XAsyncOutputMonitor
+ virtual void SAL_CALL waitForCompletion( ) override;
+
+};
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/inc/fsfactory.hxx b/svl/source/inc/fsfactory.hxx
new file mode 100644
index 0000000000..450337639b
--- /dev/null
+++ b/svl/source/inc/fsfactory.hxx
@@ -0,0 +1,54 @@
+/* -*- 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 .
+ */
+
+#ifndef INCLUDED_SVL_SOURCE_INC_FSFACTORY_HXX
+#define INCLUDED_SVL_SOURCE_INC_FSFACTORY_HXX
+
+#include <com/sun/star/lang/XSingleServiceFactory.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+#include <cppuhelper/implbase.hxx>
+#include <osl/diagnose.h>
+
+class FSStorageFactory final : public ::cppu::WeakImplHelper< css::lang::XSingleServiceFactory,
+ css::lang::XServiceInfo >
+{
+ css::uno::Reference< css::uno::XComponentContext > m_xContext;
+
+public:
+ FSStorageFactory( const css::uno::Reference< css::uno::XComponentContext >& xContext )
+ : m_xContext( xContext )
+ {
+ OSL_ENSURE( xContext.is(), "No service manager is provided!" );
+ }
+
+ // XSingleServiceFactory
+ virtual css::uno::Reference< css::uno::XInterface > SAL_CALL createInstance() override;
+ virtual css::uno::Reference< css::uno::XInterface > SAL_CALL createInstanceWithArguments( const css::uno::Sequence< css::uno::Any >& aArguments ) override;
+
+ // XServiceInfo
+ virtual OUString SAL_CALL getImplementationName() override;
+ virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override;
+ virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override;
+
+};
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/inc/items_helper.hxx b/svl/source/inc/items_helper.hxx
new file mode 100644
index 0000000000..a86b72eab8
--- /dev/null
+++ b/svl/source/inc/items_helper.hxx
@@ -0,0 +1,60 @@
+/* -*- 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 .
+ */
+
+#pragma once
+
+#include <sal/config.h>
+
+#include <sal/types.h>
+#include <svl/whichranges.hxx>
+
+#include <utility>
+
+namespace svl::detail
+{
+/**
+ * Determines the number of sal_uInt16s in a container of pairs of
+ * sal_uInt16s, each representing a range of sal_uInt16s, and total capacity of the ranges.
+ */
+inline sal_uInt16 CountRanges(const WhichRangesContainer& pRanges)
+{
+ sal_uInt16 nCapacity = 0;
+ for (const auto& rPair : pRanges)
+ {
+ nCapacity += rangeSize(rPair.first, rPair.second);
+ }
+ return nCapacity;
+}
+
+inline bool validRanges2(const WhichRangesContainer& pRanges)
+{
+ for (sal_Int32 i = 0; i < pRanges.size(); ++i)
+ {
+ auto p = pRanges[i];
+ if (!validRange(p.first, p.second))
+ return false;
+ // ranges must be sorted
+ if (i < pRanges.size() - 1 && !validGap(p.second, pRanges[i + 1].first))
+ return false;
+ }
+ return true;
+}
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/svl/source/inc/poolio.hxx b/svl/source/inc/poolio.hxx
new file mode 100644
index 0000000000..118deaf50e
--- /dev/null
+++ b/svl/source/inc/poolio.hxx
@@ -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/.
+ *
+ * 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 .
+ */
+
+#ifndef INCLUDED_SVL_SOURCE_INC_POOLIO_HXX
+#define INCLUDED_SVL_SOURCE_INC_POOLIO_HXX
+
+#include <rtl/ref.hxx>
+#include <svl/itempool.hxx>
+#include <svl/SfxBroadcaster.hxx>
+#include <tools/debug.hxx>
+#include <memory>
+#include <o3tl/sorted_vector.hxx>
+#include <utility>
+
+class SfxPoolItem;
+class SfxItemPoolUser;
+
+struct SfxItemPool_Impl
+{
+ SfxBroadcaster aBC;
+ OUString aName;
+ std::vector<SfxPoolItem*> maPoolDefaults;
+ std::vector<SfxPoolItem*>* mpStaticDefaults;
+ SfxItemPool* mpMaster;
+ rtl::Reference<SfxItemPool> mpSecondary;
+ WhichRangesContainer mpPoolRanges;
+ sal_uInt16 mnStart;
+ sal_uInt16 mnEnd;
+ MapUnit eDefMetric;
+
+ SfxItemPool_Impl( SfxItemPool* pMaster, OUString _aName, sal_uInt16 nStart, sal_uInt16 nEnd )
+ : aName(std::move(_aName))
+ , maPoolDefaults(nEnd - nStart + 1)
+ , mpStaticDefaults(nullptr)
+ , mpMaster(pMaster)
+ , mnStart(nStart)
+ , mnEnd(nEnd)
+ , eDefMetric(MapUnit::MapCM)
+ {
+ DBG_ASSERT(mnStart, "Start-Which-Id must be greater 0" );
+ }
+
+ ~SfxItemPool_Impl()
+ {
+ DeleteItems();
+ }
+
+ void DeleteItems()
+ {
+ maPoolDefaults.clear();
+ mpPoolRanges.reset();
+ }
+};
+
+#endif // INCLUDED_SVL_SOURCE_INC_POOLIO_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/inc/stringio.hxx b/svl/source/inc/stringio.hxx
new file mode 100644
index 0000000000..51d52abeb3
--- /dev/null
+++ b/svl/source/inc/stringio.hxx
@@ -0,0 +1,47 @@
+/* -*- 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 .
+ */
+
+#ifndef INCLUDED_SVL_SOURCE_INC_STRINGIO_HXX
+#define INCLUDED_SVL_SOURCE_INC_STRINGIO_HXX
+
+#include <rtl/ustring.hxx>
+
+class SvStream;
+
+/** Read in a Unicode string from a streamed byte string representation.
+
+ @param rStream Some (input) stream. Its Stream/TargetCharSets must
+ be set to correct values!
+
+ @return On success, returns the reconstructed Unicode string.
+ */
+OUString readByteString(SvStream& rStream);
+
+/** Write a byte string representation of a Unicode string into a stream.
+
+ @param rStream Some (output) stream. Its Stream/TargetCharSets must
+ be set to correct values!
+
+ @param rString Some Unicode string.
+ */
+void writeByteString(SvStream& rStream, std::u16string_view rString);
+
+#endif // INCLUDED_SVL_SOURCE_INC_STRINGIO_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/items/IndexedStyleSheets.cxx b/svl/source/items/IndexedStyleSheets.cxx
new file mode 100644
index 0000000000..57e2dddbf1
--- /dev/null
+++ b/svl/source/items/IndexedStyleSheets.cxx
@@ -0,0 +1,244 @@
+/* -*- 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 <svl/IndexedStyleSheets.hxx>
+#include <svl/style.hxx>
+
+#include <stdexcept>
+#include <algorithm>
+#include <utility>
+
+
+namespace {
+const size_t NUMBER_OF_FAMILIES = 7;
+size_t family_to_index(SfxStyleFamily family)
+{
+ switch (family) {
+ case SfxStyleFamily::Char:
+ return 0;
+ case SfxStyleFamily::Para:
+ return 1;
+ case SfxStyleFamily::Frame:
+ return 2;
+ case SfxStyleFamily::Page:
+ return 3;
+ case SfxStyleFamily::Pseudo:
+ return 4;
+ case SfxStyleFamily::Table:
+ return 5;
+ case SfxStyleFamily::All:
+ return 6;
+ default: break;
+ }
+ assert(false); // only for compiler warning. all cases are handled in the switch
+ return 0;
+}
+}
+
+namespace svl {
+
+IndexedStyleSheets::IndexedStyleSheets()
+{
+ for (size_t i = 0; i < NUMBER_OF_FAMILIES; i++) {
+ mStyleSheetPositionsByFamily.emplace_back();
+ }
+;}
+
+void IndexedStyleSheets::Register(const SfxStyleSheetBase& style, sal_Int32 pos)
+{
+ mPositionsByName.insert(std::make_pair(style.GetName(), pos));
+ size_t position = family_to_index(style.GetFamily());
+ mStyleSheetPositionsByFamily.at(position).push_back(pos);
+ size_t positionForFamilyAll = family_to_index(SfxStyleFamily::All);
+ mStyleSheetPositionsByFamily.at(positionForFamilyAll).push_back(pos);
+}
+
+void
+IndexedStyleSheets::Reindex()
+{
+ mPositionsByName.clear();
+ mStyleSheetPositionsByFamily.clear();
+ for (size_t i = 0; i < NUMBER_OF_FAMILIES; i++) {
+ mStyleSheetPositionsByFamily.emplace_back();
+ }
+
+ sal_Int32 i = 0;
+ for (const auto& rxStyleSheet : mStyleSheets) {
+ SfxStyleSheetBase* p = rxStyleSheet.get();
+ Register(*p, i);
+ ++i;
+ }
+}
+
+sal_Int32 IndexedStyleSheets::GetNumberOfStyleSheets() const
+{
+ return mStyleSheets.size();
+}
+
+void
+IndexedStyleSheets::AddStyleSheet(const rtl::Reference< SfxStyleSheetBase >& style)
+{
+ if (!HasStyleSheet(style)) {
+ mStyleSheets.push_back(style);
+ // since we just added an element to the vector, we can safely do -1 as it will always be >= 1
+ Register(*style, mStyleSheets.size()-1);
+ }
+}
+
+bool
+IndexedStyleSheets::RemoveStyleSheet(const rtl::Reference< SfxStyleSheetBase >& style)
+{
+ std::pair<MapType::const_iterator, MapType::const_iterator> range = mPositionsByName.equal_range(style->GetName());
+ for (MapType::const_iterator it = range.first; it != range.second; ++it)
+ {
+ sal_Int32 pos = it->second;
+ if (mStyleSheets.at(pos) == style)
+ {
+ mStyleSheets.erase(mStyleSheets.begin() + pos);
+ Reindex();
+ return true;
+ }
+ }
+ return false;
+}
+
+std::vector<sal_Int32> IndexedStyleSheets::FindPositionsByName(const OUString& name) const
+{
+ std::vector<sal_Int32> r;
+ std::pair<MapType::const_iterator, MapType::const_iterator> range = mPositionsByName.equal_range(name);
+ for (MapType::const_iterator it = range.first; it != range.second; ++it) {
+ r.push_back(it->second);
+ }
+ return r;
+}
+
+std::vector<sal_Int32> IndexedStyleSheets::FindPositionsByNameAndPredicate(const OUString& name,
+ StyleSheetPredicate& predicate, SearchBehavior behavior) const
+{
+ std::vector<sal_Int32> r;
+ auto range = mPositionsByName.equal_range(name);
+ for (auto it = range.first; it != range.second; ++it) {
+ sal_Int32 pos = it->second;
+ SfxStyleSheetBase *ssheet = mStyleSheets.at(pos).get();
+ if (predicate.Check(*ssheet)) {
+ r.push_back(pos);
+ if (behavior == SearchBehavior::ReturnFirst) {
+ break;
+ }
+ }
+ }
+ return r;
+}
+
+
+sal_Int32
+IndexedStyleSheets::GetNumberOfStyleSheetsWithPredicate(StyleSheetPredicate& predicate) const
+{
+ return std::count_if(mStyleSheets.begin(), mStyleSheets.end(),
+ [&predicate](const rtl::Reference<SfxStyleSheetBase>& rxStyleSheet) {
+ const SfxStyleSheetBase *ssheet = rxStyleSheet.get();
+ return predicate.Check(*ssheet);
+ });
+}
+
+SfxStyleSheetBase*
+IndexedStyleSheets::GetNthStyleSheetThatMatchesPredicate(
+ sal_Int32 n,
+ StyleSheetPredicate& predicate,
+ sal_Int32 startAt)
+{
+ SfxStyleSheetBase* retval = nullptr;
+ sal_Int32 matching = 0;
+ for (VectorType::const_iterator it = mStyleSheets.begin()+startAt; it != mStyleSheets.end(); ++it) {
+ SfxStyleSheetBase *ssheet = it->get();
+ if (predicate.Check(*ssheet)) {
+ if (matching == n) {
+ retval = it->get();
+ break;
+ }
+ ++matching;
+ }
+ }
+ return retval;
+}
+
+sal_Int32 IndexedStyleSheets::FindStyleSheetPosition(const SfxStyleSheetBase& style) const
+{
+ VectorType::const_iterator it = std::find(mStyleSheets.begin(), mStyleSheets.end(), &style);
+ if (it == mStyleSheets.end()) {
+ throw std::runtime_error("IndexedStyleSheets::FindStylePosition Looked for style not in index");
+ }
+ return std::distance(mStyleSheets.begin(), it);
+}
+
+void
+IndexedStyleSheets::Clear(StyleSheetDisposer& disposer)
+{
+ for (const auto& rxStyleSheet : mStyleSheets) {
+ disposer.Dispose(rxStyleSheet);
+ }
+ mStyleSheets.clear();
+ mPositionsByName.clear();
+}
+
+IndexedStyleSheets::~IndexedStyleSheets()
+{
+}
+
+bool
+IndexedStyleSheets::HasStyleSheet(const rtl::Reference< SfxStyleSheetBase >& style) const
+{
+ std::pair<MapType::const_iterator, MapType::const_iterator> range = mPositionsByName.equal_range(style->GetName());
+ for (MapType::const_iterator it = range.first; it != range.second; ++it)
+ {
+ if (mStyleSheets.at(it->second) == style)
+ return true;
+ }
+ return false;
+}
+
+SfxStyleSheetBase*
+IndexedStyleSheets::GetStyleSheetByPosition(sal_Int32 pos)
+{
+ if( pos < static_cast<sal_Int32>(mStyleSheets.size()) )
+ return mStyleSheets.at(pos).get();
+ return nullptr;
+}
+
+void
+IndexedStyleSheets::ApplyToAllStyleSheets(StyleSheetCallback& callback) const
+{
+ for (const auto& rxStyleSheet : mStyleSheets) {
+ callback.DoIt(*rxStyleSheet);
+ }
+}
+
+std::vector<sal_Int32>
+IndexedStyleSheets::FindPositionsByPredicate(StyleSheetPredicate& predicate) const
+{
+ std::vector<sal_Int32> r;
+ for (VectorType::const_iterator it = mStyleSheets.begin(); it != mStyleSheets.end(); ++it) {
+ if (predicate.Check(**it)) {
+ r.push_back(std::distance(mStyleSheets.begin(), it));
+ }
+ }
+ return r;
+}
+
+const std::vector<sal_Int32>&
+IndexedStyleSheets::GetStyleSheetPositionsByFamily(SfxStyleFamily e) const
+{
+ size_t position = family_to_index(e);
+ return mStyleSheetPositionsByFamily.at(position);
+}
+
+} /* namespace svl */
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/items/cenumitm.cxx b/svl/source/items/cenumitm.cxx
new file mode 100644
index 0000000000..713e1608ef
--- /dev/null
+++ b/svl/source/items/cenumitm.cxx
@@ -0,0 +1,148 @@
+/* -*- 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/Any.hxx>
+#include <svl/cenumitm.hxx>
+#include <svl/eitem.hxx>
+
+#include <comphelper/extract.hxx>
+#include <libxml/xmlwriter.h>
+#include <sal/log.hxx>
+
+
+// virtual
+bool SfxEnumItemInterface::operator ==(const SfxPoolItem & rItem) const
+{
+ SAL_WARN_IF(!SfxPoolItem::operator ==(rItem), "svl.items", "unequal type, with ID/pos " << Which() );
+ return GetEnumValue()
+ == static_cast< const SfxEnumItemInterface * >(&rItem)->
+ GetEnumValue();
+}
+
+// virtual
+bool SfxEnumItemInterface::GetPresentation(SfxItemPresentation, MapUnit,
+ MapUnit, OUString & rText,
+ const IntlWrapper&) const
+{
+ rText = OUString::number( GetEnumValue() );
+ return true;
+}
+
+// virtual
+bool SfxEnumItemInterface::QueryValue(css::uno::Any& rVal, sal_uInt8)
+ const
+{
+ rVal <<= sal_Int32(GetEnumValue());
+ return true;
+}
+
+// virtual
+bool SfxEnumItemInterface::PutValue(const css::uno::Any& rVal,
+ sal_uInt8)
+{
+ sal_Int32 nTheValue = 0;
+
+ if ( ::cppu::enum2int( nTheValue, rVal ) )
+ {
+ SetEnumValue(sal_uInt16(nTheValue));
+ return true;
+ }
+ SAL_WARN("svl.items", "SfxEnumItemInterface::PutValue(): Wrong type");
+ return false;
+}
+
+// virtual
+bool SfxEnumItemInterface::HasBoolValue() const
+{
+ return false;
+}
+
+// virtual
+bool SfxEnumItemInterface::GetBoolValue() const
+{
+ return false;
+}
+
+// virtual
+void SfxEnumItemInterface::SetBoolValue(bool)
+{}
+
+SfxPoolItem* SfxBoolItem::CreateDefault()
+{
+ return new SfxBoolItem();
+}
+
+// virtual
+bool SfxBoolItem::operator ==(const SfxPoolItem & rItem) const
+{
+ assert(SfxPoolItem::operator==(rItem));
+ return m_bValue == static_cast< SfxBoolItem const * >(&rItem)->m_bValue;
+}
+
+// virtual
+bool SfxBoolItem::GetPresentation(SfxItemPresentation,
+ MapUnit, MapUnit,
+ OUString & rText,
+ const IntlWrapper&) const
+{
+ rText = GetValueTextByVal(m_bValue);
+ return true;
+}
+
+void SfxBoolItem::dumpAsXml(xmlTextWriterPtr pWriter) const
+{
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SfxBoolItem"));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), BAD_CAST(GetValueTextByVal(m_bValue).toUtf8().getStr()));
+ SfxPoolItem::dumpAsXml(pWriter);
+ (void)xmlTextWriterEndElement(pWriter);
+}
+
+// virtual
+bool SfxBoolItem::QueryValue(css::uno::Any& rVal, sal_uInt8) const
+{
+ rVal <<= m_bValue;
+ return true;
+}
+
+// virtual
+bool SfxBoolItem::PutValue(const css::uno::Any& rVal, sal_uInt8)
+{
+ bool bTheValue = bool();
+ if (rVal >>= bTheValue)
+ {
+ m_bValue = bTheValue;
+ return true;
+ }
+ SAL_WARN("svl.items", "SfxBoolItem::PutValue(): Wrong type");
+ return false;
+}
+
+// virtual
+SfxBoolItem* SfxBoolItem::Clone(SfxItemPool *) const
+{
+ return new SfxBoolItem(*this);
+}
+
+// virtual
+OUString SfxBoolItem::GetValueTextByVal(bool bTheValue) const
+{
+ return bTheValue ? OUString("TRUE") : OUString("FALSE");
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/items/cintitem.cxx b/svl/source/items/cintitem.cxx
new file mode 100644
index 0000000000..d9a77adc2a
--- /dev/null
+++ b/svl/source/items/cintitem.cxx
@@ -0,0 +1,209 @@
+/* -*- 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/Any.hxx>
+#include <svl/cintitem.hxx>
+#include <sal/log.hxx>
+
+
+// virtual
+bool CntByteItem::operator ==(const SfxPoolItem & rItem) const
+{
+ assert(SfxPoolItem::operator==(rItem));
+ return m_nValue == static_cast< const CntByteItem * >(&rItem)->m_nValue;
+}
+
+// virtual
+bool CntByteItem::GetPresentation(SfxItemPresentation, MapUnit, MapUnit,
+ OUString & rText,
+ const IntlWrapper&) const
+{
+ rText = OUString::number( m_nValue );
+ return true;
+}
+
+// virtual
+bool CntByteItem::QueryValue(css::uno::Any& rVal, sal_uInt8) const
+{
+ sal_Int8 nValue = m_nValue;
+ rVal <<= nValue;
+ return true;
+}
+
+// virtual
+bool CntByteItem::PutValue(const css::uno::Any& rVal, sal_uInt8)
+{
+ sal_Int8 nValue = sal_Int8();
+ if (rVal >>= nValue)
+ {
+ m_nValue = nValue;
+ return true;
+ }
+
+ SAL_WARN("svl.items", "CntByteItem::PutValue - Wrong type!");
+ return false;
+}
+
+// virtual
+CntByteItem* CntByteItem::Clone(SfxItemPool *) const
+{
+ return new CntByteItem(*this);
+}
+
+// virtual
+bool CntUInt16Item::operator ==(const SfxPoolItem & rItem) const
+{
+ assert(SfxPoolItem::operator==(rItem));
+ return m_nValue == static_cast<const CntUInt16Item *>(&rItem)->m_nValue;
+}
+
+// virtual
+bool CntUInt16Item::GetPresentation(SfxItemPresentation,
+ MapUnit, MapUnit,
+ OUString & rText,
+ const IntlWrapper&)
+ const
+{
+ rText = OUString::number( m_nValue );
+ return true;
+}
+
+// virtual
+bool CntUInt16Item::QueryValue(css::uno::Any& rVal, sal_uInt8) const
+{
+ sal_Int32 nValue = m_nValue;
+ rVal <<= nValue;
+ return true;
+}
+
+// virtual
+bool CntUInt16Item::PutValue(const css::uno::Any& rVal, sal_uInt8)
+{
+ sal_Int32 nValue = 0;
+ if (rVal >>= nValue)
+ {
+ SAL_WARN_IF(nValue < 0 || nValue > SAL_MAX_UINT16, "svl.items", "Overflow in UInt16 value!");
+ m_nValue = static_cast<sal_uInt16>(nValue);
+ return true;
+ }
+
+ SAL_WARN("svl.items", "CntUInt16Item::PutValue - Wrong type!");
+ return false;
+}
+
+// virtual
+CntUInt16Item* CntUInt16Item::Clone(SfxItemPool *) const
+{
+ return new CntUInt16Item(*this);
+}
+
+// virtual
+bool CntInt32Item::operator ==(const SfxPoolItem & rItem) const
+{
+ assert(SfxPoolItem::operator==(rItem));
+ return m_nValue == static_cast<const CntInt32Item *>(&rItem)->m_nValue;
+}
+
+// virtual
+bool CntInt32Item::GetPresentation(SfxItemPresentation,
+ MapUnit, MapUnit,
+ OUString & rText,
+ const IntlWrapper&) const
+{
+ rText = OUString::number( m_nValue );
+ return true;
+}
+
+// virtual
+bool CntInt32Item::QueryValue(css::uno::Any& rVal, sal_uInt8) const
+{
+ sal_Int32 nValue = m_nValue;
+ rVal <<= nValue;
+ return true;
+}
+
+// virtual
+bool CntInt32Item::PutValue(const css::uno::Any& rVal, sal_uInt8)
+{
+ sal_Int32 nValue = 0;
+ if (rVal >>= nValue)
+ {
+ m_nValue = nValue;
+ return true;
+ }
+
+ SAL_WARN("svl.items", "CntInt32Item::PutValue - Wrong type!");
+ return false;
+}
+
+// virtual
+CntInt32Item* CntInt32Item::Clone(SfxItemPool *) const
+{
+ return new CntInt32Item(*this);
+}
+
+// virtual
+bool CntUInt32Item::operator ==(const SfxPoolItem & rItem) const
+{
+ assert(SfxPoolItem::operator==(rItem));
+ return m_nValue == static_cast<const CntUInt32Item *>(&rItem)->m_nValue;
+}
+
+// virtual
+bool CntUInt32Item::GetPresentation(SfxItemPresentation,
+ MapUnit, MapUnit,
+ OUString & rText,
+ const IntlWrapper&)
+ const
+{
+ rText = OUString::number(m_nValue);
+ return true;
+}
+
+// virtual
+bool CntUInt32Item::QueryValue(css::uno::Any& rVal, sal_uInt8) const
+{
+ sal_Int32 nValue = m_nValue;
+ SAL_WARN_IF(nValue < 0, "svl.items", "Overflow in UInt32 value!");
+ rVal <<= nValue;
+ return true;
+}
+
+// virtual
+bool CntUInt32Item::PutValue(const css::uno::Any& rVal, sal_uInt8)
+{
+ sal_Int32 nValue = 0;
+ if (rVal >>= nValue)
+ {
+ SAL_WARN_IF(nValue < 0, "svl.items", "Overflow in UInt32 value!");
+ m_nValue = nValue;
+ return true;
+ }
+
+ SAL_WARN("svl.items", "CntUInt32Item::PutValue - Wrong type!");
+ return false;
+}
+
+// virtual
+CntUInt32Item* CntUInt32Item::Clone(SfxItemPool *) const
+{
+ return new CntUInt32Item(*this);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/items/custritm.cxx b/svl/source/items/custritm.cxx
new file mode 100644
index 0000000000..0d68b6d455
--- /dev/null
+++ b/svl/source/items/custritm.cxx
@@ -0,0 +1,72 @@
+/* -*- 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/Any.hxx>
+
+#include <osl/diagnose.h>
+#include <unotools/intlwrapper.hxx>
+#include <svl/custritm.hxx>
+
+
+// virtual
+bool CntUnencodedStringItem::operator ==(const SfxPoolItem & rItem) const
+{
+ assert(SfxPoolItem::operator==(rItem));
+ return m_aValue
+ == static_cast< const CntUnencodedStringItem * >(&rItem)->
+ m_aValue;
+}
+
+// virtual
+bool CntUnencodedStringItem::GetPresentation(SfxItemPresentation, MapUnit,
+ MapUnit, OUString & rText,
+ const IntlWrapper&) const
+{
+ rText = m_aValue;
+ return true;
+}
+
+// virtual
+bool CntUnencodedStringItem::QueryValue(css::uno::Any& rVal, sal_uInt8) const
+{
+ rVal <<= m_aValue;
+ return true;
+}
+
+// virtual
+bool CntUnencodedStringItem::PutValue(const css::uno::Any& rVal,
+ sal_uInt8)
+{
+ OUString aTheValue;
+ if (rVal >>= aTheValue)
+ {
+ m_aValue = aTheValue;
+ return true;
+ }
+ OSL_FAIL("CntUnencodedStringItem::PutValue(): Wrong type");
+ return false;
+}
+
+// virtual
+CntUnencodedStringItem* CntUnencodedStringItem::Clone(SfxItemPool *) const
+{
+ return new CntUnencodedStringItem(*this);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/items/flagitem.cxx b/svl/source/items/flagitem.cxx
new file mode 100644
index 0000000000..270be1e575
--- /dev/null
+++ b/svl/source/items/flagitem.cxx
@@ -0,0 +1,67 @@
+/* -*- 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 <svl/flagitem.hxx>
+#include <svl/poolitem.hxx>
+#include <sal/log.hxx>
+
+
+SfxFlagItem::SfxFlagItem( sal_uInt16 nW, sal_uInt16 nV ) :
+ SfxPoolItem( nW ),
+ nVal(nV)
+{
+}
+
+bool SfxFlagItem::GetPresentation
+(
+ SfxItemPresentation /*ePresentation*/,
+ MapUnit /*eCoreMetric*/,
+ MapUnit /*ePresentationMetric*/,
+ OUString& rText,
+ const IntlWrapper&
+) const
+{
+ rText.clear();
+ for ( sal_uInt8 nFlag = 0; nFlag < GetFlagCount(); ++nFlag )
+ rText += GetFlag(nFlag) ? std::u16string_view(u"true") : std::u16string_view(u"false");
+ return true;
+}
+
+sal_uInt8 SfxFlagItem::GetFlagCount() const
+{
+ SAL_INFO("svl", "calling GetValueText(sal_uInt16) on SfxFlagItem -- override!");
+ return 0;
+}
+
+bool SfxFlagItem::operator==( const SfxPoolItem& rItem ) const
+{
+ assert(SfxPoolItem::operator==(rItem));
+ return static_cast<const SfxFlagItem&>(rItem).nVal == nVal;
+}
+
+SfxFlagItem* SfxFlagItem::Clone(SfxItemPool *) const
+{
+ return new SfxFlagItem( *this );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/items/globalnameitem.cxx b/svl/source/items/globalnameitem.cxx
new file mode 100644
index 0000000000..3f8d3265d7
--- /dev/null
+++ b/svl/source/items/globalnameitem.cxx
@@ -0,0 +1,90 @@
+/* -*- 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/Any.hxx>
+#include <com/sun/star/uno/Sequence.hxx>
+#include <com/sun/star/script/Converter.hpp>
+
+#include <osl/diagnose.h>
+
+#include <comphelper/processfactory.hxx>
+
+#include <svl/globalnameitem.hxx>
+
+SfxPoolItem* SfxGlobalNameItem::CreateDefault() { return new SfxGlobalNameItem; }
+
+
+SfxGlobalNameItem::SfxGlobalNameItem()
+{
+}
+
+
+SfxGlobalNameItem::SfxGlobalNameItem( sal_uInt16 nW, const SvGlobalName& rName )
+: SfxPoolItem( nW ),
+ m_aName( rName )
+{
+}
+
+SfxGlobalNameItem::~SfxGlobalNameItem()
+{
+}
+
+bool SfxGlobalNameItem::operator==( const SfxPoolItem& rItem ) const
+{
+ return SfxPoolItem::operator==(rItem) &&
+ static_cast<const SfxGlobalNameItem&>(rItem).m_aName == m_aName;
+}
+
+SfxGlobalNameItem* SfxGlobalNameItem::Clone(SfxItemPool *) const
+{
+ return new SfxGlobalNameItem( *this );
+}
+
+// virtual
+bool SfxGlobalNameItem::PutValue( const css::uno::Any& rVal, sal_uInt8 )
+{
+ css::uno::Reference < css::script::XTypeConverter > xConverter
+ ( css::script::Converter::create( ::comphelper::getProcessComponentContext() ));
+ css::uno::Sequence< sal_Int8 > aSeq;
+ css::uno::Any aNew;
+
+ try { aNew = xConverter->convertTo( rVal, cppu::UnoType<css::uno::Sequence < sal_Int8 >>::get() ); }
+ catch (css::uno::Exception&) {}
+ aNew >>= aSeq;
+ if ( aSeq.getLength() == 16 )
+ {
+ m_aName.MakeFromMemory( aSeq.getConstArray() );
+ return true;
+ }
+
+ OSL_FAIL( "SfxGlobalNameItem::PutValue - Wrong type!" );
+ return true;
+}
+
+// virtual
+bool SfxGlobalNameItem::QueryValue( css::uno::Any& rVal, sal_uInt8 ) const
+{
+ css::uno::Sequence< sal_Int8 > aSeq( 16 );
+ void const * pData = &m_aName.GetCLSID();
+ memcpy( aSeq.getArray(), pData, 16 );
+ rVal <<= aSeq;
+ return true;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/items/grabbagitem.cxx b/svl/source/items/grabbagitem.cxx
new file mode 100644
index 0000000000..39ee566866
--- /dev/null
+++ b/svl/source/items/grabbagitem.cxx
@@ -0,0 +1,70 @@
+/* -*- 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 <svl/grabbagitem.hxx>
+#include <sal/config.h>
+
+#include <sal/log.hxx>
+#include <com/sun/star/beans/PropertyValue.hpp>
+#include <com/sun/star/uno/Sequence.hxx>
+
+using namespace com::sun::star;
+
+SfxGrabBagItem::SfxGrabBagItem() = default;
+
+SfxGrabBagItem::SfxGrabBagItem(sal_uInt16 nWhich)
+ : SfxPoolItem(nWhich)
+{
+}
+
+SfxGrabBagItem::~SfxGrabBagItem() = default;
+
+bool SfxGrabBagItem::operator==(const SfxPoolItem& rItem) const
+{
+ return SfxPoolItem::operator==(rItem)
+ && m_aMap == static_cast<const SfxGrabBagItem*>(&rItem)->m_aMap;
+}
+
+SfxGrabBagItem* SfxGrabBagItem::Clone(SfxItemPool* /*pPool*/) const
+{
+ return new SfxGrabBagItem(*this);
+}
+
+bool SfxGrabBagItem::PutValue(const uno::Any& rVal, sal_uInt8 /*nMemberId*/)
+{
+ uno::Sequence<beans::PropertyValue> aValue;
+ if (rVal >>= aValue)
+ {
+ m_aMap.clear();
+ for (beans::PropertyValue const& aPropertyValue : std::as_const(aValue))
+ {
+ m_aMap[aPropertyValue.Name] = aPropertyValue.Value;
+ }
+ return true;
+ }
+
+ SAL_WARN("svl", "SfxGrabBagItem::PutValue: wrong type");
+ return false;
+}
+
+bool SfxGrabBagItem::QueryValue(uno::Any& rVal, sal_uInt8 /*nMemberId*/) const
+{
+ uno::Sequence<beans::PropertyValue> aValue(m_aMap.size());
+ beans::PropertyValue* pValue = aValue.getArray();
+ for (const auto& i : m_aMap)
+ {
+ pValue[0].Name = i.first;
+ pValue[0].Value = i.second;
+ ++pValue;
+ }
+ rVal <<= aValue;
+ return true;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/items/ilstitem.cxx b/svl/source/items/ilstitem.cxx
new file mode 100644
index 0000000000..0cb9ea8e6c
--- /dev/null
+++ b/svl/source/items/ilstitem.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 <com/sun/star/script/Converter.hpp>
+
+#include <comphelper/processfactory.hxx>
+#include <comphelper/sequence.hxx>
+
+#include <svl/ilstitem.hxx>
+
+
+SfxPoolItem* SfxIntegerListItem::CreateDefault() { return new SfxIntegerListItem; }
+
+SfxIntegerListItem::SfxIntegerListItem()
+{
+}
+
+SfxIntegerListItem::SfxIntegerListItem( sal_uInt16 which, ::std::vector < sal_Int32 >&& rList )
+ : SfxPoolItem( which )
+ , m_aList( std::move(rList) )
+{
+}
+
+SfxIntegerListItem::SfxIntegerListItem( sal_uInt16 which, const css::uno::Sequence < sal_Int32 >& rList )
+ : SfxPoolItem( which )
+{
+ comphelper::sequenceToContainer(m_aList, rList);
+}
+
+SfxIntegerListItem::~SfxIntegerListItem()
+{
+}
+
+bool SfxIntegerListItem::operator==( const SfxPoolItem& rPoolItem ) const
+{
+ if ( !SfxPoolItem::operator==(rPoolItem) )
+ return false;
+
+ const SfxIntegerListItem & rItem = static_cast<const SfxIntegerListItem&>(rPoolItem);
+ return rItem.m_aList == m_aList;
+}
+
+SfxIntegerListItem* SfxIntegerListItem::Clone( SfxItemPool * ) const
+{
+ return new SfxIntegerListItem( *this );
+}
+
+bool SfxIntegerListItem::PutValue ( const css::uno::Any& rVal, sal_uInt8 )
+{
+ css::uno::Reference < css::script::XTypeConverter > xConverter
+ ( css::script::Converter::create(::comphelper::getProcessComponentContext()) );
+ css::uno::Any aNew;
+ try { aNew = xConverter->convertTo( rVal, cppu::UnoType<css::uno::Sequence < sal_Int32 >>::get() ); }
+ catch (css::uno::Exception&)
+ {
+ return true;
+ }
+
+ css::uno::Sequence<sal_Int32> aTempSeq;
+ bool bRet = aNew >>= aTempSeq;
+ if (bRet)
+ m_aList = comphelper::sequenceToContainer<std::vector<sal_Int32>>(aTempSeq);
+ return bRet;
+}
+
+bool SfxIntegerListItem::QueryValue( css::uno::Any& rVal, sal_uInt8 ) const
+{
+ rVal <<= comphelper::containerToSequence(m_aList);
+ return true;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/items/imageitm.cxx b/svl/source/items/imageitm.cxx
new file mode 100644
index 0000000000..cba4b7103b
--- /dev/null
+++ b/svl/source/items/imageitm.cxx
@@ -0,0 +1,84 @@
+/* -*- 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 <svl/imageitm.hxx>
+#include <com/sun/star/uno/Sequence.hxx>
+
+
+SfxPoolItem* SfxImageItem::CreateDefault() { return new SfxImageItem; }
+
+SfxImageItem::SfxImageItem( sal_uInt16 which )
+ : SfxInt16Item( which, 0 ), mnAngle(0), mbMirrored(false)
+{
+}
+
+SfxImageItem::SfxImageItem( const SfxImageItem& rItem )
+ : SfxInt16Item( rItem ),
+ mnAngle(rItem.mnAngle), mbMirrored(rItem.mbMirrored)
+{
+}
+
+SfxImageItem::~SfxImageItem()
+{
+}
+
+SfxImageItem* SfxImageItem::Clone( SfxItemPool* ) const
+{
+ return new SfxImageItem( *this );
+}
+
+bool SfxImageItem::operator==( const SfxPoolItem& rItem ) const
+{
+ if (!SfxInt16Item::operator==(rItem))
+ return false;
+ const SfxImageItem& rOther = static_cast<const SfxImageItem&>(rItem);
+ return mnAngle == rOther.mnAngle && mbMirrored == rOther.mbMirrored;
+}
+
+bool SfxImageItem::QueryValue( css::uno::Any& rVal, sal_uInt8 ) const
+{
+ css::uno::Sequence< css::uno::Any > aSeq{ css::uno::Any(GetValue()),
+ css::uno::Any(sal_Int16(mnAngle)),
+ css::uno::Any(mbMirrored),
+ css::uno::Any(maURL) };
+ rVal <<= aSeq;
+ return true;
+}
+
+bool SfxImageItem::PutValue( const css::uno::Any& rVal, sal_uInt8 )
+{
+ css::uno::Sequence< css::uno::Any > aSeq;
+ if (( rVal >>= aSeq ) && ( aSeq.getLength() == 4 ))
+ {
+ sal_Int16 nVal = sal_Int16();
+ if ( aSeq[0] >>= nVal )
+ SetValue( nVal );
+ sal_Int16 nTmp = 0;
+ aSeq[1] >>= nTmp;
+ mnAngle = Degree10(nTmp);
+ aSeq[2] >>= mbMirrored;
+ aSeq[3] >>= maURL;
+ return true;
+ }
+
+ return false;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/items/int64item.cxx b/svl/source/items/int64item.cxx
new file mode 100644
index 0000000000..06efd1fc9a
--- /dev/null
+++ b/svl/source/items/int64item.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/.
+ */
+
+#include <svl/int64item.hxx>
+
+SfxInt64Item::SfxInt64Item( sal_uInt16 nWhich, sal_Int64 nVal ) :
+ SfxPoolItem(nWhich), mnValue(nVal)
+{
+}
+
+SfxInt64Item::~SfxInt64Item() {}
+
+bool SfxInt64Item::operator== ( const SfxPoolItem& rItem ) const
+{
+ return SfxPoolItem::operator==(rItem) && mnValue == static_cast<const SfxInt64Item&>(rItem).mnValue;
+}
+
+bool SfxInt64Item::GetPresentation(
+ SfxItemPresentation, MapUnit, MapUnit, OUString& rText,
+ const IntlWrapper& /*rIntlWrapper*/ ) const
+{
+ rText = OUString::number(mnValue);
+ return true;
+}
+
+bool SfxInt64Item::QueryValue(
+ css::uno::Any& rVal, sal_uInt8 /*nMemberId*/ ) const
+{
+ rVal <<= mnValue;
+ return true;
+}
+
+bool SfxInt64Item::PutValue(
+ const css::uno::Any& rVal, sal_uInt8 /*nMemberId*/ )
+{
+ sal_Int64 nVal;
+
+ if (rVal >>= nVal)
+ {
+ mnValue = nVal;
+ return true;
+ }
+
+ return false;
+}
+
+SfxInt64Item* SfxInt64Item::Clone( SfxItemPool* /*pOther*/ ) const
+{
+ return new SfxInt64Item(*this);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/items/intitem.cxx b/svl/source/items/intitem.cxx
new file mode 100644
index 0000000000..0ced3eb201
--- /dev/null
+++ b/svl/source/items/intitem.cxx
@@ -0,0 +1,183 @@
+/* -*- 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 <svl/intitem.hxx>
+#include <com/sun/star/uno/Any.hxx>
+#include <osl/diagnose.h>
+#include <tools/bigint.hxx>
+#include <svl/metitem.hxx>
+#include <libxml/xmlwriter.h>
+#include <boost/property_tree/ptree.hpp>
+
+
+
+
+SfxPoolItem* SfxByteItem::CreateDefault()
+{
+ return new SfxByteItem();
+};
+
+SfxPoolItem* SfxInt16Item::CreateDefault()
+{
+ return new SfxInt16Item();
+};
+
+// virtual
+bool SfxInt16Item::operator ==(const SfxPoolItem & rItem) const
+{
+ assert(SfxPoolItem::operator==(rItem));
+ return m_nValue == static_cast< const SfxInt16Item * >(&rItem)->
+ m_nValue;
+}
+
+// virtual
+bool SfxInt16Item::GetPresentation(SfxItemPresentation,
+ MapUnit, MapUnit,
+ OUString & rText,
+ const IntlWrapper&) const
+{
+ rText = OUString::number(m_nValue);
+ return true;
+}
+
+
+boost::property_tree::ptree SfxInt16Item::dumpAsJSON() const
+{
+ boost::property_tree::ptree aTree = SfxPoolItem::dumpAsJSON();
+ aTree.put("state", GetValue());
+ return aTree;
+}
+
+
+// virtual
+bool SfxInt16Item::QueryValue(css::uno::Any& rVal, sal_uInt8) const
+{
+ sal_Int16 nValue = m_nValue;
+ rVal <<= nValue;
+ return true;
+}
+
+// virtual
+bool SfxInt16Item::PutValue(const css::uno::Any& rVal, sal_uInt8 )
+{
+ sal_Int16 nValue = sal_Int16();
+ if (rVal >>= nValue)
+ {
+ m_nValue = nValue;
+ return true;
+ }
+
+ OSL_FAIL( "SfxInt16Item::PutValue - Wrong type!" );
+ return false;
+}
+
+SfxInt16Item* SfxInt16Item::Clone(SfxItemPool *) const
+{
+ return new SfxInt16Item(*this);
+}
+
+SfxPoolItem* SfxUInt16Item::CreateDefault()
+{
+ return new SfxUInt16Item();
+};
+
+void SfxUInt16Item::dumpAsXml(xmlTextWriterPtr pWriter) const
+{
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SfxUInt16Item"));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("whichId"), BAD_CAST(OString::number(Which()).getStr()));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), BAD_CAST(OString::number(GetValue()).getStr()));
+ (void)xmlTextWriterEndElement(pWriter);
+}
+
+boost::property_tree::ptree SfxUInt16Item::dumpAsJSON() const
+{
+ boost::property_tree::ptree aTree = SfxPoolItem::dumpAsJSON();
+ aTree.put("state", GetValue());
+ return aTree;
+}
+
+
+
+
+SfxPoolItem* SfxInt32Item::CreateDefault()
+{
+ return new SfxInt32Item();
+};
+
+void SfxInt32Item::dumpAsXml(xmlTextWriterPtr pWriter) const
+{
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SfxInt32Item"));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("whichId"), BAD_CAST(OString::number(Which()).getStr()));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), BAD_CAST(OString::number(GetValue()).getStr()));
+ (void)xmlTextWriterEndElement(pWriter);
+}
+
+boost::property_tree::ptree SfxInt32Item::dumpAsJSON() const
+{
+ boost::property_tree::ptree aTree = SfxPoolItem::dumpAsJSON();
+ aTree.put("state", GetValue());
+ return aTree;
+}
+
+
+
+
+SfxPoolItem* SfxUInt32Item::CreateDefault()
+{
+ return new SfxUInt32Item();
+};
+
+void SfxUInt32Item::dumpAsXml(xmlTextWriterPtr pWriter) const
+{
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SfxUInt32Item"));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("whichId"), BAD_CAST(OString::number(Which()).getStr()));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), BAD_CAST(OString::number(GetValue()).getStr()));
+ (void)xmlTextWriterEndElement(pWriter);
+}
+
+boost::property_tree::ptree SfxUInt32Item::dumpAsJSON() const
+{
+ boost::property_tree::ptree aTree = SfxPoolItem::dumpAsJSON();
+ aTree.put("state", GetValue());
+ return aTree;
+}
+
+SfxMetricItem::SfxMetricItem(sal_uInt16 which, sal_Int32 nValue):
+ SfxInt32Item(which, nValue)
+{
+}
+
+// virtual
+void SfxMetricItem::ScaleMetrics(tools::Long nMult, tools::Long nDiv)
+{
+ BigInt aTheValue(GetValue());
+ aTheValue *= nMult;
+ aTheValue += nDiv / 2;
+ aTheValue /= nDiv;
+ SetValue(aTheValue);
+}
+
+// virtual
+bool SfxMetricItem::HasMetrics() const
+{
+ return true;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/items/itemiter.cxx b/svl/source/items/itemiter.cxx
new file mode 100644
index 0000000000..d8864c387e
--- /dev/null
+++ b/svl/source/items/itemiter.cxx
@@ -0,0 +1,84 @@
+/* -*- 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 <svl/itemiter.hxx>
+#include <svl/itemset.hxx>
+
+SfxItemIter::SfxItemIter(const SfxItemSet& rItemSet)
+ : m_rSet(rItemSet)
+{
+ if (!m_rSet.m_nCount)
+ {
+ m_nStart = 0;
+ m_nEnd = 0;
+ }
+ else
+ {
+ SfxPoolItem const** ppFnd = m_rSet.m_ppItems;
+
+ // Find the first Item that is set
+ for (m_nStart = 0; !*(ppFnd + m_nStart); ++m_nStart)
+ ; // empty loop
+ if (1 < m_rSet.Count())
+ for (m_nEnd = m_rSet.TotalCount(); !*(ppFnd + --m_nEnd);)
+ ; // empty loop
+ else
+ m_nEnd = m_nStart;
+ }
+
+ m_nCurrent = m_nStart;
+}
+
+// Precondition : m_nCurrent < m_nEnd
+const SfxPoolItem* SfxItemIter::ImplNextItem()
+{
+ SfxPoolItem const** ppFnd = m_rSet.m_ppItems;
+ do
+ {
+ m_nCurrent++;
+ } while (m_nCurrent < m_nEnd && !*(ppFnd + m_nCurrent));
+ return *(ppFnd + m_nCurrent);
+}
+
+SfxItemState SfxItemIter::GetItemState(bool bSrchInParent, const SfxPoolItem** ppItem) const
+{
+ // we have the offset, so use it to profit. It is always valid, so no need
+ // to check if smaller than TotalCount()
+ SfxItemState eState(m_rSet.GetItemState_ForOffset(m_nCurrent, ppItem));
+
+ // search in parent?
+ if (bSrchInParent && nullptr != m_rSet.GetParent()
+ && (SfxItemState::UNKNOWN == eState || SfxItemState::DEFAULT == eState))
+ {
+ // nOffset was only valid for *local* SfxItemSet, need to continue with WhichID
+ const sal_uInt16 nWhich(m_rSet.GetWhichByOffset(m_nCurrent));
+ eState = m_rSet.GetParent()->GetItemState_ForWhichID(eState, nWhich, true, ppItem);
+ }
+
+ return eState;
+}
+
+void SfxItemIter::ClearItem()
+{
+ // we have the offset, so use it to profit. It is always valid, so no need
+ // to check if smaller than TotalCount()
+ const_cast<SfxItemSet&>(m_rSet).ClearSingleItem_ForOffset(m_nCurrent);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/items/itempool.cxx b/svl/source/items/itempool.cxx
new file mode 100644
index 0000000000..faedbd516a
--- /dev/null
+++ b/svl/source/items/itempool.cxx
@@ -0,0 +1,1253 @@
+/* -*- 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 <svl/itempool.hxx>
+#include <svl/setitem.hxx>
+
+#include <string.h>
+#include <libxml/xmlwriter.h>
+
+#include <osl/diagnose.h>
+#include <sal/log.hxx>
+#include <svl/SfxBroadcaster.hxx>
+#include <svl/hint.hxx>
+#include <svl/itemset.hxx>
+
+#include <poolio.hxx>
+
+#include <cassert>
+#include <vector>
+
+#ifdef DBG_UTIL
+static size_t nAllDirectlyPooledSfxPoolItemCount(0);
+static size_t nRemainingDirectlyPooledSfxPoolItemCount(0);
+size_t getAllDirectlyPooledSfxPoolItemCount() { return nAllDirectlyPooledSfxPoolItemCount; }
+size_t getRemainingDirectlyPooledSfxPoolItemCount() { return nRemainingDirectlyPooledSfxPoolItemCount; }
+#endif
+
+// WhichIDs that need to set _bNeedsPoolRegistration in SfxItemInfo
+// to true to allow a register of all items of that type/with that WhichID
+// to be accessible using SfxItemPool::GetItemSurrogates. Created by
+// grepping for 'GetItemSurrogates' usages & interpreting. Some
+// are double, more may be necessary. There is a SAL_INFO("svl.items", ...)
+// in SfxItemPool::GetItemSurrogates that will give hints on missing flags.
+//
+// due to SwTable::UpdateFields
+// due to SwCursorShell::GotoNxtPrvTableFormula
+// due to DocumentFieldsManager::UpdateTableFields
+// due to SwTable::GatherFormulas
+// RES_BOXATR_FORMULA ok
+// due to SwContentTree::EditEntry
+// due to SwDoc::FindINetAttr
+// due to SwUndoResetAttr::RedoImpl
+// due to SwContentTree::EditEntry
+// due to SwContentTree::BringEntryToAttention
+// RES_TXTATR_REFMARK ok
+// due to ImpEditEngine::WriteRTF
+// due to ScDocument::UpdateFontCharSet()
+// due to ScXMLFontAutoStylePool_Impl
+// due to SdXImpressDocument::getPropertyValue
+// due to Writer::AddFontItems_
+// EE_CHAR_FONTINFO ok
+// due to ImpEditEngine::WriteRTF
+// due to ScXMLFontAutoStylePool_Impl
+// due to SdXImpressDocument::getPropertyValue
+// due to Writer::AddFontItems_
+// EE_CHAR_FONTINFO_CJK ok
+// due to ImpEditEngine::WriteRTF
+// due to ScXMLFontAutoStylePool_Impl
+// due to SdXImpressDocument::getPropertyValue
+// due to Writer::AddFontItems_
+// EE_CHAR_FONTINFO_CTL ok
+// due to ImpEditEngine::WriteRTF
+// EE_CHAR_COLOR ok
+// due to ScDocumentPool::StyleDeleted
+// ATTR_PATTERN ok
+// due to ScDocument::UpdateFontCharSet()
+// due to ScXMLFontAutoStylePool_Impl
+// ATTR_FONT ok
+// due to OptimizeHasAttrib
+// ATTR_ROTATE_VALUE ok
+// due to ScDocument::GetDocColors()
+// ATTR_BACKGROUND ok
+// ATTR_FONT_COLOR ok
+// due to ScXMLExport::CollectUserDefinedNamespaces
+// ATTR_USERDEF ok
+// due to ScXMLExport::CollectUserDefinedNamespaces
+// due to SwXMLExport::exportDoc
+// EE_PARA_XMLATTRIBS ok
+// due to ScXMLExport::CollectUserDefinedNamespaces
+// due to SwXMLExport::exportDoc
+// EE_CHAR_XMLATTRIBS ok
+// due to ScXMLExport::CollectUserDefinedNamespaces
+// due to SwXMLExport::exportDoc
+// SDRATTR_XMLATTRIBUTES ok
+// due to ScXMLFontAutoStylePool_Impl
+// ATTR_CJK_FONT ok
+// ATTR_CTL_FONT ok
+// ATTR_PAGE_HEADERLEFT ok
+// ATTR_PAGE_FOOTERLEFT ok
+// ATTR_PAGE_HEADERRIGHT ok
+// ATTR_PAGE_FOOTERRIGHT ok
+// ATTR_PAGE_HEADERFIRST ok
+// ATTR_PAGE_FOOTERFIRST ok
+// due to ScCellShell::ExecuteEdit
+// due to ScTabViewShell::CreateRefDialogController
+// SCITEM_CONDFORMATDLGDATA ok
+// due to SdDrawDocument::UpdatePageRelativeURLs
+// EE_FEATURE_FIELD ok
+// due to SvxUnoMarkerTable::replaceByName
+// due to SvxShape::setPropertyValueImpl/SvxShape::SetFillAttribute
+// due to XLineStartItem::checkForUniqueItem
+// XATTR_LINESTART ok
+// due to SvxUnoMarkerTable::replaceByName
+// due to SvxShape::setPropertyValueImpl/SvxShape::SetFillAttribute
+// due to XLineStartItem::checkForUniqueItem
+// XATTR_LINEEND ok
+// due to SvxUnoNameItemTable
+// due to SvxShape::setPropertyValueImpl/SvxShape::SetFillAttribute
+// due to NameOrIndex::CheckNamedItem all derived from NameOrIndex
+// XATTR_FILLBITMAP ok
+// due to SvxUnoNameItemTable
+// due to SvxShape::setPropertyValueImpl/SvxShape::SetFillAttribute
+// XATTR_LINEDASH ok
+// due to SvxUnoNameItemTable
+// due to SvxShape::setPropertyValueImpl/SvxShape::SetFillAttribute
+// due to NameOrIndex::CheckNamedItem all derived from NameOrIndex
+// XATTR_FILLGRADIENT ok
+// due to SvxUnoNameItemTable
+// due to SvxShape::setPropertyValueImpl/SvxShape::SetFillAttribute
+// XATTR_FILLHATCH ok
+// due to SvxUnoNameItemTable
+// due to SvxShape::setPropertyValueImpl/SvxShape::SetFillAttribute
+// XATTR_FILLFLOATTRANSPARENCE ok
+// due to NamespaceIteratorImpl
+// -> needs to be evaluated
+// due to SwCursorShell::GotoNxtPrvTOXMark
+// due to SwDoc::GetTOIKeys
+// RES_TXTATR_TOXMARK ok
+// due to SwDoc::GetRefMark
+// due to SwDoc::CallEvent
+// due to SwURLStateChanged::Notify
+// due to SwHTMLWriter::CollectLinkTargets
+// due to MSWordExportBase::CollectOutlineBookmarks
+// RES_TXTATR_INETFMT ok
+// due to SwDoc::GetAllUsedDB
+// due to lcl_FindInputField
+// due to SwViewShell::IsAnyFieldInDoc
+// RES_TXTATR_FIELD ok
+// due to SwDoc::GetAllUsedDB
+// due to lcl_FindInputField
+// due to SwViewShell::IsAnyFieldInDoc
+// RES_TXTATR_INPUTFIELD ok
+// due to SwDoc::SetDefault
+// RES_PARATR_TABSTOP ok
+// due to SwDoc::GetDocColors()
+// due to RtfExport::OutColorTable
+// RES_CHRATR_COLOR ok
+// due to SwDoc::GetDocColors()
+// RES_CHRATR_HIGHLIGHT ok
+// due to SwDoc::GetDocColors()
+// RES_BACKGROUND ok
+// due to SwNode::FindPageDesc
+// due to SwPageNumberFieldType::ChangeExpansion
+// due to SwFrame::GetVirtPageNum
+// RES_PAGEDESC ok
+// due to SwAutoStylesEnumImpl::
+// RES_TXTATR_CJK_RUBY ok
+// due to SwHTMLWriter::CollectLinkTargets
+// due to MSWordExportBase::CollectOutlineBookmarks
+// RES_URL
+// due to RtfExport::OutColorTable
+// RES_CHRATR_UNDERLINE ok
+// RES_CHRATR_OVERLINE ok
+// RES_CHRATR_BACKGROUND ok
+// RES_SHADOW ok
+// RES_BOX ok
+// RES_CHRATR_BOX ok
+// XATTR_FILLCOLOR ok
+// due to wwFontHelper::InitFontTable
+// due to SwXMLFontAutoStylePool_Impl::SwXMLFontAutoStylePool_Impl
+// RES_CHRATR_FONT ok
+// due to wwFontHelper::InitFontTable
+// due to SwXMLFontAutoStylePool_Impl::SwXMLFontAutoStylePool_Impl
+// RES_CHRATR_CJK_FONT ok
+// due to wwFontHelper::InitFontTable
+// due to SwXMLFontAutoStylePool_Impl::SwXMLFontAutoStylePool_Impl
+// RES_CHRATR_CTL_FONT
+// due to SwXMLExport::exportDoc
+// RES_UNKNOWNATR_CONTAINER ok
+// RES_TXTATR_UNKNOWN_CONTAINER ok
+
+
+
+#if OSL_DEBUG_LEVEL > 0
+#include <map>
+
+static void
+lcl_CheckSlots2(std::map<sal_uInt16, sal_uInt16> & rSlotMap,
+ SfxItemPool const& rPool, SfxItemInfo const* pInfo)
+{
+ if (!pInfo)
+ return; // may not be initialized yet
+ if (rPool.GetName() == "EditEngineItemPool")
+ return; // HACK: this one has loads of duplicates already, ignore it :(
+ sal_uInt16 const nFirst(rPool.GetFirstWhich());
+ sal_uInt16 const nCount(rPool.GetLastWhich() - rPool.GetFirstWhich() + 1);
+ for (sal_uInt16 n = 0; n < nCount; ++n)
+ {
+ sal_uInt16 const nSlotId(pInfo[n]._nSID);
+ if (nSlotId != 0
+ && nSlotId != 10883 // preexisting duplicate SID_ATTR_GRAF_CROP
+ && nSlotId != 10023 // preexisting duplicate SID_ATTR_BORDER_INNER
+ && nSlotId != 10024 // preexisting duplicate SID_ATTR_BORDER_OUTER
+ && nSlotId != 11013 // preexisting duplicate SID_ATTR_BORDER_DIAG_TLBR
+ && nSlotId != 11014) // preexisting duplicate SID_ATTR_BORDER_DIAG_BLTR
+ { // check for duplicate slot-id mapping
+ std::map<sal_uInt16, sal_uInt16>::const_iterator const iter(
+ rSlotMap.find(nSlotId));
+ sal_uInt16 const nWhich(nFirst + n);
+ if (iter != rSlotMap.end())
+ {
+ SAL_WARN("svl", "SfxItemPool: duplicate SlotId " << nSlotId
+ << " mapped to " << iter->second << " and " << nWhich);
+ assert(false);
+ }
+ rSlotMap.insert(std::make_pair(nSlotId, nWhich));
+ }
+ }
+}
+
+#define CHECK_SLOTS() \
+do { \
+ std::map<sal_uInt16, sal_uInt16> slotmap; \
+ for (SfxItemPool * p = pImpl->mpMaster; p; p = p->pImpl->mpSecondary.get()) \
+ { \
+ lcl_CheckSlots2(slotmap, *p, p->pItemInfos); \
+ } \
+} while (false)
+
+#else
+#define CHECK_SLOTS() do {} while (false)
+#endif
+
+sal_uInt16 SfxItemPool::GetFirstWhich() const
+{
+ return pImpl->mnStart;
+}
+
+sal_uInt16 SfxItemPool::GetLastWhich() const
+{
+ return pImpl->mnEnd;
+}
+
+bool SfxItemPool::IsInRange( sal_uInt16 nWhich ) const
+{
+ return nWhich >= pImpl->mnStart && nWhich <= pImpl->mnEnd;
+}
+
+sal_uInt16 SfxItemPool::GetIndex_Impl(sal_uInt16 nWhich) const
+{
+ if (nWhich < pImpl->mnStart || nWhich > pImpl->mnEnd)
+ {
+ assert(false && "missing bounds check before use");
+ return 0;
+ }
+ return nWhich - pImpl->mnStart;
+}
+
+sal_uInt16 SfxItemPool::GetSize_Impl() const
+{
+ return pImpl->mnEnd - pImpl->mnStart + 1;
+}
+
+const SfxPoolItem* SfxItemPool::GetPoolDefaultItem( sal_uInt16 nWhich ) const
+{
+ const SfxPoolItem* pRet;
+ if( IsInRange( nWhich ) )
+ pRet = pImpl->maPoolDefaults[GetIndex_Impl(nWhich)];
+ else if( pImpl->mpSecondary )
+ pRet = pImpl->mpSecondary->GetPoolDefaultItem( nWhich );
+ else
+ {
+ assert(false && "unknown WhichId - cannot get pool default");
+ pRet = nullptr;
+ }
+ return pRet;
+}
+
+
+bool SfxItemPool::NeedsPoolRegistration(sal_uInt16 nWhich) const
+{
+ if (!IsInRange(nWhich))
+ {
+ // get to correct pool
+ if (pImpl->mpSecondary)
+ return pImpl->mpSecondary->NeedsPoolRegistration(nWhich);
+ return false;
+ }
+
+ return NeedsPoolRegistration_Impl(nWhich - pImpl->mnStart);
+}
+
+bool SfxItemPool::Shareable(sal_uInt16 nWhich) const
+{
+ if (!IsInRange(nWhich))
+ {
+ // get to correct pool
+ if (pImpl->mpSecondary)
+ return pImpl->mpSecondary->Shareable(nWhich);
+ return false;
+ }
+
+ return Shareable_Impl(nWhich - pImpl->mnStart);
+}
+
+
+SfxBroadcaster& SfxItemPool::BC()
+{
+ return pImpl->aBC;
+}
+
+
+/**
+ * This is the regular ctor to be used for this class.
+ * An SfxItemPool instance is initialized, which can manage Items in the
+ * range from 'nStartWhich' to 'nEndWhich'.
+ *
+ * For every one of these WhichIds a static Default must be present in the
+ * 'pDefaults' array. They start with an SfxPoolItem (with the WhichId
+ * 'nStartWhich'), are sorted by WhichId and consecutively stored.
+ *
+ * 'pItemInfos' is a USHORT array arranged in the same way, which holds
+ * SlotIds and Flags. These SlotIds can be 0, if the affected Items are
+ * exclusively used in the Core.
+ * The flags allow for e.g. enabling value sharing (poolable).
+ *
+ * If the Pool is supposed to hold SfxSetItems, the ctor cannot yet contain
+ * static Defaults. This needs to be done afterwards, using
+ * @see SfxItemPool::SetDefaults(std::vector<SfxPoolItem*>*).
+ *
+ * @see SfxItemPool::SetDefaults(std::vector<SfxPoolItem*>*)
+ * @see SfxItemPool::ReleaseDefaults(std::vector<SfxPoolItem*>*,bool)
+ * @see SfxItemPool::ReleaseDefaults(bool)
+ */
+SfxItemPool::SfxItemPool
+(
+ const OUString& rName, /* Pool name to identify in the file format */
+ sal_uInt16 nStartWhich, /* First WhichId of the Pool (must be > 0) */
+ sal_uInt16 nEndWhich, /* Last WhichId of the Pool */
+ const SfxItemInfo* pInfo, /* SID Map and Item flags */
+ std::vector<SfxPoolItem*>*
+ pDefaults /* Pointer to static Defaults;
+ is directly referenced by the Pool,
+ but no transfer of ownership */
+) :
+ pItemInfos(pInfo),
+ pImpl( new SfxItemPool_Impl( this, rName, nStartWhich, nEndWhich ) ),
+ ppRegisteredSfxPoolItems(nullptr)
+{
+ pImpl->eDefMetric = MapUnit::MapTwip;
+
+ if ( pDefaults )
+ SetDefaults(pDefaults);
+
+#ifdef DBG_UTIL
+ if (pItemInfos)
+ {
+ auto p = pItemInfos;
+ auto nWhich = nStartWhich;
+ while (nWhich <= nEndWhich)
+ {
+ if (p->_nSID == nWhich)
+ {
+ SAL_WARN("svl.items", "No point mapping a SID to itself, just put a 0 here in the SfxItemInfo array, at index " << (p - pItemInfos));
+ assert(false);
+ }
+ ++p;
+ ++nWhich;
+ }
+ }
+#endif
+}
+
+
+/**
+ * Copy ctor
+ *
+ * @see SfxItemPool::Clone() const
+*/
+SfxItemPool::SfxItemPool
+(
+ const SfxItemPool& rPool, // Copy from this instance
+ bool bCloneStaticDefaults /* true
+ Copy static Defaults
+
+ false
+ Take over static Defaults */
+) :
+ salhelper::SimpleReferenceObject(),
+ pItemInfos(rPool.pItemInfos),
+ pImpl( new SfxItemPool_Impl( this, rPool.pImpl->aName, rPool.pImpl->mnStart, rPool.pImpl->mnEnd ) ),
+ ppRegisteredSfxPoolItems(nullptr)
+{
+ pImpl->eDefMetric = rPool.pImpl->eDefMetric;
+
+ // Take over static Defaults
+ if ( bCloneStaticDefaults )
+ {
+ std::vector<SfxPoolItem *>* ppDefaults = new std::vector<SfxPoolItem*>(pImpl->mnEnd-pImpl->mnStart+1);
+ for ( sal_uInt16 n = 0; n <= pImpl->mnEnd - pImpl->mnStart; ++n )
+ {
+ (*ppDefaults)[n] = (*rPool.pImpl->mpStaticDefaults)[n]->Clone(this);
+ (*ppDefaults)[n]->setStaticDefault();
+ }
+
+ SetDefaults( ppDefaults );
+ }
+ else
+ SetDefaults( rPool.pImpl->mpStaticDefaults );
+
+ // Copy Pool Defaults
+ for (size_t n = 0; n < pImpl->maPoolDefaults.size(); ++n )
+ if (rPool.pImpl->maPoolDefaults[n])
+ {
+ pImpl->maPoolDefaults[n] = rPool.pImpl->maPoolDefaults[n]->Clone(this); //resets kind
+ pImpl->maPoolDefaults[n]->setPoolDefault();
+ }
+
+ // Repair linkage
+ if ( rPool.pImpl->mpSecondary )
+ SetSecondaryPool( rPool.pImpl->mpSecondary->Clone().get() );
+}
+
+void SfxItemPool::SetDefaults( std::vector<SfxPoolItem*>* pDefaults )
+{
+ DBG_ASSERT( pDefaults, "first we ask for it, and then we don't give back..." );
+ DBG_ASSERT( !pImpl->mpStaticDefaults, "already have Defaults" );
+
+ pImpl->mpStaticDefaults = pDefaults;
+ //! if ((*mpStaticDefaults)->GetKind() != SfxItemKind::StaticDefault)
+ //! FIXME: Probably doesn't work with SetItems at the end
+ {
+ DBG_ASSERT( (*pImpl->mpStaticDefaults)[0]->GetRefCount() == 0 ||
+ IsDefaultItem( (*pImpl->mpStaticDefaults)[0] ),
+ "these are not static" );
+ for ( sal_uInt16 n = 0; n <= pImpl->mnEnd - pImpl->mnStart; ++n )
+ {
+ assert( ((*pImpl->mpStaticDefaults)[n]->Which() == n + pImpl->mnStart)
+ && "items ids in pool-ranges and in static-defaults do not match" );
+ (*pImpl->mpStaticDefaults)[n]->setStaticDefault();
+ DBG_ASSERT(nullptr == ppRegisteredSfxPoolItems || nullptr == ppRegisteredSfxPoolItems[n]
+ || ppRegisteredSfxPoolItems[n]->empty(), "defaults with setitems with items?!" );
+ }
+ }
+}
+
+void SfxItemPool::ClearDefaults()
+{
+ pImpl->mpStaticDefaults = nullptr;
+}
+
+/**
+ * Frees the static Defaults of the corresponding SfxItemPool instance
+ * and deletes them if specified.
+ *
+ * The SfxItemPool instance MUST NOT BE USED after this function has
+ * been called; only the dtor must be called.
+ */
+void SfxItemPool::ReleaseDefaults
+(
+ bool bDelete /* true
+ Deletes the array as well as the single static Defaults
+
+ false
+ Neither deletes the array not the single static Defaults */
+)
+
+
+{
+ DBG_ASSERT( pImpl->mpStaticDefaults, "requirements not met" );
+ ReleaseDefaults( pImpl->mpStaticDefaults, bDelete );
+
+ // mpStaticDefaults points to deleted memory if bDelete == true.
+ if ( bDelete )
+ pImpl->mpStaticDefaults = nullptr;
+}
+
+
+/**
+ * Frees the specified static Defaults and also deletes them, if so
+ * specified.
+ *
+ * This method MUST be called AFTER all SfxItemPool instances (which
+ * use the specified static Defaults 'pDefault') have been destroyed.
+ */
+void SfxItemPool::ReleaseDefaults
+(
+ std::vector<SfxPoolItem*>*
+ pDefaults, /* Static Defaults that are to be freed */
+
+ bool bDelete /* true
+ Deletes the array as well as the specified
+ static Defaults
+
+ false
+ Neither deletes the array nor the single
+ static Defaults */
+)
+{
+ DBG_ASSERT( pDefaults, "we first ask for it and the return nothing ..." );
+
+ for ( auto & rpItem : *pDefaults )
+ {
+ assert(IsStaticDefaultItem(rpItem));
+ rpItem->SetRefCount(0);
+ if ( bDelete )
+ {
+ delete rpItem;
+ rpItem = nullptr;
+ }
+ }
+
+ if ( bDelete )
+ {
+ delete pDefaults;
+ pDefaults = nullptr;
+ }
+}
+
+
+SfxItemPool::~SfxItemPool()
+{
+ // Need to be deleted?
+ // Caution: ppRegisteredSfxPoolItems is on-demand created and can be nullptr
+ if ( nullptr != ppRegisteredSfxPoolItems || !pImpl->maPoolDefaults.empty() )
+ Delete();
+
+ if (pImpl->mpMaster != nullptr && pImpl->mpMaster != this)
+ {
+ // This condition indicates an error.
+ // A pImpl->mpMaster->SetSecondaryPool(...) call should have been made
+ // earlier to prevent this. At this point we can only try to
+ // prevent a crash later on.
+ DBG_ASSERT( pImpl->mpMaster == this, "destroying active Secondary-Pool" );
+ if (pImpl->mpMaster->pImpl->mpSecondary == this)
+ pImpl->mpMaster->pImpl->mpSecondary = nullptr;
+ }
+}
+
+void SfxItemPool::SetSecondaryPool( SfxItemPool *pPool )
+{
+ // Reset Master in attached Pools
+ if ( pImpl->mpSecondary )
+ {
+#ifdef DBG_UTIL
+ if (nullptr != pImpl->mpStaticDefaults
+ && nullptr != ppRegisteredSfxPoolItems
+ && nullptr != pImpl->mpSecondary->ppRegisteredSfxPoolItems)
+ // Delete() did not yet run?
+ {
+ // Does the Master have SetItems?
+ bool bHasSetItems(false);
+
+ for (sal_uInt16 i(0); !bHasSetItems && i < pImpl->mnEnd - pImpl->mnStart; ++i)
+ {
+ const SfxPoolItem* pStaticDefaultItem((*pImpl->mpStaticDefaults)[i]);
+ bHasSetItems = pStaticDefaultItem->isSetItem();
+ }
+
+ if (bHasSetItems)
+ {
+ // Detached Pools must be empty
+ registeredSfxPoolItems** ppSet(pImpl->mpSecondary->ppRegisteredSfxPoolItems);
+
+ for (sal_uInt16 a(0); a < pImpl->mpSecondary->GetSize_Impl(); a++, ppSet++)
+ {
+ if (nullptr != *ppSet && (*ppSet)->size() != 0)
+ {
+ SAL_WARN("svl.items", "old secondary pool: " << pImpl->mpSecondary->pImpl->aName
+ << " of pool: " << pImpl->aName << " must be empty.");
+ break;
+ }
+ }
+ }
+ }
+#endif
+
+ pImpl->mpSecondary->pImpl->mpMaster = pImpl->mpSecondary.get();
+ for ( SfxItemPool *p = pImpl->mpSecondary->pImpl->mpSecondary.get(); p; p = p->pImpl->mpSecondary.get() )
+ p->pImpl->mpMaster = pImpl->mpSecondary.get();
+ }
+
+ // Set Master of new Secondary Pools
+ DBG_ASSERT( !pPool || pPool->pImpl->mpMaster == pPool, "Secondary is present in two Pools" );
+ SfxItemPool *pNewMaster = GetMasterPool() ? pImpl->mpMaster : this;
+ for ( SfxItemPool *p = pPool; p; p = p->pImpl->mpSecondary.get() )
+ p->pImpl->mpMaster = pNewMaster;
+
+ // Remember new Secondary Pool
+ pImpl->mpSecondary = pPool;
+
+ CHECK_SLOTS();
+}
+
+void SfxItemPool::SetItemInfos(SfxItemInfo const*const pInfo)
+{
+ pItemInfos = pInfo;
+ CHECK_SLOTS();
+}
+
+
+MapUnit SfxItemPool::GetMetric( sal_uInt16 ) const
+{
+ return pImpl->eDefMetric;
+}
+
+
+void SfxItemPool::SetDefaultMetric( MapUnit eNewMetric )
+{
+// assert((pImpl->eDefMetric == eNewMetric || !pImpl->mpPoolRanges) && "pool already frozen, cannot change metric");
+ pImpl->eDefMetric = eNewMetric;
+}
+
+MapUnit SfxItemPool::GetDefaultMetric() const
+{
+ return pImpl->eDefMetric;
+}
+
+const OUString& SfxItemPool::GetName() const
+{
+ return pImpl->aName;
+}
+
+
+bool SfxItemPool::GetPresentation
+(
+ const SfxPoolItem& rItem,
+ MapUnit eMetric,
+ OUString& rText,
+ const IntlWrapper& rIntlWrapper
+) const
+{
+ return rItem.GetPresentation(
+ SfxItemPresentation::Complete, GetMetric(rItem.Which()), eMetric, rText, rIntlWrapper );
+}
+
+
+rtl::Reference<SfxItemPool> SfxItemPool::Clone() const
+{
+ return new SfxItemPool( *this );
+}
+
+
+void SfxItemPool::Delete()
+{
+ // Already deleted?
+ // Caution: ppRegisteredSfxPoolItems is on-demand created and can be nullptr
+ if (nullptr == ppRegisteredSfxPoolItems && pImpl->maPoolDefaults.empty())
+ return;
+
+ // Inform e.g. running Requests
+ pImpl->aBC.Broadcast( SfxHint( SfxHintId::Dying ) );
+
+ // Iterate through twice: first for the SetItems.
+ if (nullptr != pImpl->mpStaticDefaults && nullptr != ppRegisteredSfxPoolItems)
+ {
+ for (size_t n = 0; n < GetSize_Impl(); ++n)
+ {
+ // *mpStaticDefaultItem could've already been deleted in a class derived
+ // from SfxItemPool
+ // This causes chaos in Itempool!
+ const SfxPoolItem* pStaticDefaultItem((*pImpl->mpStaticDefaults)[n]);
+ if (pStaticDefaultItem->isSetItem() && nullptr != ppRegisteredSfxPoolItems[n])
+ {
+ // SfxSetItem found, remove PoolItems (and defaults) with same ID
+ auto& rArray(*(ppRegisteredSfxPoolItems[n]));
+ for (auto& rItemPtr : rArray)
+ {
+ ReleaseRef(*rItemPtr, rItemPtr->GetRefCount()); // for RefCount check in dtor
+ delete rItemPtr;
+ }
+ rArray.clear();
+ // let pImpl->DeleteItems() delete item arrays in maPoolItems
+ auto& rItemPtr = pImpl->maPoolDefaults[n];
+ if (rItemPtr)
+ {
+#ifdef DBG_UTIL
+ ClearRefCount(*rItemPtr);
+#endif
+ delete rItemPtr;
+ rItemPtr = nullptr;
+ }
+ }
+ }
+ }
+
+ if (nullptr != ppRegisteredSfxPoolItems)
+ {
+ registeredSfxPoolItems** ppSet(ppRegisteredSfxPoolItems);
+
+ for (sal_uInt16 a(0); a < GetSize_Impl(); a++, ppSet++)
+ {
+ if (nullptr != *ppSet)
+ {
+ for (auto& rCandidate : **ppSet)
+ {
+ if (nullptr != rCandidate && !IsDefaultItem(rCandidate))
+ {
+ ReleaseRef(*rCandidate, rCandidate->GetRefCount()); // for RefCount check in dtor
+ delete rCandidate;
+ }
+ }
+
+ delete *ppSet;
+ *ppSet = nullptr;
+ }
+ }
+
+ delete[] ppRegisteredSfxPoolItems;
+ ppRegisteredSfxPoolItems = nullptr;
+ }
+
+ // default items
+ for (auto rItemPtr : pImpl->maPoolDefaults)
+ {
+ if (rItemPtr)
+ {
+#ifdef DBG_UTIL
+ ClearRefCount(*rItemPtr);
+#endif
+ delete rItemPtr;
+ rItemPtr = nullptr;
+ }
+ }
+
+ pImpl->DeleteItems();
+}
+
+
+void SfxItemPool::SetPoolDefaultItem(const SfxPoolItem &rItem)
+{
+ if ( IsInRange(rItem.Which()) )
+ {
+ auto& rOldDefault =
+ pImpl->maPoolDefaults[GetIndex_Impl(rItem.Which())];
+ SfxPoolItem *pNewDefault = rItem.Clone(this);
+ pNewDefault->setPoolDefault();
+ if (rOldDefault)
+ {
+ rOldDefault->SetRefCount(0);
+ delete rOldDefault;
+ rOldDefault = nullptr;
+ }
+ rOldDefault = pNewDefault;
+ }
+ else if ( pImpl->mpSecondary )
+ pImpl->mpSecondary->SetPoolDefaultItem(rItem);
+ else
+ {
+ assert(false && "unknown WhichId - cannot set pool default");
+ }
+}
+
+/**
+ * Resets the default of the given WhichId back to the static Default.
+ * If a pool default exists, it is removed.
+ */
+void SfxItemPool::ResetPoolDefaultItem( sal_uInt16 nWhichId )
+{
+ if ( IsInRange(nWhichId) )
+ {
+ auto& rOldDefault =
+ pImpl->maPoolDefaults[GetIndex_Impl(nWhichId)];
+ if (rOldDefault)
+ {
+ rOldDefault->SetRefCount(0);
+ delete rOldDefault;
+ rOldDefault = nullptr;
+ }
+ }
+ else if ( pImpl->mpSecondary )
+ pImpl->mpSecondary->ResetPoolDefaultItem(nWhichId);
+ else
+ {
+ assert(false && "unknown WhichId - cannot reset pool default");
+ }
+}
+
+const SfxPoolItem& SfxItemPool::DirectPutItemInPoolImpl(const SfxPoolItem& rItem, sal_uInt16 nWhich, bool bPassingOwnership)
+{
+ // CAUTION: Do not register the problematic pool default
+ if (rItem.isExceptionalSCItem() && GetMasterPool()->newItem_UseDirect(rItem))
+ return rItem;
+
+#ifdef DBG_UTIL
+ nAllDirectlyPooledSfxPoolItemCount++;
+ nRemainingDirectlyPooledSfxPoolItemCount++;
+#endif
+
+ // make sure to use 'master'-pool, that's the one used by SfxItemSets
+ const SfxPoolItem* pRetval(implCreateItemEntry(*GetMasterPool(), &rItem, nWhich, bPassingOwnership));
+
+ // For the moment, as long as DirectPutItemInPoolImpl is used, make sure that
+ // the changes in implCreateItemEntry do not change anything, that would
+ // risc memory leaks by not (ab)using the garbage collector aspect of the pool.
+ registerSfxPoolItem(*pRetval);
+
+ return *pRetval;
+}
+
+void SfxItemPool::DirectRemoveItemFromPool(const SfxPoolItem& rItem)
+{
+ // CAUTION: Do not remove the problematic pool default
+ if (rItem.isExceptionalSCItem() && GetMasterPool()->newItem_UseDirect(rItem))
+ return;
+
+#ifdef DBG_UTIL
+ nRemainingDirectlyPooledSfxPoolItemCount--;
+#endif
+
+ // make sure to use 'master'-pool, that's the one used by SfxItemSets
+ implCleanupItemEntry(*GetMasterPool(), &rItem);
+}
+
+void SfxItemPool::newItem_Callback(const SfxPoolItem& rItem) const
+{
+ if (!IsInRange(rItem.Which()) && pImpl->mpSecondary)
+ pImpl->mpSecondary->newItem_Callback(rItem);
+}
+
+bool SfxItemPool::newItem_UseDirect(const SfxPoolItem& rItem) const
+{
+ if (!IsInRange(rItem.Which()) && pImpl->mpSecondary)
+ return pImpl->mpSecondary->newItem_UseDirect(rItem);
+ return false;
+}
+
+const SfxPoolItem& SfxItemPool::GetDefaultItem( sal_uInt16 nWhich ) const
+{
+ if ( !IsInRange(nWhich) )
+ {
+ if ( pImpl->mpSecondary )
+ return pImpl->mpSecondary->GetDefaultItem( nWhich );
+ assert(!"unknown which - don't ask me for defaults");
+ }
+
+ DBG_ASSERT( pImpl->mpStaticDefaults, "no defaults known - don't ask me for defaults" );
+ sal_uInt16 nPos = GetIndex_Impl(nWhich);
+ SfxPoolItem* pDefault = pImpl->maPoolDefaults[nPos];
+ if ( pDefault )
+ return *pDefault;
+ return *(*pImpl->mpStaticDefaults)[nPos];
+}
+
+SfxItemPool* SfxItemPool::GetSecondaryPool() const
+{
+ return pImpl->mpSecondary.get();
+}
+
+/* get the last pool by following the GetSecondaryPool chain */
+SfxItemPool* SfxItemPool::GetLastPoolInChain()
+{
+ SfxItemPool* pLast = this;
+ while(pLast->GetSecondaryPool())
+ pLast = pLast->GetSecondaryPool();
+ return pLast;
+}
+
+SfxItemPool* SfxItemPool::GetMasterPool() const
+{
+ return pImpl->mpMaster;
+}
+
+/**
+ * This method should be called at the master pool, when all secondary
+ * pools are appended to it.
+ *
+ * It calculates the ranges of 'which-ids' for fast construction of
+ * item-sets, which contains all 'which-ids'.
+ */
+void SfxItemPool::FreezeIdRanges()
+{
+ assert(pImpl->mpPoolRanges.empty() && "pool already frozen, cannot freeze twice");
+ FillItemIdRanges_Impl( pImpl->mpPoolRanges );
+}
+
+
+void SfxItemPool::FillItemIdRanges_Impl( WhichRangesContainer& pWhichRanges ) const
+{
+ DBG_ASSERT( pImpl->mpPoolRanges.empty(), "GetFrozenRanges() would be faster!" );
+
+ pWhichRanges.reset();
+
+ // Merge all ranges, keeping them sorted
+ for (const SfxItemPool* pPool = this; pPool; pPool = pPool->pImpl->mpSecondary.get())
+ pWhichRanges = pWhichRanges.MergeRange(pPool->pImpl->mnStart, pPool->pImpl->mnEnd);
+}
+
+const WhichRangesContainer& SfxItemPool::GetFrozenIdRanges() const
+{
+ return pImpl->mpPoolRanges;
+}
+
+const SfxPoolItem *SfxItemPool::GetItem2Default(sal_uInt16 nWhich) const
+{
+ if ( !IsInRange(nWhich) )
+ {
+ if ( pImpl->mpSecondary )
+ return pImpl->mpSecondary->GetItem2Default( nWhich );
+ assert(false && "unknown WhichId - cannot resolve surrogate");
+ return nullptr;
+ }
+ return (*pImpl->mpStaticDefaults)[ GetIndex_Impl(nWhich) ];
+}
+
+#ifdef DBG_UTIL
+static void warnForMissingPoolRegistration(const SfxItemPool& rPool, sal_uInt16 nWhich)
+{
+ if (!rPool.NeedsPoolRegistration(nWhich))
+ SAL_INFO("svl.items", "ITEM: ItemSurrogate requested for WhichID " << nWhich <<
+ " class " << typeid(rPool.GetDefaultItem(nWhich)).name() <<
+ ": needs _bNeedsPoolRegistration==true in SfxItemInfo for that slot");
+}
+#endif
+
+const registeredSfxPoolItems& SfxItemPool::GetItemSurrogates(sal_uInt16 nWhich) const
+{
+ static const registeredSfxPoolItems EMPTY;
+
+ if (!IsInRange(nWhich))
+ {
+ if (pImpl->mpSecondary)
+ return pImpl->mpSecondary->GetItemSurrogates(nWhich);
+ return EMPTY;
+ }
+
+ if (nullptr == ppRegisteredSfxPoolItems)
+ {
+#ifdef DBG_UTIL
+ warnForMissingPoolRegistration(*this, nWhich);
+#endif
+ return EMPTY;
+ }
+
+ registeredSfxPoolItems* pSet(ppRegisteredSfxPoolItems[nWhich - pImpl->mnStart]);
+
+ if (nullptr == pSet)
+ {
+#ifdef DBG_UTIL
+ warnForMissingPoolRegistration(*this, nWhich);
+#endif
+ return EMPTY;
+ }
+
+ return *pSet;
+}
+
+std::vector<const SfxPoolItem*> SfxItemPool::FindItemSurrogate(sal_uInt16 nWhich, SfxPoolItem const & rSample) const
+{
+ static const std::vector<const SfxPoolItem*> EMPTY;
+
+ if (nullptr == ppRegisteredSfxPoolItems)
+ return EMPTY;
+
+ if ( !IsInRange(nWhich) )
+ {
+ if ( pImpl->mpSecondary )
+ return pImpl->mpSecondary->FindItemSurrogate( nWhich, rSample );
+ assert(false && "unknown WhichId - cannot resolve surrogate");
+ return EMPTY;
+ }
+
+ // get index (must exist due to checks above)
+ const sal_uInt16 nIndex(rSample.Which() - pImpl->mnStart);
+
+ if (nullptr == ppRegisteredSfxPoolItems)
+ {
+#ifdef DBG_UTIL
+ warnForMissingPoolRegistration(*this, nWhich);
+#endif
+ return EMPTY;
+ }
+
+ // get registeredSfxPoolItems container
+ registeredSfxPoolItems* pSet(ppRegisteredSfxPoolItems[nIndex]);
+
+ if (nullptr == pSet)
+ {
+#ifdef DBG_UTIL
+ warnForMissingPoolRegistration(*this, nWhich);
+#endif
+ return EMPTY;
+ }
+
+ std::vector<const SfxPoolItem*> rv;
+
+ for (const SfxPoolItem* p : *pSet)
+ if (rSample == *p)
+ rv.push_back(p);
+
+ return rv;
+}
+
+sal_uInt16 SfxItemPool::GetWhich( sal_uInt16 nSlotId, bool bDeep ) const
+{
+ if ( !IsSlot(nSlotId) )
+ return nSlotId;
+
+ sal_uInt16 nCount = pImpl->mnEnd - pImpl->mnStart + 1;
+ for ( sal_uInt16 nOfs = 0; nOfs < nCount; ++nOfs )
+ if ( pItemInfos[nOfs]._nSID == nSlotId )
+ return nOfs + pImpl->mnStart;
+ if ( pImpl->mpSecondary && bDeep )
+ return pImpl->mpSecondary->GetWhich(nSlotId);
+ return nSlotId;
+}
+
+
+sal_uInt16 SfxItemPool::GetSlotId( sal_uInt16 nWhich ) const
+{
+ if ( !IsWhich(nWhich) )
+ return nWhich;
+
+ if ( !IsInRange( nWhich ) )
+ {
+ if ( pImpl->mpSecondary )
+ return pImpl->mpSecondary->GetSlotId(nWhich);
+ assert(false && "unknown WhichId - cannot get slot-id");
+ return 0;
+ }
+
+ sal_uInt16 nSID = pItemInfos[nWhich - pImpl->mnStart]._nSID;
+ return nSID ? nSID : nWhich;
+}
+
+
+sal_uInt16 SfxItemPool::GetTrueWhich( sal_uInt16 nSlotId, bool bDeep ) const
+{
+ if ( !IsSlot(nSlotId) )
+ return 0;
+
+ sal_uInt16 nCount = pImpl->mnEnd - pImpl->mnStart + 1;
+ for ( sal_uInt16 nOfs = 0; nOfs < nCount; ++nOfs )
+ if ( pItemInfos[nOfs]._nSID == nSlotId )
+ return nOfs + pImpl->mnStart;
+ if ( pImpl->mpSecondary && bDeep )
+ return pImpl->mpSecondary->GetTrueWhich(nSlotId);
+ return 0;
+}
+
+
+sal_uInt16 SfxItemPool::GetTrueSlotId( sal_uInt16 nWhich ) const
+{
+ if ( !IsWhich(nWhich) )
+ return 0;
+
+ if ( !IsInRange( nWhich ) )
+ {
+ if ( pImpl->mpSecondary )
+ return pImpl->mpSecondary->GetTrueSlotId(nWhich);
+ assert(false && "unknown WhichId - cannot get slot-id");
+ return 0;
+ }
+ return pItemInfos[nWhich - pImpl->mnStart]._nSID;
+}
+
+void SfxItemPool::registerSfxPoolItem(const SfxPoolItem& rItem)
+{
+ assert(rItem.Which() != 0);
+
+ if (IsSlot(rItem.Which()))
+ // do not register SlotItems
+ return;
+
+ if (rItem.isRegisteredAtPool())
+ // already registered, done
+ return;
+
+ if (!IsInRange(rItem.Which()))
+ {
+ // get to the right pool
+ if (pImpl->mpSecondary)
+ {
+ pImpl->mpSecondary->registerSfxPoolItem(rItem);
+ return;
+ }
+
+ return;
+ }
+
+ if (nullptr == ppRegisteredSfxPoolItems)
+ // on-demand allocate array of registeredSfxPoolItems and init to nullptr
+ ppRegisteredSfxPoolItems = new registeredSfxPoolItems*[GetSize_Impl()]{};
+
+ // get correct registeredSfxPoolItems
+ const sal_uInt16 nIndex(rItem.Which() - pImpl->mnStart);
+ registeredSfxPoolItems* pSet(ppRegisteredSfxPoolItems[nIndex]);
+
+ if (nullptr == pSet)
+ // on-demand allocate
+ ppRegisteredSfxPoolItems[nIndex] = pSet = new registeredSfxPoolItems;
+
+ // insert to registeredSfxPoolItems and set flag at Item
+ pSet->insert(&rItem);
+ const_cast<SfxPoolItem&>(rItem).setRegisteredAtPool(true);
+}
+
+void SfxItemPool::unregisterSfxPoolItem(const SfxPoolItem& rItem)
+{
+ if (!rItem.isRegisteredAtPool())
+ // Item is not registered, done
+ return;
+
+ if (!IsInRange(rItem.Which()))
+ {
+ // get to the right pool
+ if (pImpl->mpSecondary)
+ {
+ pImpl->mpSecondary->unregisterSfxPoolItem(rItem);
+ return;
+ }
+
+ assert(false && "unknown WhichId - cannot execute unregisterSfxPoolItem");
+ return;
+ }
+
+ // we need a valid WhichID and the array of containers has to exist
+ assert(rItem.Which() != 0);
+ assert(nullptr != ppRegisteredSfxPoolItems);
+
+ // get index (must exist due to checks above)
+ const sal_uInt16 nIndex(rItem.Which() - pImpl->mnStart);
+
+ // a valid registeredSfxPoolItems container has to exist
+ registeredSfxPoolItems* pSet(ppRegisteredSfxPoolItems[nIndex]);
+ assert(nullptr != pSet);
+
+ // remove registered Item and reset flag at Item
+ pSet->erase(&rItem);
+ const_cast<SfxPoolItem&>(rItem).setRegisteredAtPool(false);
+}
+
+bool SfxItemPool::isSfxPoolItemRegisteredAtThisPool(const SfxPoolItem& rItem) const
+{
+ if (!rItem.isRegisteredAtPool())
+ // Item is not registered at all, so also not at this Pool
+ return false;
+
+ if (IsSlot(rItem.Which()))
+ // do not check being registered for SlotItems
+ return false;
+
+ if (!IsInRange(rItem.Which()))
+ {
+ // get to the right pool
+ if (pImpl->mpSecondary)
+ return pImpl->mpSecondary->isSfxPoolItemRegisteredAtThisPool(rItem);
+ return false;
+ }
+
+ // we need a valid WhichID
+ assert(rItem.Which() != 0);
+
+ if (nullptr == ppRegisteredSfxPoolItems)
+ // when no array of containers exists the Item is not registered
+ return false;
+
+ // get index (must exist due to checks above)
+ const sal_uInt16 nIndex(rItem.Which() - pImpl->mnStart);
+
+ // get registeredSfxPoolItems container
+ registeredSfxPoolItems* pSet(ppRegisteredSfxPoolItems[nIndex]);
+
+ if (nullptr == pSet)
+ // when no registeredSfxPoolItems container exists the Item is not registered
+ return false;
+
+ // test if Item is registered
+ return pSet->find(&rItem) != pSet->end();
+}
+
+const SfxPoolItem* SfxItemPool::tryToGetEqualItem(const SfxPoolItem& rItem, sal_uInt16 nWhich) const
+{
+ if (IsSlot(nWhich))
+ // SlotItems are not registered @pool and not in any range
+ return nullptr;
+
+ if (!IsInRange(nWhich))
+ {
+ // get to the right pool
+ if (pImpl->mpSecondary)
+ return pImpl->mpSecondary->tryToGetEqualItem(rItem, nWhich);
+ return nullptr;
+ }
+
+ if (nullptr == ppRegisteredSfxPoolItems)
+ // no Items at all
+ return nullptr;
+
+ // get index (must exist due to checks above)
+ const sal_uInt16 nIndex(nWhich - pImpl->mnStart);
+
+ if (!Shareable_Impl(nIndex))
+ // not shareable
+ return nullptr;
+
+ // get registeredSfxPoolItems container
+ registeredSfxPoolItems* pSet(ppRegisteredSfxPoolItems[nIndex]);
+
+ if (nullptr == pSet)
+ // no registeredSfxPoolItems for this WhichID
+ return nullptr;
+
+ for (const auto& rCandidate : *pSet)
+ if (*rCandidate == rItem)
+ return rCandidate;
+
+ return nullptr;
+}
+
+void SfxItemPool::dumpAsXml(xmlTextWriterPtr pWriter) const
+{
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SfxItemPool"));
+
+ if (nullptr != ppRegisteredSfxPoolItems)
+ {
+ registeredSfxPoolItems** ppSet(ppRegisteredSfxPoolItems);
+
+ for (sal_uInt16 a(0); a < GetSize_Impl(); a++, ppSet++)
+ {
+ if (nullptr != *ppSet)
+ {
+ for (auto& rCandidate : **ppSet)
+ {
+ if (nullptr != rCandidate)
+ {
+ rCandidate->dumpAsXml(pWriter);
+ }
+ }
+ }
+ }
+ }
+
+ (void)xmlTextWriterEndElement(pWriter);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/items/itemprop.cxx b/svl/source/items/itemprop.cxx
new file mode 100644
index 0000000000..3885542f89
--- /dev/null
+++ b/svl/source/items/itemprop.cxx
@@ -0,0 +1,356 @@
+/* -*- 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/any.hxx>
+#include <svl/itemprop.hxx>
+#include <svl/itempool.hxx>
+#include <svl/itemset.hxx>
+#include <com/sun/star/beans/PropertyAttribute.hpp>
+#include <com/sun/star/lang/IllegalArgumentException.hpp>
+#include <memory>
+
+/*
+ * UNO III Implementation
+ */
+using namespace com::sun::star;
+using namespace com::sun::star::beans;
+using namespace com::sun::star::lang;
+using namespace com::sun::star::uno;
+
+SfxItemPropertyMap::SfxItemPropertyMap( std::span<const SfxItemPropertyMapEntry> pEntries )
+{
+ m_aMap.reserve(pEntries.size());
+ for (const auto & pEntry : pEntries)
+ {
+ assert(!pEntry.aName.isEmpty() && "empty name? might be something left an empty entry at the end of this array");
+ m_aMap.insert( &pEntry );
+ }
+}
+
+SfxItemPropertyMap::SfxItemPropertyMap( const SfxItemPropertyMap& rSource ) = default;
+
+SfxItemPropertyMap::~SfxItemPropertyMap()
+{
+}
+
+const SfxItemPropertyMapEntry* SfxItemPropertyMap::getByName( std::u16string_view rName ) const
+{
+ struct Compare
+ {
+ bool operator() ( const SfxItemPropertyMapEntry* lhs, std::u16string_view rhs ) const
+ {
+ return lhs->aName < rhs;
+ }
+ bool operator() ( std::u16string_view lhs, const SfxItemPropertyMapEntry* rhs ) const
+ {
+ return lhs < rhs->aName;
+ }
+ };
+ auto it = std::lower_bound(m_aMap.begin(), m_aMap.end(), rName, Compare());
+ if (it == m_aMap.end() || Compare()(rName, *it))
+ return nullptr;
+ return *it;
+}
+
+uno::Sequence<beans::Property> const & SfxItemPropertyMap::getProperties() const
+{
+ if( !m_aPropSeq.hasElements() )
+ {
+ m_aPropSeq.realloc( m_aMap.size() );
+ beans::Property* pPropArray = m_aPropSeq.getArray();
+ sal_uInt32 n = 0;
+ for( const SfxItemPropertyMapEntry* pEntry : m_aMap )
+ {
+ pPropArray[n].Name = pEntry->aName;
+ pPropArray[n].Handle = pEntry->nWID;
+ pPropArray[n].Type = pEntry->aType;
+ pPropArray[n].Attributes =
+ sal::static_int_cast< sal_Int16 >(pEntry->nFlags);
+ n++;
+ }
+ }
+
+ return m_aPropSeq;
+}
+
+beans::Property SfxItemPropertyMap::getPropertyByName( const OUString& rName ) const
+{
+ const SfxItemPropertyMapEntry* pEntry = getByName(rName);
+ if( !pEntry )
+ throw UnknownPropertyException(rName);
+ beans::Property aProp;
+ aProp.Name = rName;
+ aProp.Handle = pEntry->nWID;
+ aProp.Type = pEntry->aType;
+ aProp.Attributes = sal::static_int_cast< sal_Int16 >(pEntry->nFlags);
+ return aProp;
+}
+
+bool SfxItemPropertyMap::hasPropertyByName( std::u16string_view rName ) const
+{
+ return getByName(rName) != nullptr;
+}
+
+SfxItemPropertySet::~SfxItemPropertySet()
+{
+}
+
+void SfxItemPropertySet::getPropertyValue( const SfxItemPropertyMapEntry& rEntry,
+ const SfxItemSet& rSet, Any& rAny ) const
+{
+ // get the SfxPoolItem
+ const SfxPoolItem* pItem = nullptr;
+ SfxItemState eState = rSet.GetItemState( rEntry.nWID, true, &pItem );
+ if (SfxItemState::SET != eState && SfxItemPool::IsWhich(rEntry.nWID) )
+ pItem = &rSet.GetPool()->GetDefaultItem(rEntry.nWID);
+ // return item values as uno::Any
+ if(eState >= SfxItemState::DEFAULT && pItem)
+ {
+ pItem->QueryValue( rAny, rEntry.nMemberId );
+ }
+ else if(0 == (rEntry.nFlags & PropertyAttribute::MAYBEVOID))
+ {
+ throw RuntimeException(
+ "Property not found in ItemSet but not MAYBEVOID?", nullptr);
+ }
+
+ // convert general SfxEnumItem values to specific values
+ if( rEntry.aType.getTypeClass() == TypeClass_ENUM &&
+ rAny.getValueTypeClass() == TypeClass_LONG )
+ {
+ sal_Int32 nTmp = *o3tl::forceAccess<sal_Int32>(rAny);
+ rAny.setValue( &nTmp, rEntry.aType );
+ }
+}
+
+void SfxItemPropertySet::getPropertyValue( const OUString &rName,
+ const SfxItemSet& rSet, Any& rAny ) const
+{
+ // detect which-id
+ const SfxItemPropertyMapEntry* pEntry = m_aMap.getByName( rName );
+ if ( !pEntry )
+ throw UnknownPropertyException(rName);
+ getPropertyValue( *pEntry,rSet, rAny );
+}
+
+Any SfxItemPropertySet::getPropertyValue( const OUString &rName,
+ const SfxItemSet& rSet ) const
+{
+ Any aVal;
+ getPropertyValue( rName,rSet, aVal );
+ return aVal;
+}
+
+void SfxItemPropertySet::setPropertyValue( const SfxItemPropertyMapEntry& rEntry,
+ const Any& aVal,
+ SfxItemSet& rSet ) const
+{
+ // get the SfxPoolItem
+ const SfxPoolItem* pItem = nullptr;
+ std::unique_ptr<SfxPoolItem> pNewItem;
+ SfxItemState eState = rSet.GetItemState( rEntry.nWID, true, &pItem );
+ if (SfxItemState::SET != eState && SfxItemPool::IsWhich(rEntry.nWID))
+ pItem = &rSet.GetPool()->GetDefaultItem(rEntry.nWID);
+ if (pItem)
+ {
+ pNewItem.reset(pItem->Clone());
+ }
+ if(pNewItem)
+ {
+ if( !pNewItem->PutValue( aVal, rEntry.nMemberId ) )
+ {
+ throw IllegalArgumentException();
+ }
+ // apply new item
+ rSet.Put( std::move(pNewItem) );
+ }
+}
+
+void SfxItemPropertySet::setPropertyValue( const OUString &rName,
+ const Any& aVal,
+ SfxItemSet& rSet ) const
+{
+ const SfxItemPropertyMapEntry* pEntry = m_aMap.getByName( rName );
+ if ( !pEntry )
+ {
+ throw UnknownPropertyException(rName);
+ }
+ setPropertyValue(*pEntry, aVal, rSet);
+}
+
+PropertyState SfxItemPropertySet::getPropertyState(const SfxItemPropertyMapEntry& rEntry, const SfxItemSet& rSet) const
+ noexcept
+{
+ PropertyState eRet = PropertyState_DIRECT_VALUE;
+ sal_uInt16 nWhich = rEntry.nWID;
+
+ // Get item state
+ SfxItemState eState = rSet.GetItemState( nWhich, false );
+ // Return item value as UnoAny
+ if(eState == SfxItemState::DEFAULT)
+ eRet = PropertyState_DEFAULT_VALUE;
+ else if(eState < SfxItemState::DEFAULT)
+ eRet = PropertyState_AMBIGUOUS_VALUE;
+ return eRet;
+}
+
+PropertyState SfxItemPropertySet::getPropertyState(const OUString& rName, const SfxItemSet& rSet) const
+{
+ PropertyState eRet = PropertyState_DIRECT_VALUE;
+
+ // Get WhichId
+ const SfxItemPropertyMapEntry* pEntry = m_aMap.getByName( rName );
+ if( !pEntry || !pEntry->nWID )
+ {
+ throw UnknownPropertyException(rName);
+ }
+ sal_uInt16 nWhich = pEntry->nWID;
+
+ // Get item state
+ SfxItemState eState = rSet.GetItemState(nWhich, false);
+ // Return item value as UnoAny
+ if(eState == SfxItemState::DEFAULT)
+ eRet = PropertyState_DEFAULT_VALUE;
+ else if(eState < SfxItemState::DEFAULT)
+ eRet = PropertyState_AMBIGUOUS_VALUE;
+ return eRet;
+}
+
+Reference<XPropertySetInfo> const & SfxItemPropertySet::getPropertySetInfo() const
+{
+ if( !m_xInfo.is() )
+ m_xInfo = new SfxItemPropertySetInfo( m_aMap );
+ return m_xInfo;
+}
+
+SfxItemPropertySetInfo::SfxItemPropertySetInfo(const SfxItemPropertyMap &rMap )
+ : m_aOwnMap( rMap )
+{
+}
+
+SfxItemPropertySetInfo::SfxItemPropertySetInfo(std::span<const SfxItemPropertyMapEntry> pEntries )
+ : m_aOwnMap( pEntries )
+{
+}
+
+Sequence< Property > SAL_CALL SfxItemPropertySetInfo::getProperties( )
+{
+ return m_aOwnMap.getProperties();
+}
+
+SfxItemPropertySetInfo::~SfxItemPropertySetInfo()
+{
+}
+
+Property SAL_CALL SfxItemPropertySetInfo::getPropertyByName( const OUString& rName )
+{
+ return m_aOwnMap.getPropertyByName( rName );
+}
+
+sal_Bool SAL_CALL SfxItemPropertySetInfo::hasPropertyByName( const OUString& rName )
+{
+ return m_aOwnMap.hasPropertyByName( rName );
+}
+
+SfxExtItemPropertySetInfo::SfxExtItemPropertySetInfo( std::span<const SfxItemPropertyMapEntry> pEntries,
+ const Sequence<Property>& rPropSeq )
+{
+ maMap.reserve(pEntries.size() + rPropSeq.getLength());
+ for (const auto & rEntry : pEntries )
+ {
+ assert(!rEntry.aName.isEmpty() && "empty name? might be something left an empty entry at the end of this array");
+ maMap.insert( rEntry );
+ }
+ for( const auto & rProp : rPropSeq )
+ {
+ SfxItemPropertyMapEntry aTemp(
+ rProp.Name,
+ sal::static_int_cast< sal_Int16 >( rProp.Handle ), //nWID
+ rProp.Type, //aType
+ rProp.Attributes,
+ 0); //nFlags
+ maMap.insert( aTemp );
+ }
+}
+
+SfxExtItemPropertySetInfo::~SfxExtItemPropertySetInfo()
+{
+}
+
+Sequence< Property > SAL_CALL SfxExtItemPropertySetInfo::getProperties( )
+{
+ if( !m_aPropSeq.hasElements() )
+ {
+ m_aPropSeq.realloc( maMap.size() );
+ beans::Property* pPropArray = m_aPropSeq.getArray();
+ sal_uInt32 n = 0;
+ for( const SfxItemPropertyMapEntry& rEntry : maMap )
+ {
+ pPropArray[n].Name = rEntry.aName;
+ pPropArray[n].Handle = rEntry.nWID;
+ pPropArray[n].Type = rEntry.aType;
+ pPropArray[n].Attributes =
+ sal::static_int_cast< sal_Int16 >(rEntry.nFlags);
+ n++;
+ }
+ }
+
+ return m_aPropSeq;
+}
+
+Property SAL_CALL SfxExtItemPropertySetInfo::getPropertyByName( const OUString& rPropertyName )
+{
+ const SfxItemPropertyMapEntry* pEntry = getByName(rPropertyName);
+ if( !pEntry )
+ throw UnknownPropertyException(rPropertyName);
+ beans::Property aProp;
+ aProp.Name = rPropertyName;
+ aProp.Handle = pEntry->nWID;
+ aProp.Type = pEntry->aType;
+ aProp.Attributes = sal::static_int_cast< sal_Int16 >(pEntry->nFlags);
+ return aProp;
+}
+
+sal_Bool SAL_CALL SfxExtItemPropertySetInfo::hasPropertyByName( const OUString& rPropertyName )
+{
+ return getByName(rPropertyName) != nullptr;
+}
+
+const SfxItemPropertyMapEntry* SfxExtItemPropertySetInfo::getByName( std::u16string_view rName ) const
+{
+ struct Compare
+ {
+ bool operator() ( const SfxItemPropertyMapEntry& lhs, std::u16string_view rhs ) const
+ {
+ return lhs.aName < rhs;
+ }
+ bool operator() ( std::u16string_view lhs, const SfxItemPropertyMapEntry& rhs ) const
+ {
+ return lhs < rhs.aName;
+ }
+ };
+ auto it = std::lower_bound(maMap.begin(), maMap.end(), rName, Compare());
+ if (it == maMap.end() || Compare()(rName, *it))
+ return nullptr;
+ return &*it;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/items/itemset.cxx b/svl/source/items/itemset.cxx
new file mode 100644
index 0000000000..66780cb581
--- /dev/null
+++ b/svl/source/items/itemset.cxx
@@ -0,0 +1,2068 @@
+/* -*- 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 <string.h>
+
+#include <algorithm>
+#include <cassert>
+#include <cstddef>
+
+#include <libxml/xmlwriter.h>
+
+#include <sal/log.hxx>
+#include <svl/itemset.hxx>
+#include <svl/itempool.hxx>
+#include <svl/itemiter.hxx>
+#include <svl/setitem.hxx>
+#include <svl/whiter.hxx>
+#include <svl/voiditem.hxx>
+
+#include <items_helper.hxx>
+
+#ifdef DBG_UTIL
+static size_t nAllocatedSfxItemSetCount(0);
+static size_t nUsedSfxItemSetCount(0);
+static size_t nAllocatedSfxPoolItemHolderCount(0);
+static size_t nUsedSfxPoolItemHolderCount(0);
+size_t getAllocatedSfxItemSetCount() { return nAllocatedSfxItemSetCount; }
+size_t getUsedSfxItemSetCount() { return nUsedSfxItemSetCount; }
+size_t getAllocatedSfxPoolItemHolderCount() { return nAllocatedSfxPoolItemHolderCount; }
+size_t getUsedSfxPoolItemHolderCount() { return nUsedSfxPoolItemHolderCount; }
+#endif
+// NOTE: Only needed for one Item in SC (see notes below for
+// ScPatternAttr/ATTR_PATTERN). Still keep it so that when errors
+// come up to this change be able to quickly check using the
+// fallback flag 'ITEM_CLASSIC_MODE'
+static bool g_bItemClassicMode(getenv("ITEM_CLASSIC_MODE"));
+
+// I thought about this constructor a while, but when there is no
+// Item we need no cleanup at destruction (what we would need the
+// Pool for), so it is OK and makes default construction easier
+// when no Pool is needed. The other constructors guanantee that
+// there *cannot* be a state with Item set and Pool not set. IF
+// you change this class, ALWAYS ensure that this can not happen (!)
+SfxPoolItemHolder::SfxPoolItemHolder()
+: m_pPool(nullptr)
+, m_pItem(nullptr)
+#ifdef DBG_UTIL
+, m_bDeleted(false)
+#endif
+{
+#ifdef DBG_UTIL
+ nAllocatedSfxPoolItemHolderCount++;
+ nUsedSfxPoolItemHolderCount++;
+#endif
+}
+
+SfxPoolItemHolder::SfxPoolItemHolder(SfxItemPool& rPool, const SfxPoolItem* pItem, bool bPassingOwnership)
+: m_pPool(&rPool)
+, m_pItem(pItem)
+#ifndef NDEBUG
+, m_bDeleted(false)
+#endif
+{
+#ifdef DBG_UTIL
+ nAllocatedSfxPoolItemHolderCount++;
+ nUsedSfxPoolItemHolderCount++;
+#endif
+ if (nullptr != m_pItem)
+ m_pItem = implCreateItemEntry(*m_pPool, m_pItem, m_pItem->Which(), bPassingOwnership);
+}
+
+SfxPoolItemHolder::SfxPoolItemHolder(const SfxPoolItemHolder& rHolder)
+: m_pPool(rHolder.m_pPool)
+, m_pItem(rHolder.m_pItem)
+#ifndef NDEBUG
+, m_bDeleted(false)
+#endif
+{
+#ifdef DBG_UTIL
+ assert(!rHolder.isDeleted() && "Destructed instance used (!)");
+ nAllocatedSfxPoolItemHolderCount++;
+ nUsedSfxPoolItemHolderCount++;
+#endif
+ if (nullptr != m_pItem)
+ m_pItem = implCreateItemEntry(*m_pPool, m_pItem, m_pItem->Which(), false);
+}
+
+SfxPoolItemHolder::~SfxPoolItemHolder()
+{
+#ifdef DBG_UTIL
+ assert(!isDeleted() && "Destructed instance used (!)");
+ nAllocatedSfxPoolItemHolderCount--;
+#endif
+ if (nullptr != m_pItem)
+ implCleanupItemEntry(*m_pPool, m_pItem);
+#ifdef DBG_UTIL
+ m_bDeleted = true;
+#endif
+}
+
+const SfxPoolItemHolder& SfxPoolItemHolder::operator=(const SfxPoolItemHolder& rHolder)
+{
+ assert(!isDeleted() && "Destructed instance used (!)");
+ assert(!rHolder.isDeleted() && "Destructed instance used (!)");
+ if (this == &rHolder || *this == rHolder)
+ return *this;
+
+ if (nullptr != m_pItem)
+ implCleanupItemEntry(*m_pPool, m_pItem);
+
+ m_pPool = rHolder.m_pPool;
+ m_pItem = rHolder.m_pItem;
+
+ if (nullptr != m_pItem)
+ m_pItem = implCreateItemEntry(*m_pPool, m_pItem, m_pItem->Which(), false);
+
+ return *this;
+}
+
+bool SfxPoolItemHolder::operator==(const SfxPoolItemHolder &rHolder) const
+{
+ assert(!isDeleted() && "Destructed instance used (!)");
+ assert(!rHolder.isDeleted() && "Destructed instance used (!)");
+ return m_pPool == rHolder.m_pPool && areSfxPoolItemPtrsEqual(m_pItem, rHolder.m_pItem);
+}
+
+/**
+ * Ctor for a SfxItemSet with exactly the Which Ranges, which are known to
+ * the supplied SfxItemPool.
+ *
+ * For Sfx programmers: an SfxItemSet constructed in this way cannot
+ * contain any Items with SlotIds as Which values.
+ *
+ * Don't create ItemSets with full range before FreezeIdRanges()!
+ */
+SfxItemSet::SfxItemSet(SfxItemPool& rPool)
+ : m_pPool(&rPool)
+ , m_pParent(nullptr)
+ , m_nCount(0)
+ , m_nTotalCount(svl::detail::CountRanges(rPool.GetFrozenIdRanges()))
+ , m_bItemsFixed(false)
+ , m_ppItems(new SfxPoolItem const *[m_nTotalCount]{})
+ , m_pWhichRanges(rPool.GetFrozenIdRanges())
+ , m_aCallback()
+{
+#ifdef DBG_UTIL
+ nAllocatedSfxItemSetCount++;
+ nUsedSfxItemSetCount++;
+#endif
+ assert(svl::detail::validRanges2(m_pWhichRanges));
+}
+
+SfxItemSet::SfxItemSet( SfxItemPool& rPool, SfxAllItemSetFlag )
+ : m_pPool(&rPool)
+ , m_pParent(nullptr)
+ , m_nCount(0)
+ , m_nTotalCount(0)
+ , m_bItemsFixed(false)
+ , m_ppItems(nullptr)
+ , m_pWhichRanges()
+ , m_aCallback()
+{
+#ifdef DBG_UTIL
+ nAllocatedSfxItemSetCount++;
+ nUsedSfxItemSetCount++;
+#endif
+}
+
+/** special constructor for SfxItemSetFixed */
+SfxItemSet::SfxItemSet( SfxItemPool& rPool, WhichRangesContainer&& ranges, SfxPoolItem const ** ppItems, sal_uInt16 nTotalCount )
+ : m_pPool(&rPool)
+ , m_pParent(nullptr)
+ , m_nCount(0)
+ , m_nTotalCount(nTotalCount)
+ , m_bItemsFixed(true)
+ , m_ppItems(ppItems)
+ , m_pWhichRanges(std::move(ranges))
+ , m_aCallback()
+{
+#ifdef DBG_UTIL
+ nAllocatedSfxItemSetCount++;
+ nUsedSfxItemSetCount++;
+#endif
+ assert(ppItems);
+ assert(m_pWhichRanges.size() > 0);
+ assert(svl::detail::validRanges2(m_pWhichRanges));
+}
+
+SfxItemSet::SfxItemSet(SfxItemPool& pool, WhichRangesContainer wids)
+ : m_pPool(&pool)
+ , m_pParent(nullptr)
+ , m_nCount(0)
+ , m_nTotalCount(svl::detail::CountRanges(wids))
+ , m_bItemsFixed(false)
+ , m_ppItems(new SfxPoolItem const *[m_nTotalCount]{})
+ , m_pWhichRanges(std::move(wids))
+ , m_aCallback()
+{
+#ifdef DBG_UTIL
+ nAllocatedSfxItemSetCount++;
+ nUsedSfxItemSetCount++;
+#endif
+ assert(svl::detail::CountRanges(m_pWhichRanges) != 0);
+ assert(svl::detail::validRanges2(m_pWhichRanges));
+}
+
+SfxPoolItem const* implCreateItemEntry(SfxItemPool& rPool, SfxPoolItem const* pSource, sal_uInt16 nWhich, bool bPassingOwnership)
+{
+ if (nullptr == pSource)
+ // SfxItemState::UNKNOWN aka current default (nullptr)
+ // just use/return nullptr
+ return nullptr;
+
+ if (IsInvalidItem(pSource))
+ // SfxItemState::DONTCARE aka invalid item
+ // just use pSource which equals INVALID_POOL_ITEM
+ return pSource;
+
+ if (pSource->isExceptionalSCItem() && rPool.GetMasterPool()->newItem_UseDirect(*pSource))
+ // exceptional handling for *some* items, see SC
+ // (do not copy item: use directly, it is a pool default)
+ return pSource;
+
+ // CAUTION: static default items are not *that* static as it seems
+ // (or: should be). If they are freed with the Pool (see
+ // ::ReleaseDefaults) they will be deleted. Same is true for
+ // dynamic defaults. Thus currently no default can be shared
+ // at all since these may be deleted with the pool owning them.
+ // That these are not shared but cloned is ensured by those
+ // having a default RefCount of zero, so these are used merely as
+ // templates.
+ // if (IsStaticDefaultItem(pSource))
+ // return pSource;
+
+ if (0 == pSource->Which())
+ // These *should* be SfxVoidItem(0) the only Items with 0 == WhichID,
+ // these need to be cloned (currently...)
+ return pSource->Clone();
+
+ // get correct target WhichID
+ if (0 == nWhich)
+ nWhich = pSource->Which();
+
+ if (SfxItemPool::IsSlot(nWhich))
+ {
+ // SlotItems were always cloned in original (even when bPassingOwnership),
+ // so do that here, too (but without bPassingOwnership).
+ // They do not need to be registered at pool (actually impossible, pools
+ // do not have entries for SlotItems) so handle here early
+ if (!bPassingOwnership)
+ pSource = pSource->Clone(rPool.GetMasterPool());
+
+ if (pSource->Which() != nWhich)
+ const_cast<SfxPoolItem*>(pSource)->SetWhich(nWhich);
+
+ pSource->AddRef();
+ return pSource;
+ }
+
+ // get the pool with which ItemSets have to work, plus get the
+ // pool at which the WhichID is defined, so calls to it do not
+ // have to do this repeatedly
+ SfxItemPool* pMasterPool(rPool.GetMasterPool());
+ assert(nullptr != pMasterPool);
+ SfxItemPool* pTargetPool(pMasterPool);
+ while (!pTargetPool->IsInRange(nWhich))
+ pTargetPool = pTargetPool->GetSecondaryPool();
+
+ // if this goes wrong, an Item with invalid ID for this pool is
+ // processed. This is not allowed (and should not happen, e.g.
+ // ItemSets already have WhichRanges that are checked against
+ // their Pool)
+ if (nullptr == pTargetPool)
+ {
+ assert(false);
+ return pSource;
+ }
+
+ // CAUTION: Shareable_Impl and NeedsPoolRegistration_Impl
+ // use index, not WhichID (one more reason to change the Pools)
+ const sal_uInt16 nIndex(pTargetPool->GetIndex_Impl(nWhich));
+
+ // the Item itself is shareable when it already is used somewhere
+ // which is equivalent to be referenced already. IsPooledItem also
+ // checked for SFX_ITEMS_MAXREF, that is not needed here. Use a
+ // fake 'while' loop and 'break' to make this better readable
+
+ // only try to share items that are already shared somehow, else
+ // these items are probably not (heap) item-ptr's (but on the
+ // stack or else)
+ while(pSource->GetRefCount() > 0)
+ {
+ if (pSource->Which() != nWhich)
+ // If the target WhichID differs from the WhichID of a shared Item
+ // the item needs to be cloned. This happens, e.g. in
+ // ScPatternAttr::GetFromEditItemSet existing items with WhichIDs
+ // from EditEngine get set at a SetItem's ItemSet with different
+ // target ranges (e.g. look for ATTR_FONT_UNDERLINE)
+ break;
+
+ if (!pTargetPool->Shareable_Impl(nIndex))
+ // not shareable, done
+ break;
+
+ // SfxSetItems cannot be shared if they are in/use another pool
+ if (pSource->isSetItem() && static_cast<const SfxSetItem*>(pSource)->GetItemSet().GetPool() != pMasterPool)
+ break;
+
+ // If the Item is registered it is pool-dependent, so do not share when
+ // it is registered but not at this pool
+ if (pSource->isRegisteredAtPool() && !pTargetPool->isSfxPoolItemRegisteredAtThisPool(*pSource))
+ break;
+
+ // If we get here we can share the Item
+ pSource->AddRef();
+ return pSource;
+ }
+
+ // g_bItemClassicMode: try finding already existing item
+ // NOTE: the UnitTest testIteratorsDefPattern claims that that Item "can be
+ // edited by the user" which explains why it breaks so many rules for Items,
+ // it behaves like an alien. That Item in the SC Pool claims to be a
+ // 'StaticDefault' and gets changed (..?)
+
+ // only do this if classic mode or required (calls from Pool::Direct*)
+ while(g_bItemClassicMode || pSource->isExceptionalSCItem())
+ {
+ if (!pTargetPool->Shareable_Impl(nIndex))
+ // not shareable, so no need to search for identical item
+ break;
+
+ // try to get equal Item. This is the expensive part...
+ const SfxPoolItem* pExisting(pTargetPool->tryToGetEqualItem(*pSource, nWhich));
+
+ if (nullptr == pExisting)
+ // none found, done
+ break;
+
+ if (0 == pExisting->GetRefCount())
+ // do not share not-yet shared Items (should not happen)
+ break;
+
+ if (bPassingOwnership)
+ // need to cleanup if we are offered to own pSource
+ delete pSource;
+
+ // If we get here we can share the found Item
+ pExisting->AddRef();
+ return pExisting;
+ }
+
+ // check if the handed over and to be directly used item is a
+ // SfxSetItem, that would make it pool-dependent. It then must have
+ // the same target-pool, ensure that by the cost of cloning it
+ // (should not happen)
+ if (bPassingOwnership
+ && pSource->isSetItem()
+ && static_cast<const SfxSetItem*>(pSource)->GetItemSet().GetPool() != pMasterPool)
+ {
+ const SfxPoolItem* pOld(pSource);
+ pSource = pSource->Clone(pMasterPool);
+ delete pOld;
+ }
+
+ // when we reach this line we know that we have to add/create a new item. If
+ // bPassingOwnership is given just use the item, else clone it
+ if (!bPassingOwnership)
+ pSource = pSource->Clone(pMasterPool);
+
+ // ensure WhichID @Item
+ if (pSource->Which() != nWhich)
+ const_cast<SfxPoolItem*>(pSource)->SetWhich(nWhich);
+
+ // increase RefCnt 0->1
+ pSource->AddRef();
+
+ // Unfortunately e,g, SC does 'special' things for some new Items,
+ // so we need to give the opportunity for this. To limit this to
+ // the needed cases, use m_bExceptionalSCItem flag at item
+ if (pSource->isExceptionalSCItem())
+ pMasterPool->newItem_Callback(*pSource);
+
+ // try to register @Pool (only needed if not yet registered)
+ if (!pSource->isRegisteredAtPool())
+ {
+ bool bRegisterAtPool(pSource->isExceptionalSCItem());
+
+ if (!bRegisterAtPool)
+ {
+ if (g_bItemClassicMode)
+ {
+ // in classic mode register only/all shareable items
+ bRegisterAtPool = pTargetPool->Shareable_Impl(nIndex);
+ }
+ else
+ {
+ // in new mode register only/all items marked as need to be registered
+ bRegisterAtPool = pTargetPool->NeedsPoolRegistration_Impl(nIndex);
+ }
+ }
+
+ if (bRegisterAtPool)
+ pTargetPool->registerSfxPoolItem(*pSource);
+ }
+
+ return pSource;
+}
+
+void implCleanupItemEntry(SfxItemPool& rPool, SfxPoolItem const* pSource)
+{
+ if (nullptr == pSource)
+ // no entry, done
+ return;
+
+ if (IsInvalidItem(pSource))
+ // nothing to do for invalid item entries
+ return;
+
+ if (pSource->isExceptionalSCItem() && rPool.GetMasterPool()->newItem_UseDirect(*pSource))
+ // exceptional handling for *some* items, see SC
+ // do not delete Item, it is a pool default
+ return;
+
+ if (0 == pSource->Which())
+ {
+ // de-register when registered @pool
+ if (pSource->isRegisteredAtPool())
+ rPool.unregisterSfxPoolItem(*pSource);
+
+ // These *should* be SfxVoidItem(0) the only Items with 0 == WhichID
+ // and need to be deleted
+ delete pSource;
+ return;
+ }
+
+ if (1 < pSource->GetRefCount())
+ {
+ // Still multiple references present, so just alter the RefCount
+ pSource->ReleaseRef();
+ return;
+ }
+
+ if (IsDefaultItem(pSource))
+ // default items (static and dynamic) are owned by the pool, do not delete
+ return;
+
+ // decrease RefCnt before deleting (destructor asserts for it and that's
+ // good to find other errors)
+ pSource->ReleaseRef();
+
+ // de-register before deletion when registered @pool
+ if (pSource->isRegisteredAtPool())
+ rPool.unregisterSfxPoolItem(*pSource);
+
+ // delete Item
+ delete pSource;
+}
+
+SfxItemSet::SfxItemSet( const SfxItemSet& rASet )
+ : m_pPool( rASet.m_pPool )
+ , m_pParent( rASet.m_pParent )
+ , m_nCount( rASet.m_nCount )
+ , m_nTotalCount( rASet.m_nTotalCount )
+ , m_bItemsFixed(false)
+ , m_ppItems(nullptr)
+ , m_pWhichRanges( rASet.m_pWhichRanges )
+ , m_aCallback(rASet.m_aCallback)
+{
+#ifdef DBG_UTIL
+ nAllocatedSfxItemSetCount++;
+ nUsedSfxItemSetCount++;
+#endif
+ if (rASet.GetRanges().empty())
+ {
+ return;
+ }
+
+ if (0 == rASet.Count())
+ {
+ // no Items set in source ItemSet, allocate new array
+ // *plus* init to nullptr
+ m_ppItems = new const SfxPoolItem* [TotalCount()]{};
+ return;
+ }
+
+ // allocate new array (no need to initialize, will be done below)
+ m_ppItems = new const SfxPoolItem* [TotalCount()];
+
+ // Copy attributes
+ SfxPoolItem const** ppDst(m_ppItems);
+
+ for (const auto& rSource : rASet)
+ {
+ *ppDst = implCreateItemEntry(*GetPool(), rSource, 0, false);
+ ppDst++;
+ }
+
+ assert(svl::detail::validRanges2(m_pWhichRanges));
+}
+
+SfxItemSet::SfxItemSet(SfxItemSet&& rASet) noexcept
+ : m_pPool( rASet.m_pPool )
+ , m_pParent( rASet.m_pParent )
+ , m_nCount( rASet.m_nCount )
+ , m_nTotalCount( rASet.m_nTotalCount )
+ , m_bItemsFixed(false)
+ , m_ppItems( rASet.m_ppItems )
+ , m_pWhichRanges( std::move(rASet.m_pWhichRanges) )
+ , m_aCallback(rASet.m_aCallback)
+{
+#ifdef DBG_UTIL
+ nAllocatedSfxItemSetCount++;
+ nUsedSfxItemSetCount++;
+#endif
+ if (rASet.m_bItemsFixed)
+ {
+ // have to make a copy
+ m_ppItems = new const SfxPoolItem* [TotalCount()];
+
+ // can just copy the pointers, the ones in the original m_ppItems
+ // array will no longer be used/referenced (unused mem, but not lost,
+ // it's part of the ItemSet-derived object)
+ std::copy(rASet.m_ppItems, rASet.m_ppItems + TotalCount(), m_ppItems);
+ }
+ else
+ {
+ // taking over ownership
+ rASet.m_nTotalCount = 0;
+ rASet.m_ppItems = nullptr;
+ }
+ rASet.m_pPool = nullptr;
+ rASet.m_pParent = nullptr;
+ rASet.m_nCount = 0;
+
+ assert(svl::detail::validRanges2(m_pWhichRanges));
+}
+
+SfxItemSet::~SfxItemSet()
+{
+#ifdef DBG_UTIL
+ nAllocatedSfxItemSetCount--;
+#endif
+ // cleanup items. No std::fill needed, we are done with this ItemSet.
+ // the callback is not set in destructor, so no worries about that
+ ClearAllItemsImpl();
+
+ if (!m_bItemsFixed)
+ {
+ // free SfxPoolItem array
+ delete[] m_ppItems;
+ }
+
+ // for invariant-testing
+ m_pWhichRanges.reset();
+}
+
+// Delete single Items or all Items (nWhich == 0)
+sal_uInt16 SfxItemSet::ClearItem( sal_uInt16 nWhich )
+{
+ if( !Count() )
+ return 0;
+
+ if( nWhich )
+ return ClearSingleItem_ForWhichID(nWhich);
+
+ // clear all & reset to nullptr
+ const sal_uInt16 nRetval(ClearAllItemsImpl());
+ std::fill(begin(), begin() + TotalCount(), nullptr);
+ return nRetval;
+}
+
+sal_uInt16 SfxItemSet::ClearSingleItem_ForWhichID( sal_uInt16 nWhich )
+{
+ const sal_uInt16 nOffset(GetRanges().getOffsetFromWhich(nWhich));
+
+ if (INVALID_WHICHPAIR_OFFSET != nOffset)
+ {
+ // found, continue with offset
+ return ClearSingleItem_ForOffset(nOffset);
+ }
+
+ // not found, return sal_uInt16 nDel = 0;
+ return 0;
+}
+
+sal_uInt16 SfxItemSet::ClearSingleItem_ForOffset( sal_uInt16 nOffset )
+{
+ assert(nOffset < TotalCount());
+ const_iterator aEntry(begin() + nOffset);
+ assert(nullptr == *aEntry || IsInvalidItem(*aEntry) || (*aEntry)->isVoidItem() || 0 != (*aEntry)->Which());
+
+ if (nullptr == *aEntry)
+ // no entry, done
+ return 0;
+
+ // we reset an entry to nullptr -> decrement count
+ --m_nCount;
+
+ // Notification-Callback
+ if(m_aCallback)
+ {
+ m_aCallback(*aEntry, nullptr);
+ }
+
+ // cleanup item & reset ptr
+ implCleanupItemEntry(*GetPool(), *aEntry);
+ *aEntry = nullptr;
+
+ return 1;
+}
+
+sal_uInt16 SfxItemSet::ClearAllItemsImpl()
+{
+ if (0 == Count())
+ // no items set, done
+ return 0;
+
+ // loop & cleanup items
+ for (auto& rCandidate : *this)
+ {
+ if (nullptr != rCandidate && m_aCallback)
+ {
+ m_aCallback(rCandidate, nullptr);
+ }
+
+ implCleanupItemEntry(*GetPool(), rCandidate);
+ }
+
+ // remember count before resetting it, that is the retval
+ const sal_uInt16 nRetval(Count());
+ m_nCount = 0;
+ return nRetval;
+}
+
+void SfxItemSet::ClearInvalidItems()
+{
+ if (0 == Count())
+ // no items set, done
+ return;
+
+ // loop, here using const_iterator due to need to set ptr in m_ppItems array
+ for (const_iterator candidate(begin()); candidate != end(); candidate++)
+ {
+ if (IsInvalidItem(*candidate))
+ {
+ *candidate = nullptr;
+ --m_nCount;
+ }
+ }
+}
+
+void SfxItemSet::InvalidateAllItems()
+{
+ // instead of just asserting, do the change. Clear all items
+ ClearAllItemsImpl();
+
+ // set all to invalid
+ std::fill(begin(), begin() + TotalCount(), INVALID_POOL_ITEM);
+
+ // ...and correct the count - invalid items get counted
+ m_nCount = m_nTotalCount;
+}
+
+SfxItemState SfxItemSet::GetItemState_ForWhichID( SfxItemState eState, sal_uInt16 nWhich, bool bSrchInParent, const SfxPoolItem **ppItem) const
+{
+ const sal_uInt16 nOffset(GetRanges().getOffsetFromWhich(nWhich));
+
+ if (INVALID_WHICHPAIR_OFFSET != nOffset)
+ {
+ // found, continue with offset
+ eState = GetItemState_ForOffset(nOffset, ppItem);
+ }
+
+ // search in parent?
+ if (bSrchInParent && nullptr != GetParent() && (SfxItemState::UNKNOWN == eState || SfxItemState::DEFAULT == eState))
+ {
+ // nOffset was only valid for *local* SfxItemSet, need to continue with WhichID
+ // Use the *highest* SfxItemState as result
+ return GetParent()->GetItemState_ForWhichID( eState, nWhich, true, ppItem);
+ }
+
+ return eState;
+}
+
+SfxItemState SfxItemSet::GetItemState_ForOffset( sal_uInt16 nOffset, const SfxPoolItem **ppItem) const
+{
+ // check and assert from invalid offset. The caller is responsible for
+ // ensuring a valid offset (see callers, all checked & safe)
+ assert(nOffset < TotalCount());
+ SfxPoolItem const* pCandidate(*(begin() + nOffset));
+
+ if (nullptr == pCandidate)
+ // set to Default
+ return SfxItemState::DEFAULT;
+
+ if (IsInvalidItem(pCandidate))
+ // Different ones are present
+ return SfxItemState::DONTCARE;
+
+ if (pCandidate->isVoidItem())
+ // Item is Disabled
+ return SfxItemState::DISABLED;
+
+ if (nullptr != ppItem)
+ // if we have the Item, add it to output an hand back
+ *ppItem = pCandidate;
+
+ // Item is set
+ return SfxItemState::SET;
+}
+
+bool SfxItemSet::HasItem(sal_uInt16 nWhich, const SfxPoolItem** ppItem) const
+{
+ const bool bRet(SfxItemState::SET == GetItemState_ForWhichID(SfxItemState::UNKNOWN, nWhich, true, ppItem));
+
+ // we need to reset ppItem when it was *not* set by GetItemState_ForWhichID
+ // since many usages of that return parameter re-use it, so it might still
+ // be set to 'something'
+ if (!bRet && nullptr != ppItem)
+ {
+ *ppItem = nullptr;
+ }
+
+ return bRet;
+}
+
+const SfxPoolItem* SfxItemSet::PutImpl(const SfxPoolItem& rItem, sal_uInt16 nWhich, bool bPassingOwnership)
+{
+ bool bActionNeeded(0 != nWhich);
+ sal_uInt16 nOffset(INVALID_WHICHPAIR_OFFSET);
+
+#ifdef _WIN32
+ // Win build *insists* for initialization, triggers warning C4701:
+ // "potentially uninitialized local variable 'aEntry' used" for
+ // lines below. This is not the case here, but I know of no way
+ // to silence the warning in another way
+ const_iterator aEntry(begin());
+#else
+ const_iterator aEntry;
+#endif
+
+ if (bActionNeeded)
+ {
+ nOffset = GetRanges().getOffsetFromWhich(nWhich);
+ bActionNeeded = (INVALID_WHICHPAIR_OFFSET != nOffset);
+ }
+
+ if (bActionNeeded)
+ {
+ aEntry = begin() + nOffset;
+
+ if (nullptr == *aEntry)
+ {
+ // increase count if there was no entry before
+ ++m_nCount;
+ }
+ else
+ {
+ // compare items, evtl. containing content compare
+ bActionNeeded = !SfxPoolItem::areSame(**aEntry, rItem);
+ }
+ }
+
+ if (!bActionNeeded)
+ {
+ if (bPassingOwnership)
+ {
+ delete &rItem;
+ }
+
+ return nullptr;
+ }
+
+ // prepare new entry
+ SfxPoolItem const* pNew(implCreateItemEntry(*GetPool(), &rItem, nWhich, bPassingOwnership));
+
+ // Notification-Callback
+ if(m_aCallback)
+ {
+ m_aCallback(*aEntry, pNew);
+ }
+
+ // cleanup old entry & set entry at m_ppItems array
+ implCleanupItemEntry(*GetPool(), *aEntry);
+ *aEntry = pNew;
+
+ return pNew;
+}
+
+bool SfxItemSet::Put(const SfxItemSet& rSource, bool bInvalidAsDefault)
+{
+ if (0 == rSource.Count())
+ // no items in source, done
+ return false;
+
+ const_iterator aSource(rSource.begin());
+ sal_uInt16 nNumberToGo(rSource.Count());
+ bool bRetval(false);
+
+ // iterate based on WhichIDs to have it available for evtl. PutImpl calls
+ for (const WhichPair& rPair : rSource.GetRanges())
+ {
+ for (sal_uInt16 nWhich(rPair.first); nWhich <= rPair.second; nWhich++, aSource++)
+ {
+ if (nullptr != *aSource)
+ {
+ nNumberToGo--;
+
+ if (IsInvalidItem(*aSource))
+ {
+ if (bInvalidAsDefault)
+ {
+ bRetval |= 0 != ClearSingleItem_ForWhichID(nWhich);
+ }
+ else
+ {
+ InvalidateItem_ForWhichID(nWhich);
+ }
+ }
+ else
+ {
+ bRetval |= nullptr != PutImpl(**aSource, nWhich, false);
+ }
+ }
+
+ if (0 == nNumberToGo)
+ {
+ // we can return early if all set Items are handled
+ return bRetval;
+ }
+ }
+ }
+
+ return bRetval;
+}
+
+/**
+ * This method takes the Items from the 'rSet' and adds to '*this'.
+ * Which ranges in '*this' that are non-existent in 'rSet' will not
+ * be altered. The Which range of '*this' is also not changed.
+ *
+ * Items set in 'rSet' are also set in '*this'.
+ * Default (0 pointer) and Invalid (-1 pointer) Items are processed
+ * according to their parameter 'eDontCareAs' and 'eDefaultAs':
+ *
+ * SfxItemState::SET: Hard set to the default of the Pool
+ * SfxItemState::DEFAULT: Deleted (0 pointer)
+ * SfxItemState::DONTCARE: Invalid (-1 pointer)
+ *
+ * NB: All other values for 'eDontCareAs' and 'eDefaultAs' are invalid
+ */
+void SfxItemSet::PutExtended
+(
+ const SfxItemSet& rSource, // Source of the Items to be put
+ SfxItemState eDontCareAs, // What will happen to the DontCare Items
+ SfxItemState eDefaultAs // What will happen to the Default Items
+)
+{
+ // don't "optimize" with "if( rSource.Count()" because of dontcare + defaults
+ const_iterator aSource(rSource.begin());
+
+ for (const WhichPair& rPair : rSource.GetRanges())
+ {
+ for (sal_uInt16 nWhich = rPair.first; nWhich <= rPair.second; nWhich++, aSource++)
+ {
+ if (nullptr != *aSource)
+ {
+ if (IsInvalidItem(*aSource))
+ {
+ // Item is DontCare:
+ switch (eDontCareAs)
+ {
+ case SfxItemState::SET:
+ PutImpl(rSource.GetPool()->GetDefaultItem(nWhich), nWhich, false);
+ break;
+
+ case SfxItemState::DEFAULT:
+ ClearSingleItem_ForWhichID(nWhich);
+ break;
+
+ case SfxItemState::DONTCARE:
+ InvalidateItem_ForWhichID(nWhich);
+ break;
+
+ default:
+ assert(!"invalid Argument for eDontCareAs");
+ }
+ }
+ else
+ {
+ // Item is set:
+ PutImpl(**aSource, nWhich, false);
+ }
+ }
+ else
+ {
+ // Item is default:
+ switch (eDefaultAs)
+ {
+ case SfxItemState::SET:
+ PutImpl(rSource.GetPool()->GetDefaultItem(nWhich), nWhich, false);
+ break;
+
+ case SfxItemState::DEFAULT:
+ ClearSingleItem_ForWhichID(nWhich);
+ break;
+
+ case SfxItemState::DONTCARE:
+ InvalidateItem_ForWhichID(nWhich);
+ break;
+
+ default:
+ assert(!"invalid Argument for eDefaultAs");
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Expands the ranges of settable items by 'nFrom' to 'nTo'. Keeps state of
+ * items which are new ranges too.
+ */
+void SfxItemSet::MergeRange( sal_uInt16 nFrom, sal_uInt16 nTo )
+{
+ // check if all from new range are already included. This will
+ // use the cache in WhichRangesContainer since we check linearly.
+ // Start with assuming all are included, but only if not empty.
+ // If empty all included is wrong (and GetRanges().MergeRange
+ // will do the right thing/shortcut)
+ bool bAllIncluded(!GetRanges().empty());
+
+ for (sal_uInt16 a(nFrom); bAllIncluded && a <= nTo; a++)
+ if (INVALID_WHICHPAIR_OFFSET == GetRanges().getOffsetFromWhich(a))
+ bAllIncluded = false;
+
+ // if yes, we are done
+ if (bAllIncluded)
+ return;
+
+ // need to create new WhichRanges
+ auto pNewRanges = m_pWhichRanges.MergeRange(nFrom, nTo);
+ RecreateRanges_Impl(pNewRanges);
+ m_pWhichRanges = std::move(pNewRanges);
+}
+
+/**
+ * Modifies the ranges of settable items. Keeps state of items which
+ * are new ranges too.
+ */
+void SfxItemSet::SetRanges( const WhichRangesContainer& pNewRanges )
+{
+ // Identical Ranges?
+ if (GetRanges() == pNewRanges)
+ return;
+
+ assert(svl::detail::validRanges2(pNewRanges));
+ RecreateRanges_Impl(pNewRanges);
+ m_pWhichRanges = pNewRanges;
+}
+
+void SfxItemSet::SetRanges( WhichRangesContainer&& pNewRanges )
+{
+ // Identical Ranges?
+ if (GetRanges() == pNewRanges)
+ return;
+
+ assert(svl::detail::validRanges2(pNewRanges));
+ RecreateRanges_Impl(pNewRanges);
+ m_pWhichRanges = std::move(pNewRanges);
+}
+
+void SfxItemSet::RecreateRanges_Impl(const WhichRangesContainer& rNewRanges)
+{
+ // create new item-array (by iterating through all new ranges)
+ const sal_uInt16 nNewTotalCount(svl::detail::CountRanges(rNewRanges));
+ SfxPoolItem const** aNewItemArray(new const SfxPoolItem* [nNewTotalCount]);
+ sal_uInt16 nNewCount(0);
+
+ if (0 == Count())
+ {
+ // no Items set, just reset to nullptr (default)
+ std::fill(aNewItemArray, aNewItemArray + nNewTotalCount, nullptr);
+ }
+ else
+ {
+ // iterate over target - all entries there need to be set/initialized. Use
+ // WhichIDs, we need to access two different WhichRanges
+ const_iterator aNewTarget(aNewItemArray);
+
+ for (auto const & rNewRange : rNewRanges)
+ {
+ for (sal_uInt16 nWhich(rNewRange.first); nWhich <= rNewRange.second; nWhich++, aNewTarget++)
+ {
+ // check if we have that WhichID in source ranges
+ const sal_uInt16 nSourceOffset(GetRanges().getOffsetFromWhich(nWhich));
+
+ if (INVALID_WHICHPAIR_OFFSET == nSourceOffset)
+ {
+ // no entry in source, init to nullptr
+ *aNewTarget = nullptr;
+ }
+ else
+ {
+ // we have entry in source, transfer entry - whatever it may be,
+ // also for nullptr (for initialization)
+ const_iterator aSourceEntry(begin() + nSourceOffset);
+ *aNewTarget = *aSourceEntry;
+
+ // take care of new Count
+ if (nullptr != *aNewTarget)
+ {
+ nNewCount++;
+ }
+
+ // reset entry, since it got transferred it should not be cleaned-up
+ *aSourceEntry = nullptr;
+ }
+ }
+ }
+
+ // free old items: only necessary when not all Items were transferred
+ if (nNewCount != Count())
+ {
+ ClearAllItemsImpl();
+ }
+ }
+
+ // replace old items-array and ranges
+ if (m_bItemsFixed)
+ {
+ m_bItemsFixed = false;
+ }
+ else
+ {
+ delete[] m_ppItems;
+ }
+
+ m_nTotalCount = nNewTotalCount;
+ m_ppItems = aNewItemArray;
+ m_nCount = nNewCount;
+}
+
+/**
+ * The SfxItemSet takes over exactly those SfxPoolItems that are
+ * set in rSet and are in their own Which range. All others are removed.
+ * The SfxItemPool is retained, such that SfxPoolItems that have been
+ * taken over, are moved from the rSet's SfxItemPool to the SfxItemPool
+ * of *this.
+ *
+ * SfxPoolItems in rSet, for which holds 'IsInvalidItem() == true' are
+ * taken over as invalid items.
+ *
+ * @return bool true
+ * SfxPoolItems have been taken over
+ *
+ * false
+ * No SfxPoolItems have been taken over, because
+ * e.g. the Which ranges of SfxItemSets are not intersecting
+ * or the intersection does not contain SfxPoolItems that are
+ * set in rSet
+ */
+bool SfxItemSet::Set
+(
+ const SfxItemSet& rSet, /* The SfxItemSet, whose SfxPoolItems are
+ to been taken over */
+
+ bool bDeep /* true (default)
+
+ The SfxPoolItems from the parents that may
+ be present in rSet, are also taken over into
+ this SfxPoolItemSet
+
+ false
+ The SfxPoolItems from the parents of
+ rSet are not taken into account */
+)
+{
+ bool bRet = false;
+ if (Count())
+ ClearItem();
+ if ( bDeep )
+ {
+ SfxWhichIter aIter1(*this);
+ SfxWhichIter aIter2(rSet);
+ sal_uInt16 nWhich1 = aIter1.FirstWhich();
+ sal_uInt16 nWhich2 = aIter2.FirstWhich();
+ for (;;)
+ {
+ if (!nWhich1 || !nWhich2)
+ break;
+ if (nWhich1 > nWhich2)
+ {
+ nWhich2 = aIter2.NextWhich();
+ continue;
+ }
+ if (nWhich1 < nWhich2)
+ {
+ nWhich1 = aIter1.NextWhich();
+ continue;
+ }
+ const SfxPoolItem* pItem;
+ if( SfxItemState::SET == aIter2.GetItemState( true, &pItem ) )
+ bRet |= nullptr != Put( *pItem, pItem->Which() );
+ nWhich1 = aIter1.NextWhich();
+ nWhich2 = aIter2.NextWhich();
+ }
+ }
+ else
+ bRet = Put(rSet, false);
+
+ return bRet;
+}
+
+const SfxPoolItem* SfxItemSet::GetItem(sal_uInt16 nId, bool bSearchInParent) const
+{
+ // evtl. Convert from SlotID to WhichId
+ const sal_uInt16 nWhich(GetPool()->GetWhich(nId));
+
+ // Is the Item set or 'bDeep == true' available?
+ const SfxPoolItem *pItem(nullptr);
+ const SfxItemState eState(GetItemState_ForWhichID(SfxItemState::UNKNOWN, nWhich, bSearchInParent, &pItem));
+
+ if (bSearchInParent && SfxItemState::DEFAULT == eState && SfxItemPool::IsWhich(nWhich))
+ {
+ pItem = &GetPool()->GetDefaultItem(nWhich);
+ }
+
+ return pItem;
+}
+
+const SfxPoolItem& SfxItemSet::Get( sal_uInt16 nWhich, bool bSrchInParent) const
+{
+ // Search the Range in which the Which is located in:
+ const sal_uInt16 nOffset(GetRanges().getOffsetFromWhich(nWhich));
+
+ if (INVALID_WHICHPAIR_OFFSET != nOffset)
+ {
+ const_iterator aFoundOne(begin() + nOffset);
+
+ if (nullptr != *aFoundOne)
+ {
+ if (IsInvalidItem(*aFoundOne))
+ {
+ //FIXME: The following code is duplicated further down
+ assert(m_pPool);
+ //!((SfxAllItemSet *)this)->aDefault.SetWhich(nWhich);
+ //!return aDefault;
+ return GetPool()->GetDefaultItem(nWhich);
+ }
+#ifdef DBG_UTIL
+ const SfxPoolItem *pItem = *aFoundOne;
+ if ( pItem->isVoidItem() || !pItem->Which() )
+ SAL_INFO("svl.items", "SFX_WARNING: Getting disabled Item");
+#endif
+ return **aFoundOne;
+ }
+ }
+
+ if (bSrchInParent && nullptr != GetParent())
+ {
+ return GetParent()->Get(nWhich, bSrchInParent);
+ }
+
+ // Get the Default from the Pool and return
+ assert(m_pPool);
+ return GetPool()->GetDefaultItem(nWhich);
+}
+
+/**
+ * Only retain the Items that are also present in rSet
+ * (nevermind their value).
+ */
+void SfxItemSet::Intersect( const SfxItemSet& rSet )
+{
+ // Delete all Items not contained in rSet
+ assert(m_pPool && "Not implemented without Pool");
+
+ if (!Count() || this == &rSet)
+ // none set -> none to delete
+ // same ItemSet? -> no Items not contained
+ return;
+
+ if (!rSet.Count())
+ {
+ // no Items contained in rSet -> Delete everything
+ ClearItem();
+ return;
+ }
+
+ // CAUTION: In the former impl, the
+ // - version for different ranges checked for SfxItemState::UNKNOWN
+ // in rSet -> this means that the WhichID is *not* defined in
+ // the ranges of rSet *at all* > definitely an *error*
+ // - version for same ranges checked for
+ // nullptr != local && nullptr == rSet.
+ // All together I think also using the text
+ // "Delete all Items not contained in rSet" leads to
+ // locally delete all Items that are *not* set in rSet
+ // -> != SfxItemState::SET
+
+ // same ranges?
+ if (GetRanges() == rSet.GetRanges())
+ {
+ for (sal_uInt16 nOffset(0); nOffset < TotalCount(); nOffset++)
+ {
+ if (SfxItemState::SET != rSet.GetItemState_ForOffset(nOffset, nullptr))
+ {
+ // can use same offset
+ ClearSingleItem_ForOffset(nOffset);
+ }
+ }
+ }
+ else
+ {
+ sal_uInt16 nOffset(0);
+
+ for (auto const & rRange : GetRanges())
+ {
+ for (sal_uInt16 nWhich(rRange.first); nWhich <= rRange.second; nWhich++, nOffset++)
+ {
+ if (SfxItemState::SET != rSet.GetItemState_ForWhichID(SfxItemState::UNKNOWN, nWhich, false, nullptr))
+ {
+ // can use offset
+ ClearSingleItem_ForOffset(nOffset);
+ }
+ }
+ }
+ }
+}
+
+void SfxItemSet::Differentiate(const SfxItemSet& rSet)
+{
+ assert(m_pPool && "Not implemented without Pool");
+
+ // Delete all Items contained in rSet
+ if (!Count() || !rSet.Count())
+ // None set?
+ return;
+
+ if (this == &rSet)
+ {
+ // same ItemSet, all Items are contained -> Delete everything
+ ClearItem();
+ return;
+ }
+
+ // CAUTION: In the former impl, the
+ // - version for different ranges checked for SfxItemState::SET
+ // in rSet
+ // - version for same ranges checked for
+ // nullptr != local && nullptr != rSet.
+ // All together I think also using the text
+ // "Delete all Items contained in rSet" leads to
+ // locally delete all Items that *are *not* set in rSet
+ // -> ==SfxItemState::SET
+
+ // same ranges?
+ if (GetRanges() == rSet.GetRanges())
+ {
+ for (sal_uInt16 nOffset(0); nOffset < TotalCount(); nOffset++)
+ {
+ if (SfxItemState::SET == rSet.GetItemState_ForOffset(nOffset, nullptr))
+ {
+ // can use same offset
+ ClearSingleItem_ForOffset(nOffset);
+ }
+ }
+ }
+ else
+ {
+ sal_uInt16 nOffset(0);
+
+ for (auto const & rRange : GetRanges())
+ {
+ for (sal_uInt16 nWhich(rRange.first); nWhich <= rRange.second; nWhich++, nOffset++)
+ {
+ if (SfxItemState::SET == rSet.GetItemState_ForWhichID(SfxItemState::UNKNOWN, nWhich, false, nullptr))
+ {
+ // can use offset
+ ClearSingleItem_ForOffset(nOffset);
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Decision table for MergeValue(s)
+ *
+ * Principles:
+ * 1. If the Which value in the 1st set is "unknown", there's never any action
+ * 2. If the Which value in the 2nd set is "unknown", it's made the "default"
+ * 3. For comparisons the values of the "default" Items are take into account
+ *
+ * 1st Item 2nd Item Values bIgnoreDefs Remove Assign Add
+ *
+ * set set == sal_False - - -
+ * default set == sal_False - - -
+ * dontcare set == sal_False - - -
+ * unknown set == sal_False - - -
+ * set default == sal_False - - -
+ * default default == sal_False - - -
+ * dontcare default == sal_False - - -
+ * unknown default == sal_False - - -
+ * set dontcare == sal_False 1st Item -1 -
+ * default dontcare == sal_False - -1 -
+ * dontcare dontcare == sal_False - - -
+ * unknown dontcare == sal_False - - -
+ * set unknown == sal_False 1st Item -1 -
+ * default unknown == sal_False - - -
+ * dontcare unknown == sal_False - - -
+ * unknown unknown == sal_False - - -
+ *
+ * set set != sal_False 1st Item -1 -
+ * default set != sal_False - -1 -
+ * dontcare set != sal_False - - -
+ * unknown set != sal_False - - -
+ * set default != sal_False 1st Item -1 -
+ * default default != sal_False - - -
+ * dontcare default != sal_False - - -
+ * unknown default != sal_False - - -
+ * set dontcare != sal_False 1st Item -1 -
+ * default dontcare != sal_False - -1 -
+ * dontcare dontcare != sal_False - - -
+ * unknown dontcare != sal_False - - -
+ * set unknown != sal_False 1st Item -1 -
+ * default unknown != sal_False - - -
+ * dontcare unknown != sal_False - - -
+ * unknown unknown != sal_False - - -
+ *
+ * set set == sal_True - - -
+ * default set == sal_True - 2nd Item 2nd Item
+ * dontcare set == sal_True - - -
+ * unknown set == sal_True - - -
+ * set default == sal_True - - -
+ * default default == sal_True - - -
+ * dontcare default == sal_True - - -
+ * unknown default == sal_True - - -
+ * set dontcare == sal_True - - -
+ * default dontcare == sal_True - -1 -
+ * dontcare dontcare == sal_True - - -
+ * unknown dontcare == sal_True - - -
+ * set unknown == sal_True - - -
+ * default unknown == sal_True - - -
+ * dontcare unknown == sal_True - - -
+ * unknown unknown == sal_True - - -
+ *
+ * set set != sal_True 1st Item -1 -
+ * default set != sal_True - 2nd Item 2nd Item
+ * dontcare set != sal_True - - -
+ * unknown set != sal_True - - -
+ * set default != sal_True - - -
+ * default default != sal_True - - -
+ * dontcare default != sal_True - - -
+ * unknown default != sal_True - - -
+ * set dontcare != sal_True 1st Item -1 -
+ * default dontcare != sal_True - -1 -
+ * dontcare dontcare != sal_True - - -
+ * unknown dontcare != sal_True - - -
+ * set unknown != sal_True - - -
+ * default unknown != sal_True - - -
+ * dontcare unknown != sal_True - - -
+ * unknown unknown != sal_True - - -
+ */
+void SfxItemSet::MergeItem_Impl(const SfxPoolItem **ppFnd1, const SfxPoolItem *pFnd2, bool bIgnoreDefaults)
+{
+ assert(ppFnd1 != nullptr && "Merging to 0-Item");
+
+ // 1st Item is Default?
+ if ( !*ppFnd1 )
+ {
+ if ( IsInvalidItem(pFnd2) )
+ // Decision table: default, dontcare, doesn't matter, doesn't matter
+ *ppFnd1 = INVALID_POOL_ITEM;
+
+ else if ( pFnd2 && !bIgnoreDefaults &&
+ GetPool()->GetDefaultItem(pFnd2->Which()) != *pFnd2 )
+ // Decision table: default, set, !=, sal_False
+ *ppFnd1 = INVALID_POOL_ITEM;
+
+ else if ( pFnd2 && bIgnoreDefaults )
+ // Decision table: default, set, doesn't matter, sal_True
+ *ppFnd1 = implCreateItemEntry(*GetPool(), pFnd2, 0, false);
+ // *ppFnd1 = &GetPool()->Put( *pFnd2 );
+
+ if ( *ppFnd1 )
+ ++m_nCount;
+ }
+
+ // 1st Item set?
+ else if ( !IsInvalidItem(*ppFnd1) )
+ {
+ if ( !pFnd2 )
+ {
+ // 2nd Item is Default
+ if ( !bIgnoreDefaults &&
+ **ppFnd1 != GetPool()->GetDefaultItem((*ppFnd1)->Which()) )
+ {
+ // Decision table: set, default, !=, sal_False
+ implCleanupItemEntry(*GetPool(), *ppFnd1);
+ // GetPool()->Remove( **ppFnd1 );
+ *ppFnd1 = INVALID_POOL_ITEM;
+ }
+ }
+ else if ( IsInvalidItem(pFnd2) )
+ {
+ // 2nd Item is dontcare
+ if ( !bIgnoreDefaults ||
+ **ppFnd1 != GetPool()->GetDefaultItem( (*ppFnd1)->Which()) )
+ {
+ // Decision table: set, dontcare, doesn't matter, sal_False
+ // or: set, dontcare, !=, sal_True
+ implCleanupItemEntry(*GetPool(), *ppFnd1);
+ // GetPool()->Remove( **ppFnd1 );
+ *ppFnd1 = INVALID_POOL_ITEM;
+ }
+ }
+ else
+ {
+ // 2nd Item is set
+ if ( **ppFnd1 != *pFnd2 )
+ {
+ // Decision table: set, set, !=, doesn't matter
+ implCleanupItemEntry(*GetPool(), *ppFnd1);
+ // GetPool()->Remove( **ppFnd1 );
+ *ppFnd1 = INVALID_POOL_ITEM;
+ }
+ }
+ }
+}
+
+void SfxItemSet::MergeValues( const SfxItemSet& rSet )
+{
+ // WARNING! When making changes/fixing bugs, always update the table above!!
+ assert( GetPool() == rSet.GetPool() && "MergeValues with different Pools" );
+
+ // CAUTION: Old version did *different* things when the WhichRanges
+ // were the same (true) or different (false) (which is an error/
+ // false optimization):
+ // true: MergeItem_Impl was directly fed with SfxItem*'s
+ // for entry @this & @rSet
+ // false: Looped over rSet WhichID's, fetched defaults from pool,
+ // fed all that to SfxItemSet::MergeValue which then
+ // evtl. could not find that WhichID in local WhichRanges
+ // Better to loop over local WhichRanges (these get changed) and look
+ // for Item with same WhichID in rSet, this is done now.
+ if (GetRanges() == rSet.GetRanges())
+ {
+
+ // loop over both & merge, WhichIDs are identical
+ for (const_iterator dst(begin()), src(rSet.begin()); dst != end(); dst++, src++)
+ {
+ MergeItem_Impl(dst, *src, false/*bIgnoreDefaults*/);
+ }
+ }
+ else
+ {
+ // loop over local which-ranges - the local Items need to be changed
+ const_iterator dst(begin());
+
+ for (auto const & rRange : GetRanges())
+ {
+ for (sal_uInt16 nWhich(rRange.first); nWhich <= rRange.second; nWhich++, dst++)
+ {
+ // try to get offset in rSet for same WhichID
+ const sal_uInt16 nOffset(rSet.GetRanges().getOffsetFromWhich(nWhich));
+
+ if (INVALID_WHICHPAIR_OFFSET != nOffset)
+ {
+ // if entry with same WhichID exists in rSet, merge with local entry
+ MergeItem_Impl(dst, *(rSet.begin() + nOffset), false/*bIgnoreDefaults*/);
+ }
+ }
+ }
+ }
+}
+
+void SfxItemSet::MergeValue(const SfxPoolItem& rAttr, bool bIgnoreDefaults)
+{
+ if (0 == rAttr.Which())
+ // seems to be SfxVoidItem(0), nothing to do
+ return;
+
+ const sal_uInt16 nOffset(GetRanges().getOffsetFromWhich(rAttr.Which()));
+
+ if (INVALID_WHICHPAIR_OFFSET != nOffset)
+ {
+ MergeItem_Impl(begin() + nOffset, &rAttr, bIgnoreDefaults);
+ }
+}
+
+void SfxItemSet::InvalidateItem_ForWhichID(sal_uInt16 nWhich)
+{
+ const sal_uInt16 nOffset(GetRanges().getOffsetFromWhich(nWhich));
+
+ if (INVALID_WHICHPAIR_OFFSET != nOffset)
+ {
+ InvalidateItem_ForOffset(nOffset);
+ }
+}
+
+void SfxItemSet::InvalidateItem_ForOffset(sal_uInt16 nOffset)
+{
+ // check and assert from invalid offset. The caller is responsible for
+ // ensuring a valid offset (see callers, all checked & safe)
+ assert(nOffset < TotalCount());
+ const_iterator aFoundOne(begin() + nOffset);
+
+ if (nullptr == *aFoundOne)
+ {
+ // entry goes from nullptr -> invalid
+ ++m_nCount;
+ }
+ else
+ {
+ // entry is set
+ if (IsInvalidItem(*aFoundOne))
+ // already invalid item, done
+ return;
+
+ // cleanup entry
+ implCleanupItemEntry(*GetPool(), *aFoundOne);
+ }
+
+ // set new entry
+ *aFoundOne = INVALID_POOL_ITEM;
+}
+
+sal_uInt16 SfxItemSet::GetWhichByOffset( sal_uInt16 nOffset ) const
+{
+ assert(nOffset < TotalCount());
+
+ // 1st try to get a set SfxPoolItem and fetch the WhichID from there.
+ const SfxPoolItem* pItem(nullptr);
+ (void)GetItemState_ForOffset(nOffset, &pItem);
+
+ if (nullptr != pItem && 0 != pItem->Which())
+ return pItem->Which();
+
+ // 2nd have to get from WhichRangesContainer. That might use
+ // the buffering, too. We might assert a return value of zero
+ // (which means invalid WhichID), but we already assert for
+ // a valid offset at the start of this method
+ return GetRanges().getWhichFromOffset(nOffset);
+}
+
+bool SfxItemSet::operator==(const SfxItemSet &rCmp) const
+{
+ return Equals( rCmp, true);
+}
+
+bool SfxItemSet::Equals(const SfxItemSet &rCmp, bool bComparePool) const
+{
+ // check if same incarnation
+ if (this == &rCmp)
+ return true;
+
+ // check parents (if requested, also bComparePool)
+ if (bComparePool && GetParent() != rCmp.GetParent())
+ return false;
+
+ // check pools (if requested)
+ if (bComparePool && GetPool() != rCmp.GetPool())
+ return false;
+
+ // check count of set items
+ if (Count() != rCmp.Count())
+ return false;
+
+ // both have no items, done
+ if (0 == Count())
+ return true;
+
+ // check if ranges are equal
+ if (GetRanges() == rCmp.GetRanges())
+ {
+ // if yes, we can simplify: are all pointers the same?
+ if (0 == memcmp( m_ppItems, rCmp.m_ppItems, TotalCount() * sizeof(m_ppItems[0]) ))
+ return true;
+
+ // compare each one separately
+ const SfxPoolItem **ppItem1(m_ppItems);
+ const SfxPoolItem **ppItem2(rCmp.m_ppItems);
+
+ for (sal_uInt16 nPos(0); nPos < TotalCount(); nPos++)
+ {
+ // do full SfxPoolItem compare
+ if (!SfxPoolItem::areSame(*ppItem1, *ppItem2))
+ return false;
+ ++ppItem1;
+ ++ppItem2;
+ }
+
+ return true;
+ }
+
+ // Not same ranges, need to compare content. Only need to check if
+ // the content of one is inside the other due to already having
+ // compared Count() above.
+ // Iterate over local SfxItemSet by using locval ranges and offset,
+ // so we can access needed data at least for one SfxItemSet more
+ // direct. For the 2nd one we need the WhichID which we have by
+ // iterating over the ranges.
+ sal_uInt16 nOffset(0);
+ sal_uInt16 nNumberToGo(Count());
+
+ for (auto const & rRange : GetRanges())
+ {
+ for (sal_uInt16 nWhich(rRange.first); nWhich <= rRange.second; nWhich++, nOffset++)
+ {
+ const SfxPoolItem *pItem1(nullptr);
+ const SfxPoolItem *pItem2(nullptr);
+ const SfxItemState aStateA(GetItemState_ForOffset(nOffset, &pItem1));
+ const SfxItemState aStateB(rCmp.GetItemState_ForWhichID(SfxItemState::UNKNOWN, nWhich, false, &pItem2));
+
+ if (aStateA != aStateB)
+ return false;
+
+ // only compare items if SfxItemState::SET, else the item ptrs are not set
+ if (SfxItemState::SET == aStateA && !SfxPoolItem::areSame(pItem1, pItem2))
+ return false;
+
+ if (SfxItemState::DEFAULT != aStateA)
+ // if local item is not-nullptr we have compared one more, reduce NumberToGo
+ // NOTE: we could also use 'nullptr != *(begin() + nOffset)' here, but the
+ // entry was already checked by GetItemState_ForOffset above
+ nNumberToGo--;
+
+ if (0 == nNumberToGo)
+ // if we have compared Count() number of items and are still here
+ // (all were equal), we can exit early
+ return true;
+ }
+ }
+
+ return true;
+}
+
+std::unique_ptr<SfxItemSet> SfxItemSet::Clone(bool bItems, SfxItemPool *pToPool ) const
+{
+ if (pToPool && pToPool != GetPool())
+ {
+ std::unique_ptr<SfxItemSet> pNewSet(new SfxItemSet(*pToPool, GetRanges()));
+ if ( bItems )
+ {
+ SfxWhichIter aIter(*pNewSet);
+ sal_uInt16 nWhich = aIter.FirstWhich();
+ while ( nWhich )
+ {
+ const SfxPoolItem* pItem;
+ if ( SfxItemState::SET == GetItemState_ForWhichID(SfxItemState::UNKNOWN, nWhich, false, &pItem ) )
+ pNewSet->Put( *pItem, pItem->Which() );
+ nWhich = aIter.NextWhich();
+ }
+ }
+ return pNewSet;
+ }
+ else
+ return std::unique_ptr<SfxItemSet>(bItems
+ ? new SfxItemSet(*this)
+ : new SfxItemSet(*GetPool(), GetRanges()));
+}
+
+SfxItemSet SfxItemSet::CloneAsValue(bool bItems, SfxItemPool *pToPool ) const
+{
+ // if you are trying to clone, then the thing you are cloning is polymorphic, which means
+ // it cannot be cloned as a value
+ assert((typeid(*this) == typeid(SfxItemSet)) && "cannot call this on a subclass of SfxItemSet");
+
+ if (pToPool && pToPool != GetPool())
+ {
+ SfxItemSet aNewSet(*pToPool, GetRanges());
+ if ( bItems )
+ {
+ SfxWhichIter aIter(aNewSet);
+ sal_uInt16 nWhich = aIter.FirstWhich();
+ while ( nWhich )
+ {
+ const SfxPoolItem* pItem;
+ if ( SfxItemState::SET == GetItemState_ForWhichID(SfxItemState::UNKNOWN, nWhich, false, &pItem ) )
+ aNewSet.Put( *pItem, pItem->Which() );
+ nWhich = aIter.NextWhich();
+ }
+ }
+ return aNewSet;
+ }
+ else
+ return bItems
+ ? *this
+ : SfxItemSet(*GetPool(), GetRanges());
+}
+
+void SfxItemSet::dumpAsXml(xmlTextWriterPtr pWriter) const
+{
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SfxItemSet"));
+ SfxItemIter aIter(*this);
+ for (const SfxPoolItem* pItem = aIter.GetCurItem(); pItem; pItem = aIter.NextItem())
+ {
+ if (IsInvalidItem(pItem))
+ {
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("invalid"));
+ (void)xmlTextWriterEndElement(pWriter);
+ }
+ else
+ {
+ pItem->dumpAsXml(pWriter);
+ }
+ }
+ (void)xmlTextWriterEndElement(pWriter);
+}
+
+
+// ----------------------------------------------- class SfxAllItemSet
+
+SfxAllItemSet::SfxAllItemSet( SfxItemPool &rPool )
+: SfxItemSet(rPool, SfxAllItemSetFlag::Flag)
+{
+}
+
+SfxAllItemSet::SfxAllItemSet(const SfxItemSet &rCopy)
+: SfxItemSet(rCopy)
+{
+}
+
+/**
+ * Explicitly define this ctor to avoid auto-generation by the compiler.
+ * The compiler does not take the ctor with the 'const SfxItemSet&'!
+ */
+SfxAllItemSet::SfxAllItemSet(const SfxAllItemSet &rCopy)
+: SfxItemSet(rCopy)
+{
+}
+
+/**
+ * Putting with automatic extension of the WhichId with the ID of the Item.
+ */
+const SfxPoolItem* SfxAllItemSet::PutImpl( const SfxPoolItem& rItem, sal_uInt16 nWhich, bool bPassingOwnership )
+{
+ MergeRange(nWhich, nWhich);
+ return SfxItemSet::PutImpl(rItem, nWhich, bPassingOwnership);
+}
+
+/**
+ * Disable Item
+ * Using a VoidItem with Which value 0
+ */
+void SfxItemSet::DisableItem(sal_uInt16 nWhich)
+{
+ Put( SfxVoidItem(0), nWhich );
+}
+
+std::unique_ptr<SfxItemSet> SfxAllItemSet::Clone(bool bItems, SfxItemPool *pToPool ) const
+{
+ if (pToPool && pToPool != GetPool())
+ {
+ std::unique_ptr<SfxAllItemSet> pNewSet(new SfxAllItemSet( *pToPool ));
+ if ( bItems )
+ pNewSet->Set( *this );
+ return pNewSet;
+ }
+ else
+ return std::unique_ptr<SfxItemSet>(bItems ? new SfxAllItemSet(*this) : new SfxAllItemSet(*GetPool()));
+}
+
+
+WhichRangesContainer::WhichRangesContainer( const WhichPair* wids, sal_Int32 nSize )
+{
+ auto p = new WhichPair[nSize];
+ for (int i=0; i<nSize; ++i)
+ p[i] = wids[i];
+ m_pairs = p;
+ m_size = nSize;
+ m_bOwnRanges = true;
+ m_aLastWhichPairOffset = INVALID_WHICHPAIR_OFFSET;
+ m_aLastWhichPairFirst = 0;
+ m_aLastWhichPairSecond = 0;
+}
+
+WhichRangesContainer::WhichRangesContainer(sal_uInt16 nWhichStart, sal_uInt16 nWhichEnd)
+ : m_size(1), m_bOwnRanges(true)
+{
+ auto p = new WhichPair[1];
+ p[0] = { nWhichStart, nWhichEnd };
+ m_pairs = p;
+ m_aLastWhichPairOffset = INVALID_WHICHPAIR_OFFSET;
+ m_aLastWhichPairFirst = 0;
+ m_aLastWhichPairSecond = 0;
+}
+
+WhichRangesContainer::WhichRangesContainer(WhichRangesContainer && other)
+{
+ std::swap(m_pairs, other.m_pairs);
+ std::swap(m_size, other.m_size);
+ std::swap(m_bOwnRanges, other.m_bOwnRanges);
+ m_aLastWhichPairOffset = INVALID_WHICHPAIR_OFFSET;
+ m_aLastWhichPairFirst = 0;
+ m_aLastWhichPairSecond = 0;
+}
+
+WhichRangesContainer& WhichRangesContainer::operator=(WhichRangesContainer && other)
+{
+ std::swap(m_pairs, other.m_pairs);
+ std::swap(m_size, other.m_size);
+ std::swap(m_bOwnRanges, other.m_bOwnRanges);
+ m_aLastWhichPairOffset = INVALID_WHICHPAIR_OFFSET;
+ m_aLastWhichPairFirst = 0;
+ m_aLastWhichPairSecond = 0;
+ return *this;
+}
+
+WhichRangesContainer& WhichRangesContainer::operator=(WhichRangesContainer const & other)
+{
+ reset();
+ m_size = other.m_size;
+ m_bOwnRanges = other.m_bOwnRanges;
+ m_aLastWhichPairOffset = INVALID_WHICHPAIR_OFFSET;
+ m_aLastWhichPairFirst = 0;
+ m_aLastWhichPairSecond = 0;
+ if (m_bOwnRanges)
+ {
+ auto p = new WhichPair[m_size];
+ for (int i=0; i<m_size; ++i)
+ p[i] = other.m_pairs[i];
+ m_pairs = p;
+ }
+ else
+ m_pairs = other.m_pairs;
+ return *this;
+}
+
+WhichRangesContainer::~WhichRangesContainer()
+{
+ reset();
+}
+
+bool WhichRangesContainer::operator==(WhichRangesContainer const & other) const
+{
+ if (m_size != other.m_size)
+ return false;
+ if (m_pairs == other.m_pairs)
+ return true;
+ return std::equal(m_pairs, m_pairs + m_size, other.m_pairs, other.m_pairs + m_size);
+}
+
+
+void WhichRangesContainer::reset()
+{
+ if (m_bOwnRanges)
+ {
+ delete [] m_pairs;
+ m_bOwnRanges = false;
+ }
+ m_pairs = nullptr;
+ m_size = 0;
+ m_aLastWhichPairOffset = INVALID_WHICHPAIR_OFFSET;
+ m_aLastWhichPairFirst = 0;
+ m_aLastWhichPairSecond = 0;
+}
+
+#ifdef DBG_UTIL
+static size_t g_nHit(0);
+static size_t g_nMiss(1);
+static bool g_bShowWhichRangesHitRate(getenv("SVL_SHOW_WHICHRANGES_HITRATE"));
+static void isHit() { g_nHit++; }
+static void isMiss()
+{
+ g_nMiss++;
+ const double fHitRate(double(g_nHit) /double(g_nMiss));
+ if (0 == g_nMiss % 1000 && g_bShowWhichRangesHitRate)
+ SAL_WARN("svl", "ITEM: hits: " << g_nHit << " misses: " << g_nMiss << " hits/misses(rate): " << fHitRate);
+}
+#endif
+
+sal_uInt16 WhichRangesContainer::getOffsetFromWhich(sal_uInt16 nWhich) const
+{
+ if (empty())
+ return INVALID_WHICHPAIR_OFFSET;
+
+ // special case for single entry - happens often e.g. UI stuff
+ if (1 == m_size)
+ {
+ if( m_pairs->first <= nWhich && nWhich <= m_pairs->second )
+ return nWhich - m_pairs->first;
+
+ // we have only one WhichPair entry and it's not contained -> failed
+ return INVALID_WHICHPAIR_OFFSET;
+ }
+
+ // check if nWhich is inside last successfully used WhichPair
+ if (INVALID_WHICHPAIR_OFFSET != m_aLastWhichPairOffset
+ && m_aLastWhichPairFirst <= nWhich
+ && nWhich <= m_aLastWhichPairSecond)
+ {
+#ifdef DBG_UTIL
+ isHit();
+#endif
+ // we can re-use the last found WhichPair
+ return m_aLastWhichPairOffset + (nWhich - m_aLastWhichPairFirst);
+ }
+
+#ifdef DBG_UTIL
+ isMiss();
+#endif
+
+ // we have to find the correct WhichPair, iterate linear. This
+ // also directly updates the buffered m_aLastWhichPair* values
+ m_aLastWhichPairOffset = 0;
+
+ for (const WhichPair& rPair : *this)
+ {
+ // Within this range?
+ if( rPair.first <= nWhich && nWhich <= rPair.second )
+ {
+ // found, remember parameters for buffered hits
+ m_aLastWhichPairFirst = rPair.first;
+ m_aLastWhichPairSecond = rPair.second;
+
+ // ...and return
+ return m_aLastWhichPairOffset + (nWhich - m_aLastWhichPairFirst);
+ }
+
+ m_aLastWhichPairOffset += rPair.second - rPair.first + 1;
+ }
+
+ // *need* to reset: if 1st WhichPair only one entry it could be 1
+ // what could wrongly trigger re-use above for next search
+ m_aLastWhichPairOffset = INVALID_WHICHPAIR_OFFSET;
+
+ return m_aLastWhichPairOffset;
+}
+
+sal_uInt16 WhichRangesContainer::getWhichFromOffset(sal_uInt16 nOffset) const
+{
+ // check for empty, if yes, return null which is an invalid WhichID
+ if (empty())
+ return 0;
+
+ // special case for single entry - happens often e.g. UI stuff
+ if (1 == m_size)
+ {
+ if (nOffset <= m_pairs->second - m_pairs->first)
+ return m_pairs->first + nOffset;
+
+ // we have only one WhichPair entry and it's not contained -> failed
+ return 0;
+ }
+
+ // check if nWhich is inside last successfully used WhichPair
+ if (INVALID_WHICHPAIR_OFFSET != m_aLastWhichPairOffset)
+ {
+ // only try if we are beyond or at m_aLastWhichPairOffset to
+ // not get numerically negative
+ if (nOffset >= m_aLastWhichPairOffset)
+ {
+ const sal_uInt16 nAdaptedOffset(nOffset - m_aLastWhichPairOffset);
+
+ if (nAdaptedOffset <= m_aLastWhichPairSecond - m_aLastWhichPairFirst)
+ {
+#ifdef DBG_UTIL
+ isHit();
+#endif
+ return m_aLastWhichPairFirst + nAdaptedOffset;
+ }
+ }
+ }
+
+#ifdef DBG_UTIL
+ isMiss();
+#endif
+
+ // Iterate over WhichPairs in WhichRangesContainer
+ // Do not update buffered last hit (m_aLastWhichPair*), these calls
+ // are potentially more rare than getOffsetFromWhich calls. Still,
+ // it could also be done here
+ for( auto const & pPtr : *this )
+ {
+ const sal_uInt16 nWhichPairRange(pPtr.second - pPtr.first);
+ if( nOffset <= nWhichPairRange )
+ return pPtr.first + nOffset;
+ nOffset -= nWhichPairRange + 1;
+ }
+
+ // no WhichID found, return invalid one
+ return 0;
+}
+
+// Adds a range to which ranges, keeping the ranges in valid state (sorted, non-overlapping)
+WhichRangesContainer WhichRangesContainer::MergeRange(sal_uInt16 nFrom,
+ sal_uInt16 nTo) const
+{
+ assert(svl::detail::validRange(nFrom, nTo));
+
+ if (empty())
+ return WhichRangesContainer(nFrom, nTo);
+
+ // reset buffer
+ m_aLastWhichPairOffset = INVALID_WHICHPAIR_OFFSET;
+
+ // create vector of ranges (sal_uInt16 pairs of lower and upper bound)
+ const size_t nOldCount = size();
+ // Allocate one item more than we already have.
+ // In the worst case scenario we waste a little bit
+ // of memory, but we avoid another allocation, which is more important.
+ std::unique_ptr<WhichPair[]> aRangesTable(new WhichPair[nOldCount+1]);
+ int aRangesTableSize = 0;
+ bool bAdded = false;
+ for (const auto& rPair : *this)
+ {
+ if (!bAdded && rPair.first >= nFrom)
+ { // insert new range, keep ranges sorted
+ aRangesTable[aRangesTableSize++] = { nFrom, nTo };
+ bAdded = true;
+ }
+ // insert current range
+ aRangesTable[aRangesTableSize++] = rPair;
+ }
+ if (!bAdded)
+ aRangesTable[aRangesTableSize++] = { nFrom, nTo };
+
+ // true if ranges overlap or adjoin, false if ranges are separate
+ auto needMerge = [](WhichPair lhs, WhichPair rhs) {
+ return (lhs.first - 1) <= rhs.second && (rhs.first - 1) <= lhs.second;
+ };
+
+ auto it = aRangesTable.get();
+ auto endIt = aRangesTable.get() + aRangesTableSize;
+ // we have at least one range at this point
+ for (;;)
+ {
+ auto itNext = std::next(it);
+ if (itNext == endIt)
+ break;
+ // check if neighbouring ranges overlap or adjoin
+ if (needMerge(*it, *itNext))
+ {
+ // lower bounds are sorted, implies: it->first = min(it[0].first, it[1].first)
+ it->second = std::max(it->second, itNext->second);
+ // remove next element
+ std::move(std::next(itNext), endIt, itNext);
+ --aRangesTableSize;
+ endIt = aRangesTable.get() + aRangesTableSize;
+ }
+ else
+ ++it;
+ }
+
+ return WhichRangesContainer(std::move(aRangesTable), aRangesTableSize);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/items/lckbitem.cxx b/svl/source/items/lckbitem.cxx
new file mode 100644
index 0000000000..87165ae507
--- /dev/null
+++ b/svl/source/items/lckbitem.cxx
@@ -0,0 +1,102 @@
+/* -*- 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 <svl/lckbitem.hxx>
+#include <tools/stream.hxx>
+#include <osl/diagnose.h>
+#include <com/sun/star/uno/Any.hxx>
+#include <com/sun/star/uno/Sequence.hxx>
+
+
+SfxPoolItem* SfxLockBytesItem::CreateDefault() { return new SfxLockBytesItem; }
+
+
+SfxLockBytesItem::SfxLockBytesItem()
+{
+}
+
+
+SfxLockBytesItem::~SfxLockBytesItem()
+{
+}
+
+
+bool SfxLockBytesItem::operator==( const SfxPoolItem& rItem ) const
+{
+ return SfxPoolItem::operator==(rItem) && static_cast<const SfxLockBytesItem&>(rItem)._xVal == _xVal;
+}
+
+SfxLockBytesItem* SfxLockBytesItem::Clone(SfxItemPool *) const
+{
+ return new SfxLockBytesItem( *this );
+}
+
+// virtual
+bool SfxLockBytesItem::PutValue( const css::uno::Any& rVal, sal_uInt8 )
+{
+ css::uno::Sequence< sal_Int8 > aSeq;
+ if ( rVal >>= aSeq )
+ {
+ if ( aSeq.hasElements() )
+ {
+ SvMemoryStream* pStream = new SvMemoryStream();
+ pStream->WriteBytes( aSeq.getConstArray(), aSeq.getLength() );
+ pStream->Seek(0);
+
+ _xVal = new SvLockBytes( pStream, true );
+ }
+ else
+ _xVal = nullptr;
+
+ return true;
+ }
+
+ OSL_FAIL( "SfxLockBytesItem::PutValue - Wrong type!" );
+ return true;
+}
+
+// virtual
+bool SfxLockBytesItem::QueryValue( css::uno::Any& rVal, sal_uInt8 ) const
+{
+ if ( _xVal.is() )
+ {
+ sal_uInt32 nLen;
+ SvLockBytesStat aStat;
+
+ if ( _xVal->Stat( &aStat ) == ERRCODE_NONE )
+ nLen = aStat.nSize;
+ else
+ return false;
+
+ std::size_t nRead = 0;
+ css::uno::Sequence< sal_Int8 > aSeq( nLen );
+
+ _xVal->ReadAt( 0, aSeq.getArray(), nLen, &nRead );
+ rVal <<= aSeq;
+ }
+ else
+ {
+ css::uno::Sequence< sal_Int8 > aSeq( 0 );
+ rVal <<= aSeq;
+ }
+
+ return true;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/items/legacyitem.cxx b/svl/source/items/legacyitem.cxx
new file mode 100644
index 0000000000..b0daa3b906
--- /dev/null
+++ b/svl/source/items/legacyitem.cxx
@@ -0,0 +1,69 @@
+/* -*- 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 <svl/legacyitem.hxx>
+#include <tools/stream.hxx>
+#include <svl/eitem.hxx>
+#include <svl/cintitem.hxx>
+
+namespace legacy
+{
+ namespace SfxBool
+ {
+ sal_uInt16 GetVersion(sal_uInt16)
+ {
+ return 0;
+ }
+
+ void Create(SfxBoolItem& rItem, SvStream& rStrm, sal_uInt16)
+ {
+ bool tmp(false);
+ rStrm.ReadCharAsBool(tmp);
+ rItem.SetValue(tmp);
+ }
+
+ SvStream& Store(const SfxBoolItem& rItem, SvStream& rStrm, sal_uInt16)
+ {
+ rStrm.WriteBool(rItem.GetValue()); // not bool for serialization!
+ return rStrm;
+ }
+ }
+ namespace CntInt32
+ {
+ sal_uInt16 GetVersion(sal_uInt16)
+ {
+ return 0;
+ }
+
+ void Create(CntInt32Item& rItem, SvStream& rStrm, sal_uInt16)
+ {
+ sal_Int32 tmp(0);
+ rStrm.ReadInt32(tmp);
+ rItem.SetValue(tmp);
+ }
+
+ SvStream& Store(const CntInt32Item& rItem, SvStream& rStrm, sal_uInt16)
+ {
+ rStrm.WriteInt32(rItem.GetValue());
+ return rStrm;
+ }
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/items/macitem.cxx b/svl/source/items/macitem.cxx
new file mode 100644
index 0000000000..0b242c7cff
--- /dev/null
+++ b/svl/source/items/macitem.cxx
@@ -0,0 +1,243 @@
+/* -*- 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/safeint.hxx>
+#include <sal/log.hxx>
+#include <comphelper/fileformat.h>
+#include <tools/stream.hxx>
+
+#include <svl/macitem.hxx>
+#include <stringio.hxx>
+#include <algorithm>
+#include <utility>
+
+SvxMacro::SvxMacro( OUString _aMacName, const OUString &rLanguage)
+ : aMacName(std::move( _aMacName )), aLibName( rLanguage),
+ eType( EXTENDED_STYPE)
+{
+ if ( rLanguage == SVX_MACRO_LANGUAGE_STARBASIC )
+ eType=STARBASIC;
+ else if ( rLanguage == SVX_MACRO_LANGUAGE_JAVASCRIPT )
+ eType=JAVASCRIPT;
+}
+
+OUString SvxMacro::GetLanguage()const
+{
+ if(eType==STARBASIC)
+ {
+ return SVX_MACRO_LANGUAGE_STARBASIC;
+ }
+ else if(eType==JAVASCRIPT)
+ {
+ return SVX_MACRO_LANGUAGE_JAVASCRIPT;
+ }
+ else if(eType==EXTENDED_STYPE)
+ {
+ return SVX_MACRO_LANGUAGE_SF;
+
+ }
+ return aLibName;
+}
+
+SvxMacroTableDtor& SvxMacroTableDtor::operator=( const SvxMacroTableDtor& rTbl )
+{
+ if (this != &rTbl)
+ {
+ aSvxMacroTable.clear();
+ aSvxMacroTable.insert(rTbl.aSvxMacroTable.begin(), rTbl.aSvxMacroTable.end());
+ }
+ return *this;
+}
+
+bool SvxMacroTableDtor::operator==( const SvxMacroTableDtor& rOther ) const
+{
+ // Count different => odd in any case
+ // Compare single ones; the sequence matters due to performance reasons
+ return std::equal(aSvxMacroTable.begin(), aSvxMacroTable.end(),
+ rOther.aSvxMacroTable.begin(), rOther.aSvxMacroTable.end(),
+ [](const SvxMacroTable::value_type& rOwnEntry, const SvxMacroTable::value_type& rOtherEntry) {
+ const SvxMacro& rOwnMac = rOwnEntry.second;
+ const SvxMacro& rOtherMac = rOtherEntry.second;
+ return rOwnEntry.first == rOtherEntry.first
+ && rOwnMac.GetLibName() == rOtherMac.GetLibName()
+ && rOwnMac.GetMacName() == rOtherMac.GetMacName();
+ });
+}
+
+void SvxMacroTableDtor::Read( SvStream& rStrm )
+{
+ sal_uInt16 nVersion;
+ rStrm.ReadUInt16( nVersion );
+
+ short nReadMacro(0);
+ rStrm.ReadInt16(nReadMacro);
+ if (nReadMacro < 0)
+ {
+ SAL_WARN("editeng", "Parsing error: negative value " << nReadMacro);
+ return;
+ }
+
+ auto nMacro = o3tl::make_unsigned(nReadMacro);
+
+ const size_t nMinStringSize = rStrm.GetStreamCharSet() == RTL_TEXTENCODING_UNICODE ? 4 : 2;
+ size_t nMinRecordSize = 2 + 2*nMinStringSize;
+ if( SVX_MACROTBL_VERSION40 <= nVersion )
+ nMinRecordSize+=2;
+
+ const size_t nMaxRecords = rStrm.remainingSize() / nMinRecordSize;
+ if (nMacro > nMaxRecords)
+ {
+ SAL_WARN("editeng", "Parsing error: " << nMaxRecords <<
+ " max possible entries, but " << nMacro<< " claimed, truncating");
+ nMacro = nMaxRecords;
+ }
+
+ for (decltype(nMacro) i = 0; i < nMacro; ++i)
+ {
+ sal_uInt16 nCurKey, eType = STARBASIC;
+ OUString aLibName, aMacName;
+ rStrm.ReadUInt16( nCurKey );
+ aLibName = readByteString(rStrm);
+ aMacName = readByteString(rStrm);
+
+ if( SVX_MACROTBL_VERSION40 <= nVersion )
+ rStrm.ReadUInt16( eType );
+
+ aSvxMacroTable.emplace( SvMacroItemId(nCurKey), SvxMacro( aMacName, aLibName, static_cast<ScriptType>(eType) ) );
+ }
+}
+
+SvStream& SvxMacroTableDtor::Write( SvStream& rStream ) const
+{
+ sal_uInt16 nVersion = SOFFICE_FILEFORMAT_31 == rStream.GetVersion()
+ ? SVX_MACROTBL_VERSION31
+ : SVX_MACROTBL_VERSION40;
+
+ if( SVX_MACROTBL_VERSION40 <= nVersion )
+ rStream.WriteUInt16( nVersion );
+
+ rStream.WriteUInt16( aSvxMacroTable.size() );
+
+ for( const auto& rEntry : aSvxMacroTable )
+ {
+ if (rStream.GetError() != ERRCODE_NONE)
+ break;
+ const SvxMacro& rMac = rEntry.second;
+ rStream.WriteUInt16( static_cast<sal_uInt16>(rEntry.first) );
+ writeByteString(rStream, rMac.GetLibName());
+ writeByteString(rStream, rMac.GetMacName());
+
+ if( SVX_MACROTBL_VERSION40 <= nVersion )
+ rStream.WriteUInt16( rMac.GetScriptType() );
+ }
+ return rStream;
+}
+
+// returns NULL if no entry exists, or a pointer to the internal value
+const SvxMacro* SvxMacroTableDtor::Get(SvMacroItemId nEvent) const
+{
+ SvxMacroTable::const_iterator it = aSvxMacroTable.find(nEvent);
+ return it == aSvxMacroTable.end() ? nullptr : &(it->second);
+}
+
+// returns NULL if no entry exists, or a pointer to the internal value
+SvxMacro* SvxMacroTableDtor::Get(SvMacroItemId nEvent)
+{
+ SvxMacroTable::iterator it = aSvxMacroTable.find(nEvent);
+ return it == aSvxMacroTable.end() ? nullptr : &(it->second);
+}
+
+// return true if the key exists
+bool SvxMacroTableDtor::IsKeyValid(SvMacroItemId nEvent) const
+{
+ SvxMacroTable::const_iterator it = aSvxMacroTable.find(nEvent);
+ return it != aSvxMacroTable.end();
+}
+
+// This stores a copy of the rMacro parameter
+SvxMacro& SvxMacroTableDtor::Insert(SvMacroItemId nEvent, const SvxMacro& rMacro)
+{
+ return aSvxMacroTable.emplace( nEvent, rMacro ).first->second;
+}
+
+// If the entry exists, remove it from the map and release it's storage
+void SvxMacroTableDtor::Erase(SvMacroItemId nEvent)
+{
+ SvxMacroTable::iterator it = aSvxMacroTable.find(nEvent);
+ if ( it != aSvxMacroTable.end())
+ {
+ aSvxMacroTable.erase(it);
+ }
+}
+
+bool SvxMacroItem::operator==( const SfxPoolItem& rAttr ) const
+{
+ assert(SfxPoolItem::operator==(rAttr));
+
+ const SvxMacroTableDtor& rOwn = aMacroTable;
+ const SvxMacroTableDtor& rOther = static_cast<const SvxMacroItem&>(rAttr).aMacroTable;
+
+ return rOwn == rOther;
+}
+
+SvxMacroItem* SvxMacroItem::Clone( SfxItemPool* ) const
+{
+ return new SvxMacroItem( *this );
+}
+
+bool SvxMacroItem::GetPresentation
+(
+ SfxItemPresentation /*ePres*/,
+ MapUnit /*eCoreUnit*/,
+ MapUnit /*ePresUnit*/,
+ OUString& rText,
+ const IntlWrapper&
+) const
+{
+/*!!!
+ SvxMacroTableDtor& rTbl = (SvxMacroTableDtor&)GetMacroTable();
+ SvxMacro* pMac = rTbl.First();
+
+ while ( pMac )
+ {
+ rText += pMac->GetLibName();
+ rText += cpDelim;
+ rText += pMac->GetMacName();
+ pMac = rTbl.Next();
+ if ( pMac )
+ rText += cpDelim;
+ }
+*/
+ rText.clear();
+ return false;
+}
+
+
+void SvxMacroItem::SetMacro( SvMacroItemId nEvent, const SvxMacro& rMacro )
+{
+ // tdf#141123: emplace doesn't replace the element in the map if already exists
+ // see https://en.cppreference.com/w/cpp/container/map/emplace
+ // so first erase the macro if there's one for this event
+ aMacroTable.Erase(nEvent);
+ aMacroTable.Insert( nEvent, rMacro);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/items/poolitem.cxx b/svl/source/items/poolitem.cxx
new file mode 100644
index 0000000000..ae7c97136c
--- /dev/null
+++ b/svl/source/items/poolitem.cxx
@@ -0,0 +1,703 @@
+/* -*- 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 <svl/poolitem.hxx>
+#include <unotools/intlwrapper.hxx>
+#include <unotools/syslocale.hxx>
+#include <osl/diagnose.h>
+#include <sal/log.hxx>
+#include <libxml/xmlwriter.h>
+#include <typeinfo>
+#include <boost/property_tree/ptree.hpp>
+
+#ifdef DBG_UTIL
+#include <unordered_set>
+#endif
+
+//////////////////////////////////////////////////////////////////////////////
+// list of classes derived from SfxPoolItem
+// will not be kept up-to-date, but give a good overview for right now
+//////////////////////////////////////////////////////////////////////////////
+//
+// class SbxItem : public SfxPoolItem
+// class SvxChartColorTableItem : public SfxPoolItem
+// class DriverPoolingSettingsItem final : public SfxPoolItem
+// class DatabaseMapItem final : public SfxPoolItem
+// class DbuTypeCollectionItem : public SfxPoolItem
+// class OptionalBoolItem : public SfxPoolItem
+// class OStringListItem : public SfxPoolItem
+// class MediaItem : public SfxPoolItem
+// class SvxBoxItem : public SfxPoolItem
+// class SvxBoxInfoItem : public SfxPoolItem
+// class SvxBrushItem : public SfxPoolItem
+// class SvxBulletItem : public SfxPoolItem
+// class SvxColorItem : public SfxPoolItem
+// class SvxFontHeightItem : public SfxPoolItem
+// class SvxFieldItem : public SfxPoolItem
+// class SvxFontListItem : public SfxPoolItem
+// class SvxFontItem : public SfxPoolItem
+// class SvxHyphenZoneItem : public SfxPoolItem
+// class SvxLineItem : public SfxPoolItem
+// class SvxLRSpaceItem : public SfxPoolItem
+// class SvxNumBulletItem : public SfxPoolItem
+// class SfxHyphenRegionItem: public SfxPoolItem
+// class SvxProtectItem : public SfxPoolItem
+// class SvxSizeItem : public SfxPoolItem
+// class SwFormatFrameSize: public SvxSizeItem
+// class SvxTabStopItem : public SfxPoolItem
+// class SvxTwoLinesItem : public SfxPoolItem
+// class SvxULSpaceItem : public SfxPoolItem
+// class SvXMLAttrContainerItem: public SfxPoolItem
+// class SfxLinkItem : public SfxPoolItem
+// class SfxEventNamesItem : public SfxPoolItem
+// class SfxFrameItem: public SfxPoolItem
+// class SfxUnoAnyItem : public SfxPoolItem
+// class SfxUnoFrameItem : public SfxPoolItem
+// class SfxMacroInfoItem: public SfxPoolItem
+// class SfxObjectItem: public SfxPoolItem
+// class SfxObjectShellItem: public SfxPoolItem
+// class SfxViewFrameItem: public SfxPoolItem
+// class SfxWatermarkItem: public SfxPoolItem
+// class SfxEnumItemInterface: public SfxPoolItem
+// class SvxAdjustItem : public SfxEnumItemInterface
+// class SvxEscapementItem : public SfxEnumItemInterface
+// class SvxLineSpacingItem : public SfxEnumItemInterface
+// class SvxShadowItem : public SfxEnumItemInterface
+// class SfxEnumItem : public SfxEnumItemInterface
+// class SvxCharReliefItem : public SfxEnumItem<FontRelief>
+// class SvxCaseMapItem : public SfxEnumItem<SvxCaseMap>
+// class SvxCrossedOutItem : public SfxEnumItem<FontStrikeout>
+// class SvxFormatBreakItem : public SfxEnumItem<SvxBreak>
+// class SvxFrameDirectionItem : public SfxEnumItem<SvxFrameDirection>
+// class SvxHorJustifyItem: public SfxEnumItem<SvxCellHorJustify>
+// class SvxVerJustifyItem: public SfxEnumItem<SvxCellVerJustify>
+// class SvxJustifyMethodItem: public SfxEnumItem<SvxCellJustifyMethod>
+// class SvxLanguageItem_Base: public SfxEnumItem<LanguageType>
+// class SvxLanguageItem : public SvxLanguageItem_Base
+// class SvxPostureItem : public SfxEnumItem<FontItalic>
+// class SvxTextLineItem : public SfxEnumItem<FontLineStyle>
+// class SvxUnderlineItem : public SvxTextLineItem
+// class SvxOverlineItem : public SvxTextLineItem
+// class SvxWeightItem : public SfxEnumItem<FontWeight>
+// class SvxOrientationItem: public SfxEnumItem<SvxCellOrientation>
+// class SvxChartRegressItem : public SfxEnumItem<SvxChartRegress>
+// class SvxChartTextOrderItem : public SfxEnumItem<SvxChartTextOrder>
+// class SvxChartKindErrorItem : public SfxEnumItem<SvxChartKindError>
+// class SvxChartIndicateItem : public SfxEnumItem<SvxChartIndicate>
+// class SvxRotateModeItem: public SfxEnumItem<SvxRotateMode>
+// class SdrGrafModeItem_Base: public SfxEnumItem<GraphicDrawMode>
+// class SdrGrafModeItem : public SdrGrafModeItem_Base
+// class SdrTextAniDirectionItem: public SfxEnumItem<SdrTextAniDirection>
+// class SdrTextVertAdjustItem: public SfxEnumItem<SdrTextVertAdjust>
+// class SdrTextHorzAdjustItem: public SfxEnumItem<SdrTextHorzAdjust>
+// class SdrTextAniKindItem: public SfxEnumItem<SdrTextAniKind>
+// class SdrTextFitToSizeTypeItem : public SfxEnumItem<css::drawing::TextFitToSizeType>
+// class SdrCaptionEscDirItem: public SfxEnumItem<SdrCaptionEscDir>
+// class SdrCaptionTypeItem: public SfxEnumItem<SdrCaptionType>
+// class SdrEdgeKindItem: public SfxEnumItem<SdrEdgeKind>
+// class SdrMeasureTextHPosItem: public SfxEnumItem<css::drawing::MeasureTextHorzPos>
+// class SdrMeasureTextVPosItem: public SfxEnumItem<css::drawing::MeasureTextVertPos>
+// class SdrMeasureUnitItem: public SfxEnumItem<FieldUnit>
+// class XFillStyleItem : public SfxEnumItem<css::drawing::FillStyle>
+// class XFillBmpPosItem : public SfxEnumItem<RectPoint>
+// class XFormTextAdjustItem : public SfxEnumItem<XFormTextAdjust>
+// class XFormTextShadowItem : public SfxEnumItem<XFormTextShadow>
+// class XLineStyleItem : public SfxEnumItem<css::drawing::LineStyle>
+// class XLineJointItem : public SfxEnumItem<css::drawing::LineJoint>
+// class XLineCapItem : public SfxEnumItem<css::drawing::LineCap>
+// class XFormTextStyleItem : public SfxEnumItem<XFormTextStyle>
+// class ScViewObjectModeItem: public SfxEnumItem<ScVObjMode>
+// class SdrCircKindItem: public SfxEnumItem<SdrCircKind>
+// class SdrMeasureKindItem: public SfxEnumItem<SdrMeasureKind>
+// class SwFormatFillOrder: public SfxEnumItem<SwFillOrder>
+// class SwFormatFootnoteEndAtTextEnd : public SfxEnumItem<SwFootnoteEndPosEnum>
+// class SwFormatFootnoteAtTextEnd : public SwFormatFootnoteEndAtTextEnd
+// class SwFormatEndAtTextEnd : public SwFormatFootnoteEndAtTextEnd
+// class SwFormatSurround: public SfxEnumItem<css::text::WrapTextMode>
+// class SwMirrorGrf : public SfxEnumItem<MirrorGraph>
+// class SwDrawModeGrf_Base: public SfxEnumItem<GraphicDrawMode>
+// class SwDrawModeGrf : public SwDrawModeGrf_Base
+//class CntByteItem: public SfxPoolItem
+// class SfxByteItem: public CntByteItem
+// class SvxOrphansItem: public SfxByteItem
+// class SvxPaperBinItem : public SfxByteItem
+// class SvxWidowsItem: public SfxByteItem
+// class SwTransparencyGrf : public SfxByteItem
+//class CntUInt16Item: public SfxPoolItem
+// class SfxUInt16Item: public CntUInt16Item
+// class SvxTextRotateItem : public SfxUInt16Item
+// class SvxCharRotateItem : public SvxTextRotateItem
+// class SvxCharScaleWidthItem : public SfxUInt16Item
+// class SvxEmphasisMarkItem : public SfxUInt16Item
+// class SvxParaVertAlignItem : public SfxUInt16Item
+// class SvxWritingModeItem : public SfxUInt16Item
+// class SvxZoomItem: public SfxUInt16Item
+// class SdrPercentItem : public SfxUInt16Item
+// class SdrGrafTransparenceItem : public SdrPercentItem
+// class SdrTextAniCountItem: public SfxUInt16Item
+// class SdrTextAniDelayItem: public SfxUInt16Item
+// class Svx3DNormalsKindItem : public SfxUInt16Item
+// class Svx3DTextureProjectionXItem : public SfxUInt16Item
+// class Svx3DTextureProjectionYItem : public SfxUInt16Item
+// class Svx3DTextureKindItem : public SfxUInt16Item
+// class Svx3DTextureModeItem : public SfxUInt16Item
+// class Svx3DPerspectiveItem : public SfxUInt16Item
+// class Svx3DShadeModeItem : public SfxUInt16Item
+// class SdrEdgeLineDeltaCountItem: public SfxUInt16Item
+// class SvxViewLayoutItem: public SfxUInt16Item
+// class XFillBmpPosOffsetXItem : public SfxUInt16Item
+// class XFillBmpPosOffsetYItem : public SfxUInt16Item
+// class XFillBmpTileOffsetXItem : public SfxUInt16Item
+// class XFillBmpTileOffsetYItem : public SfxUInt16Item
+// class XFillTransparenceItem: public SfxUInt16Item
+// class XFormTextShadowTranspItem: public SfxUInt16Item
+// class XGradientStepCountItem: public SfxUInt16Item
+// class XLineTransparenceItem: public SfxUInt16Item
+// class SvxZoomSliderItem: public SfxUInt16Item
+// class SdrLayerIdItem: public SfxUInt16Item
+// class SwRotationGrf : public SfxUInt16Item
+//class CntInt32Item: public SfxPoolItem
+// class SfxInt32Item: public CntInt32Item
+// class SfxMetricItem: public SfxInt32Item
+// class XFillBmpSizeXItem : public SfxMetricItem
+// class XFillBmpSizeYItem : public SfxMetricItem
+// class XFormTextDistanceItem : public SfxMetricItem
+// class XFormTextShadowXValItem : public SfxMetricItem
+// class XFormTextShadowYValItem : public SfxMetricItem
+// class XFormTextStartItem : public SfxMetricItem
+// class XLineEndWidthItem : public SfxMetricItem
+// class XLineStartWidthItem : public SfxMetricItem
+// class XLineWidthItem : public SfxMetricItem
+// class SdrAngleItem: public SfxInt32Item
+//
+// class SdrCaptionAngleItem: public SdrAngleItem
+// class SdrMeasureTextFixedAngleItem: public SdrAngleItem
+// class SdrMeasureTextAutoAngleViewItem: public SdrAngleItem
+// class SdrRotateAllItem: public SdrAngleItem
+// class SdrRotateOneItem: public SdrAngleItem
+// class SdrShearAngleItem: public SdrAngleItem
+// class SdrHorzShearAllItem: public SdrAngleItem
+// class SdrVertShearAllItem: public SdrAngleItem
+// class SdrHorzShearOneItem: public SdrAngleItem
+// class SdrVertShearOneItem: public SdrAngleItem
+// class SdrMetricItem: public SfxInt32Item
+// class SdrCaptionEscAbsItem: public SdrMetricItem
+// class SdrCaptionGapItem: public SdrMetricItem
+// class SdrCaptionLineLenItem: public SdrMetricItem
+// class SdrEdgeNode1HorzDistItem: public SdrMetricItem
+// class SdrEdgeNode1VertDistItem: public SdrMetricItem
+// class SdrEdgeNode2HorzDistItem: public SdrMetricItem
+// class SdrEdgeNode2VertDistItem: public SdrMetricItem
+// class SdrEdgeNode1GlueDistItem: public SdrMetricItem
+// class SdrEdgeNode2GlueDistItem: public SdrMetricItem
+// class SdrAllPositionXItem: public SdrMetricItem
+// class SdrAllPositionYItem: public SdrMetricItem
+// class SdrAllSizeWidthItem: public SdrMetricItem
+// class SdrAllSizeHeightItem: public SdrMetricItem
+// class SdrLogicSizeWidthItem: public SdrMetricItem
+// class SdrLogicSizeHeightItem: public SdrMetricItem
+// class SdrMeasureOverhangItem: public SdrMetricItem
+// class SdrMoveXItem: public SdrMetricItem
+// class SdrMoveYItem: public SdrMetricItem
+// class SdrOnePositionXItem: public SdrMetricItem
+// class SdrOnePositionYItem: public SdrMetricItem
+// class SdrOneSizeWidthItem: public SdrMetricItem
+// class SdrOneSizeHeightItem: public SdrMetricItem
+// class SdrTransformRef1XItem: public SdrMetricItem
+// class SdrTransformRef1YItem: public SdrMetricItem
+// class SdrTransformRef2XItem: public SdrMetricItem
+// class SdrTransformRef2YItem: public SdrMetricItem
+// class SdrCaptionEscRelItem: public SfxInt32Item
+//class CntUInt32Item: public SfxPoolItem
+// class SfxUInt32Item: public CntUInt32Item
+// class SvxRsidItem : public SfxUInt32Item
+// class SdrGrafGamma100Item : public SfxUInt32Item
+// class SwTableBoxNumFormat : public SfxUInt32Item
+//class CntUnencodedStringItem: public SfxPoolItem
+// class SfxStringItem: public CntUnencodedStringItem
+// class SvxPageModelItem : public SfxStringItem
+// class SfxDocumentInfoItem : public SfxStringItem
+// class SvxPostItAuthorItem: public SfxStringItem
+// class SvxPostItDateItem: public SfxStringItem
+// class SvxPostItTextItem: public SfxStringItem
+// class SvxPostItIdItem: public SfxStringItem
+// class SdrMeasureFormatStringItem: public SfxStringItem
+// class NameOrIndex : public SfxStringItem
+// class XFillBitmapItem : public NameOrIndex
+// class XColorItem : public NameOrIndex
+// class XFillColorItem : public XColorItem
+// class XFormTextShadowColorItem : public XColorItem
+// class XLineColorItem : public XColorItem
+// class XSecondaryFillColorItem : public XColorItem
+// class XFillGradientItem : public NameOrIndex
+// class XFillFloatTransparenceItem : public XFillGradientItem
+// class XFillHatchItem : public NameOrIndex
+// class XLineDashItem : public NameOrIndex
+// class XLineEndItem : public NameOrIndex
+// class XLineStartItem : public NameOrIndex
+// class SfxScriptOrganizerItem : public SfxStringItem
+// class SdrLayerNameItem: public SfxStringItem
+// class SwNumRuleItem : public SfxStringItem
+//class SfxBoolItem : public SfxPoolItem
+// class SvxAutoKernItem : public SfxBoolItem
+// class SvxBlinkItem : public SfxBoolItem
+// class SvxCharHiddenItem : public SfxBoolItem
+// class SvxContourItem : public SfxBoolItem
+// class SvxForbiddenRuleItem : public SfxBoolItem
+// class SvxHangingPunctuationItem : public SfxBoolItem
+// class SvxFormatKeepItem : public SfxBoolItem
+// class SvxNoHyphenItem : public SfxBoolItem
+// class SvxOpaqueItem : public SfxBoolItem
+// class SvxParaGridItem : public SfxBoolItem
+// class SvxPrintItem : public SfxBoolItem
+// class SvxScriptSpaceItem : public SfxBoolItem
+// class SvxShadowedItem : public SfxBoolItem
+// class SvxFormatSplitItem : public SfxBoolItem
+// class SvxWordLineModeItem : public SfxBoolItem
+// class SdrOnOffItem: public SfxBoolItem
+// class SdrGrafInvertItem : public SdrOnOffItem
+// class SdrTextFixedCellHeightItem : public SfxBoolItem
+// class SdrYesNoItem: public SfxBoolItem
+// class SdrTextAniStartInsideItem: public SdrYesNoItem
+// class SdrTextAniStopInsideItem: public SdrYesNoItem
+// class SdrCaptionEscIsRelItem: public SdrYesNoItem
+// class SdrCaptionFitLineLenItem: public SdrYesNoItem
+// class SdrMeasureBelowRefEdgeItem: public SdrYesNoItem
+// class SdrMeasureTextIsFixedAngleItem: public SdrYesNoItem
+// class SdrMeasureTextRota90Item: public SdrYesNoItem
+// class SdrMeasureTextUpsideDownItem: public SdrYesNoItem
+// class SdrMeasureTextAutoAngleItem: public SdrYesNoItem
+// class SdrObjPrintableItem: public SdrYesNoItem
+// class SdrObjVisibleItem: public SdrYesNoItem
+// class Svx3DReducedLineGeometryItem : public SfxBoolItem
+// class Svx3DSmoothNormalsItem : public SfxBoolItem
+// class Svx3DSmoothLidsItem : public SfxBoolItem
+// class Svx3DCharacterModeItem : public SfxBoolItem
+// class Svx3DCloseFrontItem : public SfxBoolItem
+// class Svx3DCloseBackItem : public SfxBoolItem
+// class XFillBackgroundItem : public SfxBoolItem
+// class XFillUseSlideBackgroundItem : public SfxBoolItem
+// class XFillBmpSizeLogItem : public SfxBoolItem
+// class XFillBmpTileItem : public SfxBoolItem
+// class XFillBmpStretchItem : public SfxBoolItem
+// class XFormTextMirrorItem : public SfxBoolItem
+// class XFormTextOutlineItem : public SfxBoolItem
+// class XLineEndCenterItem : public SfxBoolItem
+// class XLineStartCenterItem : public SfxBoolItem
+// class XFormTextHideFormItem : public SfxBoolItem
+// class SwFormatNoBalancedColumns : public SfxBoolItem
+// class SwFormatEditInReadonly : public SfxBoolItem
+// class SwFormatFollowTextFlow : public SfxBoolItem
+// class SwFormatLayoutSplit : public SfxBoolItem
+// class SwFormatRowSplit : public SfxBoolItem
+// class SwInvertGrf: public SfxBoolItem
+// class SwHeaderAndFooterEatSpacingItem : public SfxBoolItem
+// class SwRegisterItem : public SfxBoolItem
+// class SwParaConnectBorderItem : public SfxBoolItem
+// class SfxFlagItem: public SfxPoolItem
+// class SfxTemplateItem: public SfxFlagItem
+// class SfxGlobalNameItem: public SfxPoolItem
+// class SfxGrabBagItem : public SfxPoolItem
+// class SfxIntegerListItem : public SfxPoolItem
+// class SfxInt64Item : public SfxPoolItem
+// class SfxInt16Item: public SfxPoolItem
+// class SvxKerningItem : public SfxInt16Item
+// class SfxImageItem : public SfxInt16Item
+// class SdrSignedPercentItem : public SfxInt16Item
+// class SdrGrafRedItem : public SdrSignedPercentItem
+// class SdrGrafGreenItem : public SdrSignedPercentItem
+// class SdrGrafBlueItem : public SdrSignedPercentItem
+// class SdrGrafLuminanceItem : public SdrSignedPercentItem
+// class SdrGrafContrastItem : public SdrSignedPercentItem
+// class SdrTextAniAmountItem: public SfxInt16Item
+// class SdrMeasureDecimalPlacesItem: public SfxInt16Item
+// class ScMergeFlagAttr: public SfxInt16Item
+// class SwLuminanceGrf : public SfxInt16Item
+// class SwContrastGrf : public SfxInt16Item
+// class SwChannelGrf : public SfxInt16Item
+// class SfxLockBytesItem : public SfxPoolItem
+// class SvxMacroItem: public SfxPoolItem
+// class SfxVoidItem final: public SfxPoolItem
+// class SfxSetItem: public SfxPoolItem
+// class SvxScriptSetItem : public SfxSetItem
+// class SfxTabDialogItem: public SfxSetItem
+// class SvxSetItem: public SfxSetItem
+// class XFillAttrSetItem : public SfxSetItem
+// class XLineAttrSetItem : public SfxSetItem
+// class ScPatternAttr: public SfxSetItem
+// class SfxPointItem: public SfxPoolItem
+// class SfxRectangleItem: public SfxPoolItem
+// class SfxRangeItem : public SfxPoolItem
+// class SfxStringListItem : public SfxPoolItem
+// class SvxSearchItem : public SfxPoolItem
+// class SfxVisibilityItem: public SfxPoolItem
+// class AffineMatrixItem : public SfxPoolItem
+// class SvxMarginItem: public SfxPoolItem
+// class SvxDoubleItem : public SfxPoolItem
+// class SvxClipboardFormatItem : public SfxPoolItem
+// class SvxColorListItem: public SfxPoolItem
+// class SvxGradientListItem : public SfxPoolItem
+// class SvxHatchListItem : public SfxPoolItem
+// class SvxBitmapListItem : public SfxPoolItem
+// class SvxPatternListItem : public SfxPoolItem
+// class SvxDashListItem : public SfxPoolItem
+// class SvxLineEndListItem : public SfxPoolItem
+// class SvxB3DVectorItem : public SfxPoolItem
+// class SvxGalleryItem : public SfxPoolItem
+// class SvxGrfCrop : public SfxPoolItem
+// class SdrGrafCropItem : public SvxGrfCrop
+// class SwCropGrf : public SvxGrfCrop
+// class SvxHyperlinkItem : public SfxPoolItem
+// class SvxNumberInfoItem : public SfxPoolItem
+// class OfaPtrItem : public SfxPoolItem
+// class OfaXColorListItem : public SfxPoolItem
+// class SvxGridItem : public SvxOptionsGrid, public SfxPoolItem
+// class SdOptionsGridItem : public SvxGridItem
+// class SvxPageItem: public SfxPoolItem
+// class SvxLongLRSpaceItem : public SfxPoolItem
+// class SvxLongULSpaceItem : public SfxPoolItem
+// class SvxPagePosSizeItem : public SfxPoolItem
+// class SvxColumnItem : public SfxPoolItem
+// class SvxObjectItem : public SfxPoolItem
+// class SdrCustomShapeGeometryItem : public SfxPoolItem
+// class SvxSmartTagItem : public SfxPoolItem
+// class SvxGraphicItem: public SfxPoolItem
+// class SdrFractionItem: public SfxPoolItem
+// class SdrScaleItem: public SdrFractionItem
+// class SdrMeasureScaleItem: public SdrScaleItem
+// class SdrResizeXAllItem: public SdrFractionItem
+// class SdrResizeYAllItem: public SdrFractionItem
+// class SdrResizeXOneItem: public SdrFractionItem
+// class SdrResizeYOneItem: public SdrFractionItem
+// class ScMergeAttr: public SfxPoolItem
+// class ScProtectionAttr: public SfxPoolItem
+// class ScPageHFItem : public SfxPoolItem
+// class ScPageScaleToItem : public SfxPoolItem
+// class ScCondFormatItem : public SfxPoolItem
+// class ScTpDefaultsItem : public SfxPoolItem
+// class ScTpCalcItem : public SfxPoolItem
+// class ScTpFormulaItem : public SfxPoolItem
+// class ScTpPrintItem : public SfxPoolItem
+// class ScTpViewItem : public SfxPoolItem
+// class ScCondFormatDlgItem : public SfxPoolItem
+// class ScInputStatusItem : public SfxPoolItem
+// class ScSortItem : public SfxPoolItem
+// class ScQueryItem : public SfxPoolItem
+// class ScSubTotalItem : public SfxPoolItem
+// class cUserListItem : public SfxPoolItem
+// class ScConsolidateItem : public SfxPoolItem
+// class ScPivotItem : public SfxPoolItem
+// class ScSolveItem : public SfxPoolItem
+// class ScTabOpItem : public SfxPoolItem
+// class SdOptionsLayoutItem : public SfxPoolItem
+// class SdOptionsMiscItem : public SfxPoolItem
+// class SdOptionsSnapItem : public SfxPoolItem
+// class SdOptionsPrintItem : public SfxPoolItem
+// class SwCondCollItem : public SfxPoolItem
+// class SwTableBoxFormula : public SfxPoolItem, public SwTableFormula
+// class SwTableBoxValue : public SfxPoolItem
+// class SwFormatCharFormat: public SfxPoolItem, public SwClient
+// class SwFormatAnchor: public SfxPoolItem
+// class SwFormatAutoFormat: public SfxPoolItem
+// class SwFormatCol : public SfxPoolItem
+// class SwFormatChain: public SfxPoolItem
+// class SwFormatContent: public SfxPoolItem
+// class SwFormatFlyCnt : public SfxPoolItem
+// class SwFormatField : public SfxPoolItem
+// class SwFormatFootnote : public SfxPoolItem
+// class SwFormatHeader: public SfxPoolItem, public SwClient
+// class SwFormatFooter: public SfxPoolItem, public SwClient
+// class SwFormatINetFormat : public SfxPoolItem
+// class SwFormatLineNumber: public SfxPoolItem
+// class SwFormatMeta : public SfxPoolItem
+// class SwFormatVertOrient: public SfxPoolItem
+// class SwFormatHoriOrient: public SfxPoolItem
+// class SwFormatPageDesc : public SfxPoolItem, public SwClient
+// class SwFormatRefMark : public SfxPoolItem
+// class SwFormatRuby : public SfxPoolItem
+// class SwFormatURL: public SfxPoolItem
+// class SwFormatWrapInfluenceOnObjPos: public SfxPoolItem
+// class SwGammaGrf : public SfxPoolItem
+// class SwMsgPoolItem : public SfxPoolItem
+// class SwPtrMsgPoolItem : public SwMsgPoolItem
+// class SwFormatChg: public SwMsgPoolItem
+// class SwUpdateAttr : public SwMsgPoolItem
+// class SwTableFormulaUpdate : public SwMsgPoolItem
+// class SwAutoFormatGetDocNode: public SwMsgPoolItem
+// class SwAttrSetChg: public SwMsgPoolItem
+// class SwFindNearestNode : public SwMsgPoolItem
+// class SwStringMsgPoolItem : public SwMsgPoolItem
+// class SwFormatDrop: public SfxPoolItem, public SwClient
+// class SwTextGridItem : public SfxPoolItem
+// class SwTOXMark : public SfxPoolItem
+// class SwFltAnchor : public SfxPoolItem
+// class SwFltRedline : public SfxPoolItem
+// class SwFltBookmark : public SfxPoolItem
+// class SwFltRDFMark : public SfxPoolItem
+// class SwFltTOX : public SfxPoolItem
+// class SwDocDisplayItem : public SfxPoolItem
+// class SwElemItem : public SfxPoolItem
+// class SwAddPrinterItem : public SfxPoolItem, public SwPrintData
+// class SwShadowCursorItem : public SfxPoolItem
+// class SwTestItem : public SfxPoolItem
+// class SwEnvItem : public SfxPoolItem
+// class SwLabItem : public SfxPoolItem
+// class SwWrtShellItem: public SfxPoolItem
+// class SwPageFootnoteInfoItem : public SfxPoolItem
+// class SwPtrItem : public SfxPoolItem
+// class SwUINumRuleItem : public SfxPoolItem
+// class SwPaMItem : public SfxPoolItem
+//////////////////////////////////////////////////////////////////////////////
+
+#ifdef DBG_UTIL
+static size_t nAllocatedSfxPoolItemCount(0);
+static size_t nUsedSfxPoolItemCount(0);
+size_t getAllocatedSfxPoolItemCount() { return nAllocatedSfxPoolItemCount; }
+size_t getUsedSfxPoolItemCount() { return nUsedSfxPoolItemCount; }
+static std::unordered_set<const SfxPoolItem*>& incarnatedSfxPoolItems()
+{
+ // Deferred instantiation to avoid initialization-order-fiasco:
+ static std::unordered_set<const SfxPoolItem*> items;
+ return items;
+}
+void listAllocatedSfxPoolItems()
+{
+ SAL_INFO("svl.items", "ITEM: List of still allocated SfxPoolItems:");
+ for (const auto& rCandidate : incarnatedSfxPoolItems())
+ {
+ SAL_INFO("svl.items", " ITEM: WhichID: " << rCandidate->Which() << " SerialNumber: "
+ << rCandidate->getSerialNumber()
+ << " Class: " << typeid(*rCandidate).name());
+ }
+}
+#endif
+
+SfxPoolItem::SfxPoolItem(sal_uInt16 const nWhich)
+ : m_nRefCount(0)
+ , m_nWhich(nWhich)
+#ifdef DBG_UTIL
+ , m_nSerialNumber(nUsedSfxPoolItemCount)
+#endif
+ , m_bIsVoidItem(false)
+ , m_bStaticDefault(false)
+ , m_bPoolDefault(false)
+ , m_bRegisteredAtPool(false)
+ , m_bExceptionalSCItem(false)
+ , m_bIsSetItem(false)
+#ifdef DBG_UTIL
+ , m_bDeleted(false)
+#endif
+{
+#ifdef DBG_UTIL
+ nAllocatedSfxPoolItemCount++;
+ nUsedSfxPoolItemCount++;
+ incarnatedSfxPoolItems().insert(this);
+#endif
+ assert(nWhich <= SHRT_MAX);
+}
+
+SfxPoolItem::~SfxPoolItem()
+{
+#ifdef DBG_UTIL
+ nAllocatedSfxPoolItemCount--;
+ incarnatedSfxPoolItems().erase(this);
+ m_bDeleted = true;
+#endif
+ assert((m_nRefCount == 0 || m_nRefCount > SFX_ITEMS_MAXREF) && "destroying item in use");
+}
+
+bool SfxPoolItem::operator==(const SfxPoolItem& rCmp) const
+{
+ SAL_WARN_IF(typeid(rCmp) != typeid(*this), "svl",
+ "comparing different pool item subclasses " << typeid(rCmp).name() << " && "
+ << typeid(*this).name());
+ assert(typeid(rCmp) == typeid(*this) && "comparing different pool item subclasses");
+ (void)rCmp;
+ return true;
+}
+
+/**
+ * This virtual method allows to get a textual representation of the value
+ * for the SfxPoolItem subclasses. It should be overridden by all UI-relevant
+ * SfxPoolItem subclasses.
+ *
+ * Because the unit of measure of the value in the SfxItemPool is only
+ * queryable via @see SfxItemPool::GetMetric(sal_uInt16) const (and not
+ * via the SfxPoolItem instance or subclass, the own unit of measure is
+ * passed to 'eCoreMetric'.
+ *
+ * The corresponding unit of measure is passed as 'ePresentationMetric'.
+ *
+ *
+ * @return SfxItemPresentation SfxItemPresentation::Nameless
+ * A textual representation (if applicable
+ * with a unit of measure) could be created,
+ * but it doesn't contain any semantic meaning
+ *
+ * SfxItemPresentation::Complete
+ * A complete textual representation could be
+ * created with semantic meaning (if applicable
+ * with unit of measure)
+ *
+ * Example:
+ *
+ * pSvxFontItem->GetPresentation( SFX_PRESENTATION_NAMELESS, ... )
+ * "12pt" with return SfxItemPresentation::Nameless
+ *
+ * pSvxColorItem->GetPresentation( SFX_PRESENTATION_COMPLETE, ... )
+ * "red" with return SfxItemPresentation::Nameless
+ * Because the SvxColorItem does not know which color it represents
+ * it cannot provide a name, which is communicated by the return value
+ *
+ * pSvxBorderItem->GetPresentation( SFX_PRESENTATION_COMPLETE, ... )
+ * "1cm top border, 2cm left border, 0.2cm bottom border, ..."
+ */
+bool SfxPoolItem::GetPresentation(
+ SfxItemPresentation /*ePresentation*/, // IN: how we should format
+ MapUnit /*eCoreMetric*/, // IN: current metric of the SfxPoolItems
+ MapUnit /*ePresentationMetric*/, // IN: target metric of the presentation
+ OUString& /*rText*/, // OUT: textual representation
+ const IntlWrapper&) const
+{
+ return false;
+}
+
+void SfxPoolItem::dumpAsXml(xmlTextWriterPtr pWriter) const
+{
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SfxPoolItem"));
+ (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this);
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("whichId"),
+ BAD_CAST(OString::number(Which()).getStr()));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("typeName"),
+ BAD_CAST(typeid(*this).name()));
+ OUString rText;
+ IntlWrapper aIntlWrapper(SvtSysLocale().GetUILanguageTag());
+ if (GetPresentation(SfxItemPresentation::Complete, MapUnit::Map100thMM, MapUnit::Map100thMM,
+ rText, aIntlWrapper))
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("presentation"),
+ BAD_CAST(rText.toUtf8().getStr()));
+ (void)xmlTextWriterEndElement(pWriter);
+}
+
+boost::property_tree::ptree SfxPoolItem::dumpAsJSON() const
+{
+ boost::property_tree::ptree aTree;
+ return aTree;
+}
+
+std::unique_ptr<SfxPoolItem> SfxPoolItem::CloneSetWhich(sal_uInt16 nNewWhich) const
+{
+ std::unique_ptr<SfxPoolItem> pItem(Clone());
+ pItem->SetWhich(nNewWhich);
+ return pItem;
+}
+
+void SfxPoolItem::ScaleMetrics(tools::Long /*lMult*/, tools::Long /*lDiv*/) {}
+
+bool SfxPoolItem::HasMetrics() const { return false; }
+
+bool SfxPoolItem::QueryValue(css::uno::Any&, sal_uInt8) const
+{
+ OSL_FAIL("There is no implementation for QueryValue for this item!");
+ return false;
+}
+
+bool SfxPoolItem::PutValue(const css::uno::Any&, sal_uInt8)
+{
+ OSL_FAIL("There is no implementation for PutValue for this item!");
+ return false;
+}
+
+bool areSfxPoolItemPtrsEqual(const SfxPoolItem* pItem1, const SfxPoolItem* pItem2)
+{
+#ifdef DBG_UTIL
+ if (nullptr != pItem1 && nullptr != pItem2 && pItem1->Which() == pItem2->Which()
+ && static_cast<const void*>(pItem1) != static_cast<const void*>(pItem2)
+ && typeid(*pItem1) == typeid(*pItem2) && *pItem1 == *pItem2)
+ {
+ SAL_INFO("svl.items", "ITEM: PtrCompare != ContentCompare (!)");
+ }
+#endif
+
+ // cast to void* to not trigger [loplugin:itemcompare]
+ return (static_cast<const void*>(pItem1) == static_cast<const void*>(pItem2));
+}
+
+bool SfxPoolItem::areSame(const SfxPoolItem* pItem1, const SfxPoolItem* pItem2)
+{
+ if (pItem1 == pItem2)
+ // pointer compare, this handles already
+ // nullptr, INVALID_POOL_ITEM, SfxVoidItem
+ // and if any Item is indeed handed over twice
+ return true;
+
+ if (nullptr == pItem1 || nullptr == pItem2)
+ // one ptr is nullptr, not both, that would
+ // have triggered above
+ return false;
+
+ if (pItem1->Which() != pItem2->Which())
+ // WhichIDs differ (fast)
+ return false;
+
+ if (typeid(*pItem1) != typeid(*pItem2))
+ // types differ (fast)
+ // NOTE: we can now use typeid since we do not have (-1)
+ // anymore for Invalid state -> safe
+ return false;
+
+ // return content compare using operator== at last
+ return *pItem1 == *pItem2;
+}
+
+bool SfxPoolItem::areSame(const SfxPoolItem& rItem1, const SfxPoolItem& rItem2)
+{
+ if (&rItem1 == &rItem2)
+ // still use pointer compare, this handles already
+ // nullptr, INVALID_POOL_ITEM, SfxVoidItem
+ // and if any Item is indeed handed over twice
+ return true;
+
+ if (rItem1.Which() != rItem2.Which())
+ // WhichIDs differ (fast)
+ return false;
+
+ if (typeid(rItem1) != typeid(rItem2))
+ // types differ (fast)
+ // NOTE: we can now use typeid since we do not have (-1)
+ // anymore for Invalid state -> safe
+ return false;
+
+ // return content compare using operator== at last
+ return rItem1 == rItem2;
+}
+
+namespace
+{
+class InvalidItem final : public SfxPoolItem
+{
+ virtual bool operator==(const SfxPoolItem&) const override { return true; }
+ virtual SfxPoolItem* Clone(SfxItemPool*) const override { return nullptr; }
+};
+InvalidItem aInvalidItem;
+}
+
+SfxPoolItem const* const INVALID_POOL_ITEM = &aInvalidItem;
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/items/ptitem.cxx b/svl/source/items/ptitem.cxx
new file mode 100644
index 0000000000..361cb4f4fc
--- /dev/null
+++ b/svl/source/items/ptitem.cxx
@@ -0,0 +1,136 @@
+/* -*- 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 <svl/ptitem.hxx>
+#include <com/sun/star/uno/Any.hxx>
+#include <com/sun/star/awt/Point.hpp>
+#include <osl/diagnose.h>
+#include <tools/mapunit.hxx>
+#include <tools/UnitConversion.hxx>
+
+#include <svl/poolitem.hxx>
+#include <svl/memberid.h>
+
+using namespace ::com::sun::star;
+
+
+SfxPoolItem* SfxPointItem::CreateDefault() { return new SfxPointItem; }
+
+
+SfxPointItem::SfxPointItem()
+{
+}
+
+
+SfxPointItem::SfxPointItem( sal_uInt16 nW, const Point& rVal ) :
+ SfxPoolItem( nW ),
+ aVal( rVal )
+{
+}
+
+
+bool SfxPointItem::GetPresentation
+(
+ SfxItemPresentation /*ePresentation*/,
+ MapUnit /*eCoreMetric*/,
+ MapUnit /*ePresentationMetric*/,
+ OUString& rText,
+ const IntlWrapper&
+) const
+{
+ rText = OUString::number(aVal.X()) + ", " + OUString::number(aVal.Y()) + ", ";
+ return true;
+}
+
+
+bool SfxPointItem::operator==( const SfxPoolItem& rItem ) const
+{
+ assert(SfxPoolItem::operator==(rItem));
+ return static_cast<const SfxPointItem&>(rItem).aVal == aVal;
+}
+
+SfxPointItem* SfxPointItem::Clone(SfxItemPool *) const
+{
+ return new SfxPointItem( *this );
+}
+
+bool SfxPointItem::QueryValue( uno::Any& rVal,
+ sal_uInt8 nMemberId ) const
+{
+ bool bConvert = 0!=(nMemberId&CONVERT_TWIPS);
+ awt::Point aTmp(aVal.X(), aVal.Y());
+ if( bConvert )
+ {
+ aTmp.X = convertTwipToMm100(aTmp.X);
+ aTmp.Y = convertTwipToMm100(aTmp.Y);
+ }
+ nMemberId &= ~CONVERT_TWIPS;
+ switch ( nMemberId )
+ {
+ case 0: rVal <<= aTmp; break;
+ case MID_X: rVal <<= aTmp.X; break;
+ case MID_Y: rVal <<= aTmp.Y; break;
+ default: OSL_FAIL("Wrong MemberId!"); return true;
+ }
+
+ return true;
+}
+
+
+bool SfxPointItem::PutValue( const uno::Any& rVal,
+ sal_uInt8 nMemberId )
+{
+ bool bConvert = 0!=(nMemberId&CONVERT_TWIPS);
+ nMemberId &= ~CONVERT_TWIPS;
+ bool bRet = false;
+ awt::Point aValue;
+ sal_Int32 nVal = 0;
+ if ( !nMemberId )
+ {
+ bRet = ( rVal >>= aValue );
+ if( bConvert )
+ {
+ aValue.X = o3tl::toTwips(aValue.X, o3tl::Length::mm100);
+ aValue.Y = o3tl::toTwips(aValue.Y, o3tl::Length::mm100);
+ }
+ }
+ else
+ {
+ bRet = ( rVal >>= nVal );
+ if( bConvert )
+ nVal = o3tl::toTwips(nVal, o3tl::Length::mm100);
+ }
+
+ if ( bRet )
+ {
+ switch ( nMemberId )
+ {
+ case 0: aVal.setX( aValue.X ); aVal.setY( aValue.Y ); break;
+ case MID_X: aVal.setX( nVal ); break;
+ case MID_Y: aVal.setY( nVal ); break;
+ default: OSL_FAIL("Wrong MemberId!"); return false;
+ }
+ }
+
+ return bRet;
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/items/rectitem.cxx b/svl/source/items/rectitem.cxx
new file mode 100644
index 0000000000..f6a5db309d
--- /dev/null
+++ b/svl/source/items/rectitem.cxx
@@ -0,0 +1,132 @@
+/* -*- 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 <svl/rectitem.hxx>
+#include <com/sun/star/uno/Any.hxx>
+#include <com/sun/star/awt/Rectangle.hpp>
+#include <osl/diagnose.h>
+
+#include <svl/poolitem.hxx>
+#include <svl/memberid.h>
+
+
+SfxPoolItem* SfxRectangleItem::CreateDefault() { return new SfxRectangleItem; }
+
+
+SfxRectangleItem::SfxRectangleItem()
+{
+}
+
+
+SfxRectangleItem::SfxRectangleItem( sal_uInt16 nW, const tools::Rectangle& rVal ) :
+ SfxPoolItem( nW ),
+ maVal( rVal )
+{
+}
+
+
+bool SfxRectangleItem::GetPresentation
+(
+ SfxItemPresentation /*ePresentation*/,
+ MapUnit /*eCoreMetric*/,
+ MapUnit /*ePresentationMetric*/,
+ OUString& rText,
+ const IntlWrapper&
+) const
+{
+ rText = OUString::number(maVal.Top()) + ", " +
+ OUString::number(maVal.Left()) + ", " +
+ OUString::number(maVal.Bottom()) + ", " +
+ OUString::number(maVal.Right());
+ return true;
+}
+
+
+bool SfxRectangleItem::operator==( const SfxPoolItem& rItem ) const
+{
+ assert(SfxPoolItem::operator==(rItem));
+ return static_cast<const SfxRectangleItem&>(rItem).maVal == maVal;
+}
+
+SfxRectangleItem* SfxRectangleItem::Clone(SfxItemPool *) const
+{
+ return new SfxRectangleItem( *this );
+}
+
+bool SfxRectangleItem::QueryValue( css::uno::Any& rVal,
+ sal_uInt8 nMemberId) const
+{
+ nMemberId &= ~CONVERT_TWIPS;
+ switch ( nMemberId )
+ {
+ case 0:
+ {
+ rVal <<= css::awt::Rectangle( maVal.Left(),
+ maVal.Top(),
+ maVal.getOpenWidth(),
+ maVal.getOpenHeight() );
+ break;
+ }
+ case MID_RECT_LEFT: rVal <<= maVal.Left(); break;
+ case MID_RECT_RIGHT: rVal <<= maVal.Top(); break;
+ case MID_WIDTH: rVal <<= maVal.getOpenWidth(); break;
+ case MID_HEIGHT: rVal <<= maVal.getOpenHeight(); break;
+ default: OSL_FAIL("Wrong MemberID!"); return false;
+ }
+
+ return true;
+}
+
+
+bool SfxRectangleItem::PutValue( const css::uno::Any& rVal,
+ sal_uInt8 nMemberId )
+{
+ bool bRet = false;
+ nMemberId &= ~CONVERT_TWIPS;
+ css::awt::Rectangle aValue;
+ sal_Int32 nVal = 0;
+ if ( !nMemberId )
+ bRet = (rVal >>= aValue);
+ else
+ bRet = (rVal >>= nVal);
+
+ if ( bRet )
+ {
+ switch ( nMemberId )
+ {
+ case 0:
+ maVal.SetLeft( aValue.X );
+ maVal.SetTop( aValue.Y );
+ maVal.setWidth( aValue.Width );
+ maVal.setHeight( aValue.Height );
+ break;
+ case MID_RECT_LEFT: maVal.SetPosX( nVal ); break;
+ case MID_RECT_RIGHT: maVal.SetPosY( nVal ); break;
+ case MID_WIDTH: maVal.setWidth( nVal ); break;
+ case MID_HEIGHT: maVal.setHeight( nVal ); break;
+ default: OSL_FAIL("Wrong MemberID!"); return false;
+ }
+ }
+
+ return bRet;
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/items/rngitem.cxx b/svl/source/items/rngitem.cxx
new file mode 100644
index 0000000000..89f7e642ed
--- /dev/null
+++ b/svl/source/items/rngitem.cxx
@@ -0,0 +1,59 @@
+/* -*- 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 <svl/rngitem.hxx>
+
+
+SfxRangeItem::SfxRangeItem( sal_uInt16 which, sal_uInt16 from, sal_uInt16 to ):
+ SfxPoolItem( which ),
+ nFrom( from ),
+ nTo( to )
+{
+}
+
+
+bool SfxRangeItem::GetPresentation
+(
+ SfxItemPresentation /*ePresentation*/,
+ MapUnit /*eCoreMetric*/,
+ MapUnit /*ePresentationMetric*/,
+ OUString& rText,
+ const IntlWrapper&
+) const
+{
+ rText = OUString::number(nFrom) + ":" + OUString::number(nTo);
+ return true;
+}
+
+
+bool SfxRangeItem::operator==( const SfxPoolItem& rItem ) const
+{
+ assert(SfxPoolItem::operator==(rItem));
+ const SfxRangeItem& rT = static_cast<const SfxRangeItem&>(rItem);
+ return nFrom==rT.nFrom && nTo==rT.nTo;
+}
+
+SfxRangeItem* SfxRangeItem::Clone(SfxItemPool *) const
+{
+ return new SfxRangeItem( Which(), nFrom, nTo );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/items/sitem.cxx b/svl/source/items/sitem.cxx
new file mode 100644
index 0000000000..037097f7bc
--- /dev/null
+++ b/svl/source/items/sitem.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/.
+ *
+ * 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/ustring.hxx>
+#include <svl/itemset.hxx>
+#include <svl/setitem.hxx>
+#include <svl/poolitem.hxx>
+
+SfxSetItem::SfxSetItem( sal_uInt16 which, const SfxItemSet &rSet) :
+ SfxPoolItem(which),
+ maSet(rSet)
+{
+ assert(!dynamic_cast<const SfxAllItemSet*>(&rSet) && "cannot handle SfxAllItemSet here");
+ setIsSetItem();
+}
+
+
+SfxSetItem::SfxSetItem( sal_uInt16 which, SfxItemSet &&pS) :
+ SfxPoolItem(which),
+ maSet(pS)
+{
+ assert(!dynamic_cast<SfxAllItemSet*>(&pS) && "cannot handle SfxAllItemSet here");
+ setIsSetItem();
+}
+
+
+SfxSetItem::SfxSetItem( const SfxSetItem& rCopy, SfxItemPool *pPool ) :
+ SfxPoolItem(rCopy),
+ maSet(rCopy.maSet.CloneAsValue(true, pPool))
+{
+ assert(!dynamic_cast<const SfxAllItemSet*>(&rCopy.maSet) && "cannot handle SfxAllItemSet here");
+ setIsSetItem();
+}
+
+
+bool SfxSetItem::operator==( const SfxPoolItem& rCmp) const
+{
+ assert(SfxPoolItem::operator==(rCmp));
+ return maSet == static_cast<const SfxSetItem &>(rCmp).maSet;
+}
+
+
+bool SfxSetItem::GetPresentation
+(
+ SfxItemPresentation /*ePresentation*/,
+ MapUnit /*eCoreMetric*/,
+ MapUnit /*ePresentationMetric*/,
+ OUString& /*rText*/,
+ const IntlWrapper&
+) const
+{
+ return false;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/items/slstitm.cxx b/svl/source/items/slstitm.cxx
new file mode 100644
index 0000000000..02784446ca
--- /dev/null
+++ b/svl/source/items/slstitm.cxx
@@ -0,0 +1,166 @@
+/* -*- 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 <svl/slstitm.hxx>
+#include <svl/poolitem.hxx>
+#include <com/sun/star/uno/Any.hxx>
+#include <com/sun/star/uno/Sequence.hxx>
+#include <comphelper/sequence.hxx>
+#include <osl/diagnose.h>
+#include <rtl/ustrbuf.hxx>
+#include <tools/lineend.hxx>
+
+SfxPoolItem* SfxStringListItem::CreateDefault() { return new SfxStringListItem; }
+
+SfxStringListItem::SfxStringListItem()
+{
+}
+
+
+SfxStringListItem::SfxStringListItem( sal_uInt16 which, const std::vector<OUString>* pList ) :
+ SfxPoolItem( which )
+{
+ // FIXME: Putting an empty list does not work
+ // Therefore the query after the count is commented out
+ if( pList /*!!! && pList->Count() */ )
+ {
+ mpList = std::make_shared<std::vector<OUString>>(*pList);
+ }
+}
+
+
+SfxStringListItem::~SfxStringListItem()
+{
+}
+
+
+std::vector<OUString>& SfxStringListItem::GetList()
+{
+ if( !mpList )
+ mpList = std::make_shared<std::vector<OUString>>();
+ return *mpList;
+}
+
+const std::vector<OUString>& SfxStringListItem::GetList () const
+{
+ return const_cast< SfxStringListItem * >(this)->GetList();
+}
+
+
+bool SfxStringListItem::operator==( const SfxPoolItem& rItem ) const
+{
+ assert(SfxPoolItem::operator==(rItem));
+
+ const SfxStringListItem& rSSLItem = static_cast<const SfxStringListItem&>(rItem);
+
+ return mpList == rSSLItem.mpList;
+}
+
+
+bool SfxStringListItem::GetPresentation
+(
+ SfxItemPresentation /*ePresentation*/,
+ MapUnit /*eCoreMetric*/,
+ MapUnit /*ePresentationMetric*/,
+ OUString& rText,
+ const IntlWrapper&
+) const
+{
+ rText = "(List)";
+ return false;
+}
+
+SfxStringListItem* SfxStringListItem::Clone( SfxItemPool *) const
+{
+ return new SfxStringListItem( *this );
+}
+
+void SfxStringListItem::SetString( const OUString& rStr )
+{
+ mpList = std::make_shared<std::vector<OUString>>();
+
+ OUString aStr(convertLineEnd(rStr, LINEEND_CR));
+ // put last string only if not empty
+ for (sal_Int32 nStart = 0; nStart >= 0 && nStart < aStr.getLength();)
+ mpList->push_back(aStr.getToken(0, '\r', nStart));
+}
+
+
+OUString SfxStringListItem::GetString()
+{
+ OUStringBuffer aStr;
+ if ( mpList )
+ {
+ for (auto iter = mpList->begin(), end = mpList->end(); iter != end;)
+ {
+ aStr.append(*iter);
+ ++iter;
+
+ if (iter == end)
+ break;
+
+ aStr.append(SAL_NEWLINE_STRING);
+ }
+ }
+ return aStr.makeStringAndClear();
+}
+
+
+void SfxStringListItem::SetStringList( const css::uno::Sequence< OUString >& rList )
+{
+ mpList = std::make_shared<std::vector<OUString>>(
+ comphelper::sequenceToContainer<std::vector<OUString>>(rList));
+}
+
+void SfxStringListItem::GetStringList( css::uno::Sequence< OUString >& rList ) const
+{
+ size_t nCount = mpList->size();
+
+ rList.realloc( nCount );
+ auto pList = rList.getArray();
+ for( size_t i=0; i < nCount; i++ )
+ pList[i] = (*mpList)[i];
+}
+
+// virtual
+bool SfxStringListItem::PutValue( const css::uno::Any& rVal, sal_uInt8 )
+{
+ css::uno::Sequence< OUString > aValue;
+ if ( rVal >>= aValue )
+ {
+ SetStringList( aValue );
+ return true;
+ }
+
+ OSL_FAIL( "SfxStringListItem::PutValue - Wrong type!" );
+ return false;
+}
+
+// virtual
+bool SfxStringListItem::QueryValue( css::uno::Any& rVal, sal_uInt8 ) const
+{
+ css::uno::Sequence< OUString > aStringList;
+ GetStringList( aStringList );
+ rVal <<= aStringList;
+ return true;
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/items/srchitem.cxx b/svl/source/items/srchitem.cxx
new file mode 100644
index 0000000000..1300bf744a
--- /dev/null
+++ b/svl/source/items/srchitem.cxx
@@ -0,0 +1,662 @@
+/* -*- 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 <svl/srchitem.hxx>
+#include <sal/macros.h>
+#include <osl/diagnose.h>
+
+#include <comphelper/propertyvalue.hxx>
+#include <unotools/searchopt.hxx>
+#include <com/sun/star/beans/PropertyValue.hpp>
+#include <com/sun/star/lang/Locale.hpp>
+#include <svl/memberid.h>
+#include <i18nlangtag/languagetag.hxx>
+
+#include <unordered_set>
+
+using namespace utl;
+using namespace com::sun::star;
+using namespace com::sun::star::beans;
+using namespace com::sun::star::uno;
+using namespace com::sun::star::util;
+
+constexpr OUString CFG_ROOT_NODE = u"Office.Common/SearchOptions"_ustr;
+
+#define SRCH_PARAMS 13
+constexpr OUString SRCH_PARA_OPTIONS = u"Options"_ustr;
+constexpr OUString SRCH_PARA_FAMILY = u"Family"_ustr;
+constexpr OUString SRCH_PARA_COMMAND = u"Command"_ustr;
+constexpr OUString SRCH_PARA_CELLTYPE = u"CellType"_ustr;
+constexpr OUString SRCH_PARA_APPFLAG = u"AppFlag"_ustr;
+constexpr OUString SRCH_PARA_ROWDIR = u"RowDirection"_ustr;
+constexpr OUString SRCH_PARA_ALLTABLES = u"AllTables"_ustr;
+constexpr OUString SRCH_PARA_SEARCHFILTERED = u"SearchFiltered"_ustr;
+constexpr OUString SRCH_PARA_SEARCHFORMATTED = u"SearchFormatted"_ustr;
+constexpr OUString SRCH_PARA_BACKWARD = u"Backward"_ustr;
+constexpr OUString SRCH_PARA_PATTERN = u"Pattern"_ustr;
+constexpr OUString SRCH_PARA_CONTENT = u"Content"_ustr;
+constexpr OUString SRCH_PARA_ASIANOPT = u"AsianOptions"_ustr;
+
+SfxPoolItem* SvxSearchItem::CreateDefault() { return new SvxSearchItem(0);}
+
+
+static Sequence< OUString > lcl_GetNotifyNames()
+{
+ // names of transliteration relevant properties
+ static const char* aTranslitNames[] =
+ {
+ "IsMatchCase", // 0
+ "Japanese/IsMatchFullHalfWidthForms", // 1
+ "Japanese/IsMatchHiraganaKatakana", // 2
+ "Japanese/IsMatchContractions", // 3
+ "Japanese/IsMatchMinusDashCho-on", // 4
+ "Japanese/IsMatchRepeatCharMarks", // 5
+ "Japanese/IsMatchVariantFormKanji", // 6
+ "Japanese/IsMatchOldKanaForms", // 7
+ "Japanese/IsMatch_DiZi_DuZu", // 8
+ "Japanese/IsMatch_BaVa_HaFa", // 9
+ "Japanese/IsMatch_TsiThiChi_DhiZi", // 10
+ "Japanese/IsMatch_HyuIyu_ByuVyu", // 11
+ "Japanese/IsMatch_SeShe_ZeJe", // 12
+ "Japanese/IsMatch_IaIya", // 13
+ "Japanese/IsMatch_KiKu", // 14
+ "Japanese/IsIgnorePunctuation", // 15
+ "Japanese/IsIgnoreWhitespace", // 16
+ "Japanese/IsIgnoreProlongedSoundMark", // 17
+ "Japanese/IsIgnoreMiddleDot", // 18
+ "IsIgnoreDiacritics_CTL", // 19
+ "IsIgnoreKashida_CTL" // 20
+ };
+
+ const int nCount = SAL_N_ELEMENTS( aTranslitNames );
+ Sequence< OUString > aNames( nCount );
+ OUString* pNames = aNames.getArray();
+ for (sal_Int32 i = 0; i < nCount; ++i)
+ pNames[i] = OUString::createFromAscii( aTranslitNames[i] );
+
+ return aNames;
+}
+
+
+SvxSearchItem::SvxSearchItem( const sal_uInt16 nId ) :
+
+ SfxPoolItem( nId ),
+ ConfigItem( CFG_ROOT_NODE ),
+
+ m_aSearchOpt ( SearchFlags::LEV_RELAXED,
+ OUString(),
+ OUString(),
+ lang::Locale(),
+ 2, 2, 2,
+ TransliterationFlags::IGNORE_CASE,
+ SearchAlgorithms2::ABSOLUTE, '\\' ),
+ m_eFamily ( SfxStyleFamily::Para ),
+ m_nCommand ( SvxSearchCmd::FIND ),
+ m_nCellType ( SvxSearchCellType::FORMULA ),
+ m_nAppFlag ( SvxSearchApp::WRITER ),
+ m_bRowDirection ( true ),
+ m_bAllTables ( false ),
+ m_bSearchFiltered ( false ),
+ m_bSearchFormatted( false ),
+ m_bNotes ( false),
+ m_bBackward ( false ),
+ m_bPattern ( false ),
+ m_bContent ( false ),
+ m_bAsianOptions ( false ),
+ m_nStartPointX(0),
+ m_nStartPointY(0)
+{
+ EnableNotification( lcl_GetNotifyNames() );
+
+ SvtSearchOptions aOpt;
+
+ m_bBackward = aOpt.IsBackwards();
+ m_bAsianOptions = aOpt.IsUseAsianOptions();
+ m_bNotes = aOpt.IsNotes();
+
+ if (aOpt.IsUseWildcard())
+ {
+ m_aSearchOpt.AlgorithmType2 = SearchAlgorithms2::WILDCARD;
+ }
+ if (aOpt.IsUseRegularExpression())
+ {
+ m_aSearchOpt.AlgorithmType2 = SearchAlgorithms2::REGEXP;
+ }
+ if (aOpt.IsSimilaritySearch())
+ {
+ m_aSearchOpt.AlgorithmType2 = SearchAlgorithms2::APPROXIMATE;
+ }
+ if (aOpt.IsWholeWordsOnly())
+ m_aSearchOpt.searchFlag |= SearchFlags::NORM_WORD_ONLY;
+
+ TransliterationFlags& rFlags = m_aSearchOpt.transliterateFlags;
+
+ if (!aOpt.IsMatchCase())
+ rFlags |= TransliterationFlags::IGNORE_CASE;
+ if ( aOpt.IsMatchFullHalfWidthForms())
+ rFlags |= TransliterationFlags::IGNORE_WIDTH;
+ if ( aOpt.IsIgnoreDiacritics_CTL())
+ rFlags |= TransliterationFlags::IGNORE_DIACRITICS_CTL ;
+ if ( aOpt.IsIgnoreKashida_CTL())
+ rFlags |= TransliterationFlags::IGNORE_KASHIDA_CTL ;
+ if ( !m_bAsianOptions )
+ return;
+
+ if ( aOpt.IsMatchHiraganaKatakana())
+ rFlags |= TransliterationFlags::IGNORE_KANA;
+ if ( aOpt.IsMatchContractions())
+ rFlags |= TransliterationFlags::ignoreSize_ja_JP;
+ if ( aOpt.IsMatchMinusDashChoon())
+ rFlags |= TransliterationFlags::ignoreMinusSign_ja_JP;
+ if ( aOpt.IsMatchRepeatCharMarks())
+ rFlags |= TransliterationFlags::ignoreIterationMark_ja_JP;
+ if ( aOpt.IsMatchVariantFormKanji())
+ rFlags |= TransliterationFlags::ignoreTraditionalKanji_ja_JP;
+ if ( aOpt.IsMatchOldKanaForms())
+ rFlags |= TransliterationFlags::ignoreTraditionalKana_ja_JP;
+ if ( aOpt.IsMatchDiziDuzu())
+ rFlags |= TransliterationFlags::ignoreZiZu_ja_JP;
+ if ( aOpt.IsMatchBavaHafa())
+ rFlags |= TransliterationFlags::ignoreBaFa_ja_JP;
+ if ( aOpt.IsMatchTsithichiDhizi())
+ rFlags |= TransliterationFlags::ignoreTiJi_ja_JP;
+ if ( aOpt.IsMatchHyuiyuByuvyu())
+ rFlags |= TransliterationFlags::ignoreHyuByu_ja_JP;
+ if ( aOpt.IsMatchSesheZeje())
+ rFlags |= TransliterationFlags::ignoreSeZe_ja_JP;
+ if ( aOpt.IsMatchIaiya())
+ rFlags |= TransliterationFlags::ignoreIandEfollowedByYa_ja_JP;
+ if ( aOpt.IsMatchKiku())
+ rFlags |= TransliterationFlags::ignoreKiKuFollowedBySa_ja_JP;
+ if ( aOpt.IsIgnorePunctuation())
+ rFlags |= TransliterationFlags::ignoreSeparator_ja_JP;
+ if ( aOpt.IsIgnoreWhitespace())
+ rFlags |= TransliterationFlags::ignoreSpace_ja_JP;
+ if ( aOpt.IsIgnoreProlongedSoundMark())
+ rFlags |= TransliterationFlags::ignoreProlongedSoundMark_ja_JP;
+ if ( aOpt.IsIgnoreMiddleDot())
+ rFlags |= TransliterationFlags::ignoreMiddleDot_ja_JP;
+}
+
+
+SvxSearchItem::SvxSearchItem( const SvxSearchItem& rItem ) :
+
+ SfxPoolItem ( rItem ),
+ ConfigItem( CFG_ROOT_NODE ),
+
+ m_aSearchOpt ( rItem.m_aSearchOpt ),
+ m_eFamily ( rItem.m_eFamily ),
+ m_nCommand ( rItem.m_nCommand ),
+ m_nCellType ( rItem.m_nCellType ),
+ m_nAppFlag ( rItem.m_nAppFlag ),
+ m_bRowDirection ( rItem.m_bRowDirection ),
+ m_bAllTables ( rItem.m_bAllTables ),
+ m_bSearchFiltered ( rItem.m_bSearchFiltered ),
+ m_bSearchFormatted ( rItem.m_bSearchFormatted ),
+ m_bNotes ( rItem.m_bNotes),
+ m_bBackward ( rItem.m_bBackward ),
+ m_bPattern ( rItem.m_bPattern ),
+ m_bContent ( rItem.m_bContent ),
+ m_bAsianOptions ( rItem.m_bAsianOptions ),
+ m_nStartPointX(rItem.m_nStartPointX),
+ m_nStartPointY(rItem.m_nStartPointY)
+{
+ EnableNotification( lcl_GetNotifyNames() );
+}
+
+SvxSearchItem::~SvxSearchItem()
+{
+}
+
+SvxSearchItem* SvxSearchItem::Clone( SfxItemPool *) const
+{
+ return new SvxSearchItem(*this);
+}
+
+//! used below
+static bool equalsWithoutLocaleOrReplace(const i18nutil::SearchOptions2& rItem1,
+ const i18nutil::SearchOptions2& rItem2)
+{
+ return rItem1.searchFlag == rItem2.searchFlag &&
+ rItem1.searchString == rItem2.searchString &&
+ //rItem1.replaceString == rItem2.replaceString &&
+ //rItem1.Locale == rItem2.Locale &&
+ rItem1.changedChars == rItem2.changedChars &&
+ rItem1.deletedChars == rItem2.deletedChars &&
+ rItem1.insertedChars == rItem2.insertedChars &&
+ rItem1.transliterateFlags == rItem2.transliterateFlags &&
+ rItem1.AlgorithmType2 == rItem2.AlgorithmType2 &&
+ rItem1.WildcardEscapeCharacter == rItem2.WildcardEscapeCharacter;
+}
+
+
+bool SvxSearchItem::operator==( const SfxPoolItem& rItem ) const
+{
+ assert(SfxPoolItem::operator==(rItem));
+ const SvxSearchItem &rSItem = static_cast<const SvxSearchItem &>(rItem);
+ return equalsIgnoring(rSItem, /*bIgnoreReplace=*/false, /*bIgnoreCommand=*/false);
+}
+
+bool SvxSearchItem::equalsIgnoring(const SvxSearchItem& rSItem, bool bIgnoreReplace,
+ bool bIgnoreCommand) const
+{
+ if (!bIgnoreReplace && m_aSearchOpt.replaceString != rSItem.m_aSearchOpt.replaceString)
+ return false;
+ if (!bIgnoreCommand && m_nCommand != rSItem.m_nCommand)
+ return false;
+
+ return ( m_bBackward == rSItem.m_bBackward ) &&
+ ( m_bPattern == rSItem.m_bPattern ) &&
+ ( m_bContent == rSItem.m_bContent ) &&
+ ( m_eFamily == rSItem.m_eFamily ) &&
+ ( m_bRowDirection == rSItem.m_bRowDirection ) &&
+ ( m_bAllTables == rSItem.m_bAllTables ) &&
+ ( m_bSearchFiltered == rSItem.m_bSearchFiltered ) &&
+ ( m_bSearchFormatted == rSItem.m_bSearchFormatted ) &&
+ ( m_nCellType == rSItem.m_nCellType ) &&
+ ( m_nAppFlag == rSItem.m_nAppFlag ) &&
+ ( m_bAsianOptions == rSItem.m_bAsianOptions ) &&
+ ( equalsWithoutLocaleOrReplace(m_aSearchOpt, rSItem.m_aSearchOpt )) &&
+ ( m_bNotes == rSItem.m_bNotes );
+}
+
+
+bool SvxSearchItem::GetPresentation
+(
+ SfxItemPresentation ,
+ MapUnit ,
+ MapUnit ,
+ OUString& ,
+ const IntlWrapper&
+) const
+{
+ return false;
+}
+
+void SvxSearchItem::Notify( const Sequence< OUString > & )
+{
+ // applies transliteration changes in the configuration database
+ // to the current SvxSearchItem
+ SetTransliterationFlags( SvtSearchOptions().GetTransliterationFlags() );
+}
+
+void SvxSearchItem::ImplCommit()
+{
+}
+
+void SvxSearchItem::SetMatchFullHalfWidthForms( bool bVal )
+{
+ if (bVal)
+ m_aSearchOpt.transliterateFlags |= TransliterationFlags::IGNORE_WIDTH;
+ else
+ m_aSearchOpt.transliterateFlags &= ~TransliterationFlags::IGNORE_WIDTH;
+}
+
+
+void SvxSearchItem::SetWordOnly( bool bVal )
+{
+ if (bVal)
+ m_aSearchOpt.searchFlag |= SearchFlags::NORM_WORD_ONLY;
+ else
+ m_aSearchOpt.searchFlag &= ~SearchFlags::NORM_WORD_ONLY;
+}
+
+
+void SvxSearchItem::SetExact( bool bVal )
+{
+ if (!bVal)
+ m_aSearchOpt.transliterateFlags |= TransliterationFlags::IGNORE_CASE;
+ else
+ m_aSearchOpt.transliterateFlags &= ~TransliterationFlags::IGNORE_CASE;
+}
+
+
+void SvxSearchItem::SetSelection( bool bVal )
+{
+ if (bVal)
+ {
+ m_aSearchOpt.searchFlag |= (SearchFlags::REG_NOT_BEGINOFLINE |
+ SearchFlags::REG_NOT_ENDOFLINE);
+ }
+ else
+ {
+ m_aSearchOpt.searchFlag &= ~(SearchFlags::REG_NOT_BEGINOFLINE |
+ SearchFlags::REG_NOT_ENDOFLINE);
+ }
+}
+
+
+void SvxSearchItem::SetRegExp( bool bVal )
+{
+ if ( bVal )
+ {
+ m_aSearchOpt.AlgorithmType2 = SearchAlgorithms2::REGEXP;
+ }
+ else if ( SearchAlgorithms2::REGEXP == m_aSearchOpt.AlgorithmType2 )
+ {
+ m_aSearchOpt.AlgorithmType2 = SearchAlgorithms2::ABSOLUTE;
+ }
+}
+
+
+void SvxSearchItem::SetWildcard( bool bVal )
+{
+ if ( bVal )
+ {
+ m_aSearchOpt.AlgorithmType2 = SearchAlgorithms2::WILDCARD;
+ }
+ else if ( SearchAlgorithms2::REGEXP == m_aSearchOpt.AlgorithmType2 )
+ {
+ m_aSearchOpt.AlgorithmType2 = SearchAlgorithms2::ABSOLUTE;
+ }
+}
+
+
+void SvxSearchItem::SetLEVRelaxed( bool bVal )
+{
+ if (bVal)
+ m_aSearchOpt.searchFlag |= SearchFlags::LEV_RELAXED;
+ else
+ m_aSearchOpt.searchFlag &= ~SearchFlags::LEV_RELAXED;
+}
+
+
+void SvxSearchItem::SetLevenshtein( bool bVal )
+{
+ if ( bVal )
+ {
+ m_aSearchOpt.AlgorithmType2 = SearchAlgorithms2::APPROXIMATE;
+ }
+ else if ( SearchAlgorithms2::APPROXIMATE == m_aSearchOpt.AlgorithmType2 )
+ {
+ m_aSearchOpt.AlgorithmType2 = SearchAlgorithms2::ABSOLUTE;
+ }
+}
+
+
+void SvxSearchItem::SetTransliterationFlags( TransliterationFlags nFlags )
+{
+ m_aSearchOpt.transliterateFlags = nFlags;
+}
+
+bool SvxSearchItem::QueryValue( css::uno::Any& rVal, sal_uInt8 nMemberId ) const
+{
+ nMemberId &= ~CONVERT_TWIPS;
+ switch ( nMemberId )
+ {
+ case 0 :
+ {
+ Sequence<PropertyValue> aSeq{
+ comphelper::makePropertyValue(SRCH_PARA_OPTIONS,
+ m_aSearchOpt.toUnoSearchOptions2()),
+ comphelper::makePropertyValue(SRCH_PARA_FAMILY, sal_Int16(m_eFamily)),
+ comphelper::makePropertyValue(SRCH_PARA_COMMAND,
+ static_cast<sal_uInt16>(m_nCommand)),
+ comphelper::makePropertyValue(SRCH_PARA_CELLTYPE,
+ static_cast<sal_uInt16>(m_nCellType)),
+ comphelper::makePropertyValue(SRCH_PARA_APPFLAG,
+ static_cast<sal_uInt16>(m_nAppFlag)),
+ comphelper::makePropertyValue(SRCH_PARA_ROWDIR, m_bRowDirection),
+ comphelper::makePropertyValue(SRCH_PARA_ALLTABLES, m_bAllTables),
+ comphelper::makePropertyValue(SRCH_PARA_SEARCHFILTERED, m_bSearchFiltered),
+ comphelper::makePropertyValue(SRCH_PARA_SEARCHFORMATTED, m_bSearchFormatted),
+ comphelper::makePropertyValue(SRCH_PARA_BACKWARD, m_bBackward),
+ comphelper::makePropertyValue(SRCH_PARA_PATTERN, m_bPattern),
+ comphelper::makePropertyValue(SRCH_PARA_CONTENT, m_bContent),
+ comphelper::makePropertyValue(SRCH_PARA_ASIANOPT, m_bAsianOptions)
+ };
+ assert(aSeq.getLength() == SRCH_PARAMS);
+ rVal <<= aSeq;
+ }
+ break;
+ case MID_SEARCH_COMMAND:
+ rVal <<= static_cast<sal_Int16>(m_nCommand); break;
+ case MID_SEARCH_STYLEFAMILY:
+ rVal <<= static_cast<sal_Int16>(m_eFamily); break;
+ case MID_SEARCH_CELLTYPE:
+ rVal <<= static_cast<sal_Int32>(m_nCellType); break;
+ case MID_SEARCH_ROWDIRECTION:
+ rVal <<= m_bRowDirection; break;
+ case MID_SEARCH_ALLTABLES:
+ rVal <<= m_bAllTables; break;
+ case MID_SEARCH_SEARCHFILTERED:
+ rVal <<= m_bSearchFiltered; break;
+ case MID_SEARCH_SEARCHFORMATTED:
+ rVal <<= m_bSearchFormatted; break;
+ case MID_SEARCH_BACKWARD:
+ rVal <<= m_bBackward; break;
+ case MID_SEARCH_PATTERN:
+ rVal <<= m_bPattern; break;
+ case MID_SEARCH_CONTENT:
+ rVal <<= m_bContent; break;
+ case MID_SEARCH_ASIANOPTIONS:
+ rVal <<= m_bAsianOptions; break;
+ case MID_SEARCH_ALGORITHMTYPE:
+ rVal <<= static_cast<sal_Int16>(i18nutil::downgradeSearchAlgorithms2(m_aSearchOpt.AlgorithmType2)); break;
+ case MID_SEARCH_ALGORITHMTYPE2:
+ rVal <<= m_aSearchOpt.AlgorithmType2; break;
+ case MID_SEARCH_FLAGS:
+ rVal <<= m_aSearchOpt.searchFlag; break;
+ case MID_SEARCH_SEARCHSTRING:
+ rVal <<= m_aSearchOpt.searchString; break;
+ case MID_SEARCH_REPLACESTRING:
+ rVal <<= m_aSearchOpt.replaceString; break;
+ case MID_SEARCH_CHANGEDCHARS:
+ rVal <<= m_aSearchOpt.changedChars; break;
+ case MID_SEARCH_DELETEDCHARS:
+ rVal <<= m_aSearchOpt.deletedChars; break;
+ case MID_SEARCH_INSERTEDCHARS:
+ rVal <<= m_aSearchOpt.insertedChars; break;
+ case MID_SEARCH_TRANSLITERATEFLAGS:
+ rVal <<= static_cast<sal_Int32>(m_aSearchOpt.transliterateFlags); break;
+ case MID_SEARCH_LOCALE:
+ {
+ LanguageType nLocale;
+ if (!m_aSearchOpt.Locale.Language.isEmpty() || !m_aSearchOpt.Locale.Country.isEmpty() )
+ nLocale = LanguageTag::convertToLanguageType( m_aSearchOpt.Locale );
+ else
+ nLocale = LANGUAGE_NONE;
+ rVal <<= static_cast<sal_Int16>(static_cast<sal_uInt16>(nLocale));
+ break;
+ }
+
+ default:
+ SAL_WARN( "svl.items", "SvxSearchItem::QueryValue(): Unknown MemberId" );
+ return false;
+ }
+
+ return true;
+}
+
+
+bool SvxSearchItem::PutValue( const css::uno::Any& rVal, sal_uInt8 nMemberId )
+{
+ nMemberId &= ~CONVERT_TWIPS;
+ auto ExtractNumericAny = [](const css::uno::Any& a, auto& target)
+ {
+ sal_Int32 nInt;
+ if (!(a >>= nInt))
+ return false;
+ target = static_cast<std::remove_reference_t<decltype(target)>>(nInt);
+ return true;
+ };
+ switch ( nMemberId )
+ {
+ case 0 :
+ {
+ Sequence< PropertyValue > aSeq;
+ if (!(rVal >>= aSeq) || aSeq.getLength() != SRCH_PARAMS)
+ break;
+ std::unordered_set<OUString> aConvertedParams;
+ for (const auto& rProp : aSeq)
+ {
+ if (rProp.Name == SRCH_PARA_OPTIONS)
+ {
+ if (css::util::SearchOptions2 nTmpSearchOpt2; rProp.Value >>= nTmpSearchOpt2)
+ {
+ m_aSearchOpt = nTmpSearchOpt2;
+ aConvertedParams.insert(rProp.Name);
+ }
+ }
+ else if (rProp.Name == SRCH_PARA_FAMILY)
+ {
+ if (SvxSearchItem::PutValue(rProp.Value, MID_SEARCH_STYLEFAMILY))
+ aConvertedParams.insert(rProp.Name);
+ }
+ else if (rProp.Name == SRCH_PARA_COMMAND)
+ {
+ if (SvxSearchItem::PutValue(rProp.Value, MID_SEARCH_COMMAND))
+ aConvertedParams.insert(rProp.Name);
+ }
+ else if (rProp.Name == SRCH_PARA_CELLTYPE)
+ {
+ if (SvxSearchItem::PutValue(rProp.Value, MID_SEARCH_CELLTYPE))
+ aConvertedParams.insert(rProp.Name);
+ }
+ else if (rProp.Name == SRCH_PARA_APPFLAG)
+ {
+ if (ExtractNumericAny(rProp.Value, m_nAppFlag))
+ aConvertedParams.insert(rProp.Name);
+ }
+ else if (rProp.Name == SRCH_PARA_ROWDIR)
+ {
+ if (SvxSearchItem::PutValue(rProp.Value, MID_SEARCH_ROWDIRECTION))
+ aConvertedParams.insert(rProp.Name);
+ }
+ else if (rProp.Name == SRCH_PARA_ALLTABLES)
+ {
+ if (SvxSearchItem::PutValue(rProp.Value, MID_SEARCH_ALLTABLES))
+ aConvertedParams.insert(rProp.Name);
+ }
+ else if (rProp.Name == SRCH_PARA_SEARCHFILTERED)
+ {
+ if (SvxSearchItem::PutValue(rProp.Value, MID_SEARCH_SEARCHFILTERED))
+ aConvertedParams.insert(rProp.Name);
+ }
+ else if (rProp.Name == SRCH_PARA_SEARCHFORMATTED)
+ {
+ if (SvxSearchItem::PutValue(rProp.Value, MID_SEARCH_SEARCHFORMATTED))
+ aConvertedParams.insert(rProp.Name);
+ }
+ else if (rProp.Name == SRCH_PARA_BACKWARD)
+ {
+ if (SvxSearchItem::PutValue(rProp.Value, MID_SEARCH_BACKWARD))
+ aConvertedParams.insert(rProp.Name);
+ }
+ else if (rProp.Name == SRCH_PARA_PATTERN)
+ {
+ if (SvxSearchItem::PutValue(rProp.Value, MID_SEARCH_PATTERN))
+ aConvertedParams.insert(rProp.Name);
+ }
+ else if (rProp.Name == SRCH_PARA_CONTENT)
+ {
+ if (SvxSearchItem::PutValue(rProp.Value, MID_SEARCH_CONTENT))
+ aConvertedParams.insert(rProp.Name);
+ }
+ else if (rProp.Name == SRCH_PARA_ASIANOPT)
+ {
+ if (SvxSearchItem::PutValue(rProp.Value, MID_SEARCH_ASIANOPTIONS))
+ aConvertedParams.insert(rProp.Name);
+ }
+ }
+ return aConvertedParams.size() == SRCH_PARAMS;
+ }
+ case MID_SEARCH_COMMAND:
+ return ExtractNumericAny(rVal, m_nCommand);
+ case MID_SEARCH_STYLEFAMILY:
+ return ExtractNumericAny(rVal, m_eFamily);
+ case MID_SEARCH_CELLTYPE:
+ return ExtractNumericAny(rVal, m_nCellType);
+ case MID_SEARCH_ROWDIRECTION:
+ return (rVal >>= m_bRowDirection);
+ case MID_SEARCH_ALLTABLES:
+ return (rVal >>= m_bAllTables);
+ case MID_SEARCH_SEARCHFILTERED:
+ return (rVal >>= m_bSearchFiltered);
+ case MID_SEARCH_SEARCHFORMATTED:
+ return (rVal >>= m_bSearchFormatted);
+ case MID_SEARCH_BACKWARD:
+ return (rVal >>= m_bBackward);
+ case MID_SEARCH_PATTERN:
+ return (rVal >>= m_bPattern);
+ case MID_SEARCH_CONTENT:
+ return (rVal >>= m_bContent);
+ case MID_SEARCH_ASIANOPTIONS:
+ return (rVal >>= m_bAsianOptions);
+ case MID_SEARCH_ALGORITHMTYPE:
+ if (SearchAlgorithms eVal; ExtractNumericAny(rVal, eVal))
+ {
+ m_aSearchOpt.AlgorithmType2 = i18nutil::upgradeSearchAlgorithms(eVal);
+ return true;
+ }
+ break;
+ case MID_SEARCH_ALGORITHMTYPE2:
+ return (rVal >>= m_aSearchOpt.AlgorithmType2);
+ case MID_SEARCH_FLAGS:
+ return (rVal >>= m_aSearchOpt.searchFlag);
+ case MID_SEARCH_SEARCHSTRING:
+ return (rVal >>= m_aSearchOpt.searchString);
+ case MID_SEARCH_REPLACESTRING:
+ return (rVal >>= m_aSearchOpt.replaceString);
+ case MID_SEARCH_CHANGEDCHARS:
+ return (rVal >>= m_aSearchOpt.changedChars);
+ case MID_SEARCH_DELETEDCHARS:
+ return (rVal >>= m_aSearchOpt.deletedChars);
+ case MID_SEARCH_INSERTEDCHARS:
+ return (rVal >>= m_aSearchOpt.insertedChars);
+ case MID_SEARCH_TRANSLITERATEFLAGS:
+ return ExtractNumericAny(rVal, m_aSearchOpt.transliterateFlags);
+ case MID_SEARCH_LOCALE:
+ if (LanguageType aVal; ExtractNumericAny(rVal, aVal))
+ {
+ m_aSearchOpt.Locale = (aVal == LANGUAGE_NONE) ? css::lang::Locale()
+ : LanguageTag::convertToLocale(aVal);
+ return true;
+ }
+ break;
+ case MID_SEARCH_STARTPOINTX:
+ return (rVal >>= m_nStartPointX);
+ case MID_SEARCH_STARTPOINTY:
+ return (rVal >>= m_nStartPointY);
+ default:
+ OSL_FAIL( "Unknown MemberId" );
+ }
+
+ return false;
+}
+
+sal_Int32 SvxSearchItem::GetStartPointX() const
+{
+ return m_nStartPointX;
+}
+
+sal_Int32 SvxSearchItem::GetStartPointY() const
+{
+ return m_nStartPointY;
+}
+
+bool SvxSearchItem::HasStartPoint() const
+{
+ return m_nStartPointX > 0 || m_nStartPointY > 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/items/stringio.cxx b/svl/source/items/stringio.cxx
new file mode 100644
index 0000000000..5345e165aa
--- /dev/null
+++ b/svl/source/items/stringio.cxx
@@ -0,0 +1,34 @@
+/* -*- 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 <stringio.hxx>
+
+#include <tools/stream.hxx>
+
+OUString readByteString(SvStream& rStream)
+{
+ return rStream.ReadUniOrByteString(rStream.GetStreamCharSet());
+}
+
+void writeByteString(SvStream& rStream, std::u16string_view rString)
+{
+ rStream.WriteUniOrByteString(rString, rStream.GetStreamCharSet());
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/items/stritem.cxx b/svl/source/items/stritem.cxx
new file mode 100644
index 0000000000..c0ec01a9ab
--- /dev/null
+++ b/svl/source/items/stritem.cxx
@@ -0,0 +1,42 @@
+/* -*- 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 <svl/stritem.hxx>
+#include <libxml/xmlwriter.h>
+
+// virtual
+SfxStringItem* SfxStringItem::Clone(SfxItemPool *) const
+{
+ return new SfxStringItem(*this);
+}
+
+void SfxStringItem::dumpAsXml(xmlTextWriterPtr pWriter) const
+{
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SfxStringItem"));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("whichId"), BAD_CAST(OString::number(Which()).getStr()));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"), BAD_CAST(GetValue().toUtf8().getStr()));
+ (void)xmlTextWriterEndElement(pWriter);
+}
+
+SfxPoolItem* SfxStringItem::CreateDefault()
+{
+ return new SfxStringItem();
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/items/style.cxx b/svl/source/items/style.cxx
new file mode 100644
index 0000000000..41551e5064
--- /dev/null
+++ b/svl/source/items/style.cxx
@@ -0,0 +1,905 @@
+/* -*- 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 <memory>
+#include <svl/style.hxx>
+
+#include <com/sun/star/lang/XComponent.hpp>
+
+#include <sal/log.hxx>
+#include <osl/diagnose.h>
+#include <unotools/intlwrapper.hxx>
+#include <svl/hint.hxx>
+#include <svl/poolitem.hxx>
+#include <svl/itemset.hxx>
+#include <svl/itempool.hxx>
+#include <svl/IndexedStyleSheets.hxx>
+#include <svl/itemiter.hxx>
+#include <unotools/syslocale.hxx>
+#include <comphelper/servicehelper.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <utility>
+
+#ifdef DBG_UTIL
+namespace {
+
+class DbgStyleSheetReferences
+{
+public:
+ DbgStyleSheetReferences() : mnStyles(0), mnPools(0) {}
+ ~DbgStyleSheetReferences()
+ {
+ SAL_WARN_IF(
+ mnStyles != 0 || mnPools != 0, "svl.items",
+ "SfxStyleSheetBase left " << mnStyles
+ << "; SfxStyleSheetBasePool left " << mnPools);
+ }
+
+ sal_uInt32 mnStyles;
+ sal_uInt32 mnPools;
+};
+
+}
+
+static DbgStyleSheetReferences aDbgStyleSheetReferences;
+#endif
+
+
+SfxStyleSheetModifiedHint::SfxStyleSheetModifiedHint
+(
+ OUString aOldName,
+ SfxStyleSheetBase& rStyleSheet // Remains with the caller
+)
+: SfxStyleSheetHint( SfxHintId::StyleSheetModified, rStyleSheet ),
+ aName(std::move( aOldName ))
+{}
+
+
+SfxStyleSheetHint::SfxStyleSheetHint
+(
+ SfxHintId nAction,
+ SfxStyleSheetBase& rStyleSheet // Remains with the caller
+)
+: SfxHint(nAction), pStyleSh( &rStyleSheet )
+{}
+
+
+class SfxStyleSheetBasePool_Impl
+{
+private:
+ SfxStyleSheetBasePool_Impl(const SfxStyleSheetBasePool_Impl&) = delete;
+ SfxStyleSheetBasePool_Impl& operator=(const SfxStyleSheetBasePool_Impl&) = delete;
+public:
+ std::shared_ptr<SfxStyleSheetIterator> pIter;
+
+ /** This member holds the indexed style sheets.
+ *
+ * @internal
+ * This member is private and not protected in order to have more control which style sheets are added
+ * where. Ideally, all calls which add/remove/change style sheets are done in the base class.
+ */
+ std::shared_ptr<svl::IndexedStyleSheets> mxIndexedStyleSheets;
+
+ SfxStyleSheetBasePool_Impl() :
+ mxIndexedStyleSheets(std::make_shared<svl::IndexedStyleSheets>()) {}
+};
+
+
+SfxStyleSheetBase::SfxStyleSheetBase( const OUString& rName, SfxStyleSheetBasePool* p, SfxStyleFamily eFam, SfxStyleSearchBits mask )
+ : m_pPool( p )
+ , nFamily( eFam )
+ , aName( rName )
+ , aFollow( rName )
+ , pSet( nullptr )
+ , nMask(mask)
+ , nHelpId( 0 )
+ , bMySet( false )
+ , bHidden( false )
+{
+#ifdef DBG_UTIL
+ aDbgStyleSheetReferences.mnStyles++;
+#endif
+}
+
+SfxStyleSheetBase::SfxStyleSheetBase( const SfxStyleSheetBase& r )
+ : WeakImplHelper()
+ , m_pPool( r.m_pPool )
+ , nFamily( r.nFamily )
+ , aName( r.aName )
+ , aParent( r.aParent )
+ , aFollow( r.aFollow )
+ , aHelpFile( r.aHelpFile )
+ , nMask( r.nMask )
+ , nHelpId( r.nHelpId )
+ , bMySet( r.bMySet )
+ , bHidden( r.bHidden )
+{
+#ifdef DBG_UTIL
+ aDbgStyleSheetReferences.mnStyles++;
+#endif
+ if( r.pSet )
+ pSet = bMySet ? new SfxItemSet( *r.pSet ) : r.pSet;
+ else
+ pSet = nullptr;
+}
+
+SfxStyleSheetBase::~SfxStyleSheetBase()
+{
+#ifdef DBG_UTIL
+ --aDbgStyleSheetReferences.mnStyles;
+#endif
+
+ if( bMySet )
+ {
+ delete pSet;
+ pSet = nullptr;
+ }
+}
+
+// Change name
+const OUString& SfxStyleSheetBase::GetName() const
+{
+ return aName;
+}
+
+bool SfxStyleSheetBase::SetName(const OUString& rName, bool bReIndexNow)
+{
+ if(rName.isEmpty())
+ return false;
+
+ if( aName != rName )
+ {
+ OUString aOldName = aName;
+ SfxStyleSheetBase *pOther = m_pPool->Find( rName, nFamily ) ;
+ if ( pOther && pOther != this )
+ return false;
+
+ if ( !aName.isEmpty() )
+ m_pPool->ChangeParent(aName, rName, nFamily, false);
+
+ if ( aFollow == aName )
+ aFollow = rName;
+ aName = rName;
+ if (bReIndexNow)
+ m_pPool->Reindex();
+
+ m_pPool->Broadcast( SfxStyleSheetModifiedHint( aOldName, *this ) );
+ }
+ return true;
+}
+
+// Change Parent
+const OUString& SfxStyleSheetBase::GetParent() const
+{
+ return aParent;
+}
+
+bool SfxStyleSheetBase::SetParent( const OUString& rName )
+{
+ if ( rName == aName )
+ return false;
+
+ if( aParent != rName )
+ {
+ SfxStyleSheetBase* pIter = m_pPool->Find(rName, nFamily);
+ if( !rName.isEmpty() && !pIter )
+ {
+ OSL_FAIL( "StyleSheet-Parent not found" );
+ return false;
+ }
+ // prevent recursive linkages
+ if( !aName.isEmpty() )
+ {
+ while(pIter)
+ {
+ if(pIter->GetName() == aName)
+ return false;
+ pIter = m_pPool->Find(pIter->GetParent(), nFamily);
+ }
+ }
+ aParent = rName;
+ }
+ m_pPool->Broadcast( SfxStyleSheetHint( SfxHintId::StyleSheetModified, *this ) );
+ return true;
+}
+
+void SfxStyleSheetBase::SetHidden( bool hidden )
+{
+ bHidden = hidden;
+ m_pPool->Broadcast( SfxStyleSheetHint( SfxHintId::StyleSheetModified, *this ) );
+}
+
+/**
+ * Change follow
+ */
+const OUString& SfxStyleSheetBase::GetFollow() const
+{
+ return aFollow;
+}
+
+bool SfxStyleSheetBase::SetFollow( const OUString& rName )
+{
+ if( aFollow != rName )
+ {
+ if( !m_pPool->Find( rName, nFamily ) )
+ {
+ SAL_WARN( "svl.items", "StyleSheet-Follow not found" );
+ return false;
+ }
+ aFollow = rName;
+ }
+ m_pPool->Broadcast( SfxStyleSheetHint( SfxHintId::StyleSheetModified, *this ) );
+ return true;
+}
+
+/**
+ * Set Itemset
+ * The default implementation creates a new set
+ */
+SfxItemSet& SfxStyleSheetBase::GetItemSet()
+{
+ if( !pSet )
+ {
+ pSet = new SfxItemSet( m_pPool->GetPool() );
+ bMySet = true;
+ }
+ return *pSet;
+}
+
+std::optional<SfxItemSet> SfxStyleSheetBase::GetItemSetForPreview()
+{
+ return GetItemSet();
+}
+
+/**
+ * Set help file and ID and return it
+ */
+sal_uLong SfxStyleSheetBase::GetHelpId( OUString& rFile )
+{
+ rFile = aHelpFile;
+ return nHelpId;
+}
+
+void SfxStyleSheetBase::SetHelpId( const OUString& rFile, sal_uLong nId )
+{
+ aHelpFile = rFile;
+ nHelpId = nId;
+}
+
+/**
+ * Next style possible?
+ * Default: Yes
+ */
+bool SfxStyleSheetBase::HasFollowSupport() const
+{
+ return true;
+}
+
+/**
+ * Base template possible?
+ * Default: Yes
+ */
+bool SfxStyleSheetBase::HasParentSupport() const
+{
+ return true;
+}
+
+/**
+ * Setting base template to NULL possible?
+ * Default: No
+ */
+bool SfxStyleSheetBase::HasClearParentSupport() const
+{
+ return false;
+}
+
+/**
+ * By default all stylesheets are set to used
+ */
+bool SfxStyleSheetBase::IsUsed() const
+{
+ return true;
+}
+
+/**
+ * Return set attributes
+ */
+OUString SfxStyleSheetBase::GetDescription( MapUnit eMetric )
+{
+ SfxItemIter aIter( GetItemSet() );
+ OUStringBuffer aDesc;
+
+ IntlWrapper aIntlWrapper(SvtSysLocale().GetUILanguageTag());
+ for (const SfxPoolItem* pItem = aIter.GetCurItem(); pItem; pItem = aIter.NextItem())
+ {
+ OUString aItemPresentation;
+
+ if ( !IsInvalidItem( pItem ) &&
+ m_pPool->GetPool().GetPresentation(
+ *pItem, eMetric, aItemPresentation, aIntlWrapper ) )
+ {
+ if ( !aDesc.isEmpty() && !aItemPresentation.isEmpty() )
+ aDesc.append(" + ");
+ if ( !aItemPresentation.isEmpty() )
+ aDesc.append(aItemPresentation);
+ }
+ }
+ return aDesc.makeStringAndClear();
+}
+
+SfxStyleFamily SfxStyleSheetIterator::GetSearchFamily() const
+{
+ return nSearchFamily;
+}
+
+inline bool SfxStyleSheetIterator::IsTrivialSearch() const
+{
+ return (( nMask & SfxStyleSearchBits::AllVisible ) == SfxStyleSearchBits::AllVisible) &&
+ (GetSearchFamily() == SfxStyleFamily::All);
+}
+
+namespace {
+
+struct DoesStyleMatchStyleSheetPredicate final : public svl::StyleSheetPredicate
+{
+ explicit DoesStyleMatchStyleSheetPredicate(SfxStyleSheetIterator *it)
+ : mIterator(it) {}
+
+ bool
+ Check(const SfxStyleSheetBase& styleSheet) override
+ {
+ bool bMatchFamily = ((mIterator->GetSearchFamily() == SfxStyleFamily::All) ||
+ ( styleSheet.GetFamily() == mIterator->GetSearchFamily() ));
+
+ bool bUsed = mIterator->SearchUsed() && styleSheet.IsUsed( );
+
+ bool bSearchHidden( mIterator->GetSearchMask() & SfxStyleSearchBits::Hidden );
+ bool bMatchVisibility = bSearchHidden || !styleSheet.IsHidden() || bUsed;
+ bool bOnlyHidden = mIterator->GetSearchMask( ) == SfxStyleSearchBits::Hidden && styleSheet.IsHidden( );
+
+ bool bMatches = bMatchFamily && bMatchVisibility
+ && (( styleSheet.GetMask() & ( mIterator->GetSearchMask() & ~SfxStyleSearchBits::Used )) ||
+ bUsed || bOnlyHidden ||
+ ( mIterator->GetSearchMask() & SfxStyleSearchBits::AllVisible ) == SfxStyleSearchBits::AllVisible );
+ return bMatches;
+ }
+
+ SfxStyleSheetIterator *mIterator;
+};
+
+}
+
+SfxStyleSheetIterator::SfxStyleSheetIterator(const SfxStyleSheetBasePool *pBase,
+ SfxStyleFamily eFam, SfxStyleSearchBits n)
+ : pBasePool(pBase)
+ , pCurrentStyle(nullptr)
+ , mnCurrentPosition(0)
+{
+ nSearchFamily=eFam;
+ bSearchUsed=false;
+ if( (( n & SfxStyleSearchBits::AllVisible ) != SfxStyleSearchBits::AllVisible )
+ && ((n & SfxStyleSearchBits::Used) == SfxStyleSearchBits::Used))
+ {
+ bSearchUsed = true;
+ n &= ~SfxStyleSearchBits::Used;
+ }
+ nMask=n;
+}
+
+SfxStyleSheetIterator::~SfxStyleSheetIterator()
+{
+}
+
+sal_Int32 SfxStyleSheetIterator::Count()
+{
+ sal_Int32 n = 0;
+ if( IsTrivialSearch())
+ {
+ n = static_cast<sal_uInt16>(pBasePool->pImpl->mxIndexedStyleSheets->GetNumberOfStyleSheets());
+ }
+ else if(nMask == SfxStyleSearchBits::All)
+ {
+ n = static_cast<sal_uInt16>(pBasePool->pImpl->mxIndexedStyleSheets->GetStyleSheetPositionsByFamily(nSearchFamily).size());
+ }
+ else
+ {
+ DoesStyleMatchStyleSheetPredicate predicate(this);
+ n = pBasePool->pImpl->mxIndexedStyleSheets->GetNumberOfStyleSheetsWithPredicate(predicate);
+ }
+ return n;
+}
+
+SfxStyleSheetBase* SfxStyleSheetIterator::operator[](sal_Int32 nIdx)
+{
+ SfxStyleSheetBase* retval = nullptr;
+ if( IsTrivialSearch())
+ {
+ retval = pBasePool->pImpl->mxIndexedStyleSheets->GetStyleSheetByPosition(nIdx);
+ mnCurrentPosition = nIdx;
+ }
+ else if(nMask == SfxStyleSearchBits::All)
+ {
+ rtl::Reference< SfxStyleSheetBase > ref =
+ pBasePool->pImpl->mxIndexedStyleSheets->GetStyleSheetByPosition(
+ pBasePool->pImpl->mxIndexedStyleSheets->GetStyleSheetPositionsByFamily(nSearchFamily).at(nIdx))
+ ;
+ retval = ref.get();
+ mnCurrentPosition = nIdx;
+ }
+ else
+ {
+ DoesStyleMatchStyleSheetPredicate predicate(this);
+ rtl::Reference< SfxStyleSheetBase > ref =
+ pBasePool->pImpl->mxIndexedStyleSheets->GetNthStyleSheetThatMatchesPredicate(nIdx, predicate);
+ if (ref)
+ {
+ mnCurrentPosition = pBasePool->pImpl->mxIndexedStyleSheets->FindStyleSheetPosition(*ref);
+ retval = ref.get();
+ }
+ }
+
+ if (retval == nullptr)
+ {
+ OSL_FAIL("Incorrect index");
+ }
+
+ return retval;
+}
+
+SfxStyleSheetBase* SfxStyleSheetIterator::First()
+{
+ if (Count() != 0) {
+ return operator[](0);
+ }
+ else {
+ return nullptr;
+ }
+}
+
+SfxStyleSheetBase* SfxStyleSheetIterator::Next()
+{
+ SfxStyleSheetBase* retval = nullptr;
+
+ if ( IsTrivialSearch() )
+ {
+ sal_Int32 nStyleSheets = pBasePool->pImpl->mxIndexedStyleSheets->GetNumberOfStyleSheets();
+ sal_Int32 newPosition = mnCurrentPosition + 1;
+ if (nStyleSheets > newPosition)
+ {
+ mnCurrentPosition = newPosition;
+ retval = pBasePool->pImpl->mxIndexedStyleSheets->GetStyleSheetByPosition(mnCurrentPosition);
+ }
+ }
+ else if(nMask == SfxStyleSearchBits::All)
+ {
+ sal_Int32 newPosition = mnCurrentPosition + 1;
+ const std::vector<sal_Int32>& familyVector
+ =
+ pBasePool->pImpl->mxIndexedStyleSheets->GetStyleSheetPositionsByFamily(nSearchFamily);
+ if (static_cast<sal_Int32>(familyVector.size()) > newPosition)
+ {
+ mnCurrentPosition = newPosition;
+ sal_Int32 stylePosition = familyVector[newPosition];
+ retval = pBasePool->pImpl->mxIndexedStyleSheets->GetStyleSheetByPosition(stylePosition);
+ }
+ }
+ else
+ {
+ DoesStyleMatchStyleSheetPredicate predicate(this);
+ rtl::Reference< SfxStyleSheetBase > ref =
+ pBasePool->pImpl->mxIndexedStyleSheets->GetNthStyleSheetThatMatchesPredicate(
+ 0, predicate, mnCurrentPosition+1);
+ retval = ref.get();
+ if (retval != nullptr) {
+ mnCurrentPosition = pBasePool->pImpl->mxIndexedStyleSheets->FindStyleSheetPosition(*ref);
+ }
+ }
+ pCurrentStyle = retval;
+ return retval;
+}
+
+SfxStyleSheetBase* SfxStyleSheetIterator::Find(const OUString& rStr)
+{
+ DoesStyleMatchStyleSheetPredicate predicate(this);
+
+ std::vector<sal_Int32> positions =
+ pBasePool->pImpl->mxIndexedStyleSheets->FindPositionsByNameAndPredicate(rStr, predicate,
+ svl::IndexedStyleSheets::SearchBehavior::ReturnFirst);
+ if (positions.empty()) {
+ return nullptr;
+ }
+
+ sal_Int32 pos = positions.front();
+ SfxStyleSheetBase* pStyle = pBasePool->pImpl->mxIndexedStyleSheets->GetStyleSheetByPosition(pos);
+ mnCurrentPosition = pos;
+ pCurrentStyle = pStyle;
+ return pCurrentStyle;
+}
+
+SfxStyleSearchBits SfxStyleSheetIterator::GetSearchMask() const
+{
+ SfxStyleSearchBits mask = nMask;
+
+ if ( bSearchUsed )
+ mask |= SfxStyleSearchBits::Used;
+ return mask;
+}
+
+SfxStyleSheetIterator* SfxStyleSheetBasePool::GetCachedIterator()
+{
+ return pImpl->pIter.get();
+}
+
+SfxStyleSheetIterator& SfxStyleSheetBasePool::GetIterator_Impl(SfxStyleFamily eFamily, SfxStyleSearchBits eMask)
+{
+ if (!pImpl->pIter || (pImpl->pIter->GetSearchMask() != eMask) || (pImpl->pIter->GetSearchFamily() != eFamily))
+ pImpl->pIter = CreateIterator(eFamily, eMask);
+ return *pImpl->pIter;
+}
+
+SfxStyleSheetBasePool::SfxStyleSheetBasePool( SfxItemPool& r ) :
+ pImpl(new SfxStyleSheetBasePool_Impl),
+ rPool(r)
+{
+#ifdef DBG_UTIL
+ aDbgStyleSheetReferences.mnPools++;
+#endif
+}
+
+SfxStyleSheetBasePool::SfxStyleSheetBasePool( const SfxStyleSheetBasePool& r ) :
+ SfxBroadcaster( r ),
+ WeakImplHelper(),
+ pImpl(new SfxStyleSheetBasePool_Impl),
+ rPool(r.rPool)
+{
+#ifdef DBG_UTIL
+ aDbgStyleSheetReferences.mnPools++;
+#endif
+
+ *this += r;
+}
+
+SfxStyleSheetBasePool::~SfxStyleSheetBasePool()
+{
+#ifdef DBG_UTIL
+ aDbgStyleSheetReferences.mnPools--;
+#endif
+
+ Broadcast( SfxHint(SfxHintId::Dying) );
+ Clear();
+}
+
+std::unique_ptr<SfxStyleSheetIterator> SfxStyleSheetBasePool::CreateIterator
+(
+ SfxStyleFamily eFam,
+ SfxStyleSearchBits mask
+)
+{
+ return std::make_unique<SfxStyleSheetIterator>(this,eFam,mask);
+}
+
+rtl::Reference<SfxStyleSheetBase> SfxStyleSheetBasePool::Create
+(
+ const OUString& rName,
+ SfxStyleFamily eFam,
+ SfxStyleSearchBits mask
+)
+{
+ return new SfxStyleSheetBase( rName, this, eFam, mask );
+}
+
+rtl::Reference<SfxStyleSheetBase> SfxStyleSheetBasePool::Create( const SfxStyleSheetBase& r )
+{
+ return new SfxStyleSheetBase( r );
+}
+
+SfxStyleSheetBase& SfxStyleSheetBasePool::Make( const OUString& rName, SfxStyleFamily eFam, SfxStyleSearchBits mask)
+{
+ OSL_ENSURE( eFam != SfxStyleFamily::All, "svl::SfxStyleSheetBasePool::Make(), FamilyAll is not an allowed Family" );
+
+ SfxStyleSheetIterator aIter(this, eFam, mask);
+ rtl::Reference< SfxStyleSheetBase > xStyle( aIter.Find( rName ) );
+ OSL_ENSURE( !xStyle.is(), "svl::SfxStyleSheetBasePool::Make(), StyleSheet already exists" );
+
+ if( !xStyle.is() )
+ {
+ xStyle = Create( rName, eFam, mask );
+ StoreStyleSheet(xStyle);
+ Broadcast(SfxStyleSheetHint(SfxHintId::StyleSheetCreated, *xStyle));
+ }
+ return *xStyle;
+}
+
+/**
+ * Helper function: If a template with this name exists it is created
+ * anew. All templates that have this template as a parent are reconnected.
+ */
+void SfxStyleSheetBasePool::Add( const SfxStyleSheetBase& rSheet )
+{
+ SfxStyleSheetIterator aIter(this, rSheet.GetFamily(), SfxStyleSearchBits::All);
+ SfxStyleSheetBase* pOld = aIter.Find( rSheet.GetName() );
+ if (pOld) {
+ Remove( pOld );
+ }
+ rtl::Reference< SfxStyleSheetBase > xNew( Create( rSheet ) );
+ pImpl->mxIndexedStyleSheets->AddStyleSheet(xNew);
+ Broadcast(SfxStyleSheetHint(SfxHintId::StyleSheetChanged, *xNew));
+}
+
+SfxStyleSheetBasePool& SfxStyleSheetBasePool::operator=( const SfxStyleSheetBasePool& r )
+{
+ if( &r != this )
+ {
+ Clear();
+ *this += r;
+ }
+ return *this;
+}
+
+namespace {
+struct AddStyleSheetCallback : svl::StyleSheetCallback
+{
+ explicit AddStyleSheetCallback(SfxStyleSheetBasePool *pool)
+ : mPool(pool) {}
+
+ void DoIt(const SfxStyleSheetBase& ssheet) override
+ {
+ mPool->Add(ssheet);
+ }
+
+ SfxStyleSheetBasePool *mPool;
+};
+}
+
+SfxStyleSheetBasePool& SfxStyleSheetBasePool::operator+=( const SfxStyleSheetBasePool& r )
+{
+ if( &r != this )
+ {
+ AddStyleSheetCallback callback(this);
+ pImpl->mxIndexedStyleSheets->ApplyToAllStyleSheets(callback);
+ }
+ return *this;
+}
+
+SfxStyleSheetBase* SfxStyleSheetBasePool::Find(const OUString& rName,
+ SfxStyleFamily eFamily,
+ SfxStyleSearchBits eMask)
+{
+ SfxStyleSheetIterator aIter(this, eFamily, eMask);
+ return aIter.Find(rName);
+}
+
+SfxStyleSheetBase* SfxStyleSheetBasePool::First(SfxStyleFamily eFamily, SfxStyleSearchBits eMask)
+{
+ return GetIterator_Impl(eFamily, eMask).First();
+}
+
+SfxStyleSheetBase* SfxStyleSheetBasePool::Next()
+{
+ assert(pImpl->pIter && "Next called without a previous First");
+ return pImpl->pIter->Next();
+}
+
+void SfxStyleSheetBasePool::Remove( SfxStyleSheetBase* p )
+{
+ if( !p )
+ return;
+
+ // Reference to keep p alive until after Broadcast call!
+ rtl::Reference<SfxStyleSheetBase> xP(p);
+ bool bWasRemoved = pImpl->mxIndexedStyleSheets->RemoveStyleSheet(xP);
+ if( !bWasRemoved )
+ return;
+
+ // Adapt all styles which have this style as parent
+ ChangeParent(p->GetName(), p->GetParent(), p->GetFamily());
+
+ // #120015# Do not dispose, the removed StyleSheet may still be used in
+ // existing SdrUndoAttrObj incarnations. Rely on refcounting for disposal,
+ // this works well under normal conditions (checked breaking and counting
+ // on SfxStyleSheetBase constructors and destructors)
+
+ // css::uno::Reference< css::lang::XComponent > xComp( getXWeak((*aIter).get()), css::uno::UNO_QUERY );
+ // if( xComp.is() ) try
+ // {
+ // xComp->dispose();
+ // }
+ // catch( css::uno::Exception& )
+ // {
+ // }
+ Broadcast( SfxStyleSheetHint( SfxHintId::StyleSheetErased, *p ) );
+}
+
+void SfxStyleSheetBasePool::Insert( SfxStyleSheetBase* p )
+{
+#if OSL_DEBUG_LEVEL > 0
+ OSL_ENSURE( p, "svl::SfxStyleSheetBasePool::Insert(), no stylesheet?" );
+
+ SfxStyleSheetIterator aIter(this, p->GetFamily(), p->GetMask());
+ SfxStyleSheetBase* pOld = aIter.Find( p->GetName() );
+ OSL_ENSURE( !pOld, "svl::SfxStyleSheetBasePool::Insert(), StyleSheet already inserted" );
+ if( !p->GetParent().isEmpty() )
+ {
+ pOld = aIter.Find( p->GetParent() );
+ OSL_ENSURE( pOld, "svl::SfxStyleSheetBasePool::Insert(), Parent not found!" );
+ }
+#endif
+ StoreStyleSheet(rtl::Reference< SfxStyleSheetBase >( p ) );
+ Broadcast( SfxStyleSheetHint( SfxHintId::StyleSheetCreated, *p ) );
+}
+
+namespace
+{
+
+struct StyleSheetDisposerFunctor final : public svl::StyleSheetDisposer
+{
+ explicit StyleSheetDisposerFunctor(SfxStyleSheetBasePool* pool)
+ : mPool(pool) {}
+
+ void
+ Dispose(rtl::Reference<SfxStyleSheetBase> styleSheet) override
+ {
+ cppu::OWeakObject* weakObject = styleSheet.get();
+ css::uno::Reference< css::lang::XComponent > xComp( weakObject, css::uno::UNO_QUERY );
+ if( xComp.is() ) try
+ {
+ xComp->dispose();
+ }
+ catch( css::uno::Exception& )
+ {
+ }
+ mPool->Broadcast(SfxStyleSheetHint(SfxHintId::StyleSheetErased, *styleSheet));
+ }
+
+ SfxStyleSheetBasePool* mPool;
+};
+
+}
+
+void SfxStyleSheetBasePool::Clear()
+{
+ StyleSheetDisposerFunctor cleanup(this);
+ pImpl->mxIndexedStyleSheets->Clear(cleanup);
+}
+
+void SfxStyleSheetBasePool::ChangeParent(std::u16string_view rOld,
+ const OUString& rNew,
+ SfxStyleFamily eFamily,
+ bool bVirtual)
+{
+ for( SfxStyleSheetBase* p = First(eFamily); p; p = Next() )
+ {
+ if( p->GetParent() == rOld )
+ {
+ if(bVirtual)
+ p->SetParent( rNew );
+ else
+ p->aParent = rNew;
+ }
+ }
+}
+
+SfxStyleSheet::SfxStyleSheet(const OUString &rName,
+ const SfxStyleSheetBasePool& r_Pool,
+ SfxStyleFamily eFam,
+ SfxStyleSearchBits mask )
+ : SfxStyleSheetBase(rName, const_cast< SfxStyleSheetBasePool* >( &r_Pool ), eFam, mask)
+{
+}
+
+SfxStyleSheet::SfxStyleSheet(const SfxStyleSheet& rStyle)
+ : SfxStyleSheetBase(rStyle)
+ , SfxListener( rStyle )
+ , SfxBroadcaster( rStyle )
+ , svl::StyleSheetUser()
+{
+}
+
+SfxStyleSheet::~SfxStyleSheet()
+{
+ Broadcast( SfxStyleSheetHint( SfxHintId::StyleSheetInDestruction, *this ) );
+}
+
+
+bool SfxStyleSheet::SetParent( const OUString& rName )
+{
+ if(aParent == rName)
+ return true;
+ const OUString aOldParent(aParent);
+ if(SfxStyleSheetBase::SetParent(rName))
+ {
+ // Remove from notification chain of the old parent if applicable
+ if(!aOldParent.isEmpty())
+ {
+ SfxStyleSheet *pParent = static_cast<SfxStyleSheet *>(m_pPool->Find(aOldParent, nFamily));
+ if(pParent)
+ EndListening(*pParent);
+ }
+ // Add to the notification chain of the new parent
+ if(!aParent.isEmpty())
+ {
+ SfxStyleSheet *pParent = static_cast<SfxStyleSheet *>(m_pPool->Find(aParent, nFamily));
+ if(pParent)
+ StartListening(*pParent);
+ }
+ return true;
+ }
+ return false;
+}
+
+/**
+ * Notify all listeners
+ */
+void SfxStyleSheet::Notify(SfxBroadcaster& rBC, const SfxHint& rHint )
+{
+ Forward(rBC, rHint);
+}
+
+bool SfxStyleSheet::isUsedByModel() const
+{
+ return IsUsed();
+}
+
+
+SfxStyleSheetPool::SfxStyleSheetPool( SfxItemPool const& rSet)
+: SfxStyleSheetBasePool( const_cast< SfxItemPool& >( rSet ) )
+{
+}
+
+rtl::Reference<SfxStyleSheetBase> SfxStyleSheetPool::Create( const OUString& rName,
+ SfxStyleFamily eFam, SfxStyleSearchBits mask )
+{
+ return new SfxStyleSheet( rName, *this, eFam, mask );
+}
+
+SfxUnoStyleSheet::SfxUnoStyleSheet( const OUString& _rName, const SfxStyleSheetBasePool& _rPool, SfxStyleFamily _eFamily, SfxStyleSearchBits _nMask )
+: cppu::ImplInheritanceHelper<SfxStyleSheet, css::style::XStyle>(_rName, _rPool, _eFamily, _nMask)
+{
+}
+
+SfxUnoStyleSheet* SfxUnoStyleSheet::getUnoStyleSheet( const css::uno::Reference< css::style::XStyle >& xStyle )
+{
+ return dynamic_cast<SfxUnoStyleSheet*>(xStyle.get());
+}
+
+void
+SfxStyleSheetBasePool::StoreStyleSheet(const rtl::Reference< SfxStyleSheetBase >& xStyle)
+{
+ pImpl->mxIndexedStyleSheets->AddStyleSheet(xStyle);
+}
+
+void
+SfxStyleSheetBasePool::Reindex()
+{
+ pImpl->mxIndexedStyleSheets->Reindex();
+}
+
+const svl::IndexedStyleSheets&
+SfxStyleSheetBasePool::GetIndexedStyleSheets() const
+{
+ return *pImpl->mxIndexedStyleSheets;
+}
+
+SfxStyleSheetBase*
+SfxStyleSheetBasePool::GetStyleSheetByPositionInIndex(unsigned pos)
+{
+ return pImpl->mxIndexedStyleSheets->GetStyleSheetByPosition(pos);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/items/stylepool.cxx b/svl/source/items/stylepool.cxx
new file mode 100644
index 0000000000..9de8d87d13
--- /dev/null
+++ b/svl/source/items/stylepool.cxx
@@ -0,0 +1,468 @@
+/* -*- 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 <svl/stylepool.hxx>
+#include <svl/itemiter.hxx>
+#include <svl/itempool.hxx>
+#include <tools/debug.hxx>
+#include <algorithm>
+#include <map>
+#include <memory>
+#include <optional>
+#include <vector>
+
+namespace {
+ /** A "Node" represents a subset of inserted SfxItemSets
+ * The root node represents the empty set
+ * The other nodes contain a SfxPoolItem and represents an item set which contains their
+ * pool item and the pool items of their parents.
+ */
+ class Node
+ {
+ std::vector<std::unique_ptr<Node>> mChildren; // child nodes, create by findChildNode(..)
+ // container of shared pointers of inserted item sets; for non-poolable
+ // items more than one item set is needed
+ std::vector< std::shared_ptr<SfxItemSet> > maItemSet;
+ std::unique_ptr<const SfxPoolItem> mpItem; // my pool item
+ Node *mpUpper; // if I'm a child node that's my parent node
+ // #i86923#
+ const bool mbIsItemIgnorable;
+ public:
+ // #i86923#
+ Node() // root node Ctor
+ : mpUpper( nullptr ),
+ mbIsItemIgnorable( false )
+ {}
+ Node( const SfxPoolItem& rItem, Node* pParent, const bool bIgnorable ) // child node Ctor
+ : mpItem( rItem.Clone() ),
+ mpUpper( pParent ),
+ mbIsItemIgnorable( bIgnorable )
+ {}
+ // #i86923#
+ bool hasItemSet( const bool bCheckUsage ) const;
+ // #i87808#
+ std::shared_ptr<SfxItemSet> const & getItemSet() const
+ {
+ return maItemSet.back();
+ }
+ std::shared_ptr<SfxItemSet> const & getUsedOrLastAddedItemSet() const;
+ void setItemSet( const SfxItemSet& rSet ){ maItemSet.push_back( std::shared_ptr<SfxItemSet>( rSet.Clone() ) ); }
+ // #i86923#
+ Node* findChildNode( const SfxPoolItem& rItem,
+ const bool bIsItemIgnorable );
+ Node* nextItemSet( Node const * pLast,
+ const bool bSkipUnusedItemSet,
+ const bool bSkipIgnorable );
+ // #i86923#
+ bool hasIgnorableChildren( const bool bCheckUsage ) const;
+ std::shared_ptr<SfxItemSet> getItemSetOfIgnorableChild(
+ const bool bSkipUnusedItemSets ) const;
+ };
+
+ // #i87808#
+ std::shared_ptr<SfxItemSet> const & Node::getUsedOrLastAddedItemSet() const
+ {
+ auto aIter = std::find_if(maItemSet.rbegin(), maItemSet.rend(),
+ [](const std::shared_ptr<SfxItemSet>& rxItemSet) { return rxItemSet.use_count() > 1; });
+
+ if (aIter != maItemSet.rend())
+ return *aIter;
+
+ return maItemSet.back();
+ }
+
+ // #i86923#
+ bool Node::hasItemSet( const bool bCheckUsage ) const
+ {
+ bool bHasItemSet = false;
+
+ if ( !maItemSet.empty())
+ {
+ if ( bCheckUsage )
+ {
+ bHasItemSet = std::any_of(maItemSet.rbegin(), maItemSet.rend(),
+ [](const std::shared_ptr<SfxItemSet>& rxItemSet) { return rxItemSet.use_count() > 1; });
+ }
+ else
+ {
+ bHasItemSet = true;
+ }
+ }
+ return bHasItemSet;
+ }
+
+ // #i86923#
+ Node* Node::findChildNode( const SfxPoolItem& rItem,
+ const bool bIsItemIgnorable )
+ {
+ for( auto const & rChild : mChildren )
+ {
+ if( rItem.Which() == rChild->mpItem->Which() &&
+ rItem == *rChild->mpItem )
+ return rChild.get();
+ }
+ // #i86923#
+ auto pNextNode = new Node( rItem, this, bIsItemIgnorable );
+ mChildren.emplace_back( pNextNode );
+ return pNextNode;
+ }
+
+ /**
+ * Find the next node which has a SfxItemSet.
+ * The input parameter pLast has a sophisticated meaning:
+ * downstairs only:
+ * pLast == 0 => scan your children and their children
+ * but neither your parents neither your siblings
+ * downstairs and upstairs:
+ * pLast == this => scan your children, their children,
+ * the children of your parent behind you, and so on
+ * partial downstairs and upstairs
+ * pLast != 0 && pLast != this => scan your children behind the given children,
+ * the children of your parent behind you and so on.
+ *
+ * OD 2008-03-11 #i86923#
+ * introduce parameters <bSkipUnusedItemSets> and <bSkipIgnorable>
+ * and its handling.
+ */
+ Node* Node::nextItemSet( Node const * pLast,
+ const bool bSkipUnusedItemSets,
+ const bool bSkipIgnorable )
+ {
+ // Searching downstairs
+ auto aIter = mChildren.begin();
+ // For pLast == 0 and pLast == this all children are of interest
+ // for another pLast the search starts behind pLast...
+ if( pLast && pLast != this )
+ {
+ aIter = std::find_if( mChildren.begin(), mChildren.end(),
+ [&] (std::unique_ptr<Node> const &p) { return p.get() == pLast; });
+ if( aIter != mChildren.end() )
+ ++aIter;
+ }
+ Node *pNext = nullptr;
+ while( aIter != mChildren.end() )
+ {
+ // #i86923#
+ if ( bSkipIgnorable && (*aIter)->mbIsItemIgnorable )
+ {
+ ++aIter;
+ continue;
+ }
+ pNext = aIter->get();
+ // #i86923#
+ if ( pNext->hasItemSet( bSkipUnusedItemSets ) )
+ {
+ return pNext;
+ }
+ if ( bSkipIgnorable &&
+ pNext->hasIgnorableChildren( bSkipUnusedItemSets ) )
+ {
+ return pNext;
+ }
+ pNext = pNext->nextItemSet( nullptr, bSkipUnusedItemSets, bSkipIgnorable ); // 0 => downstairs only
+ if( pNext )
+ return pNext;
+ ++aIter;
+ }
+ // Searching upstairs
+ if( pLast && mpUpper )
+ {
+ // #i86923#
+ pNext = mpUpper->nextItemSet( this, bSkipUnusedItemSets, bSkipIgnorable );
+ }
+ return pNext;
+ }
+
+ // #i86923#
+ bool Node::hasIgnorableChildren( const bool bCheckUsage ) const
+ {
+ return std::any_of(mChildren.begin(), mChildren.end(),
+ [&bCheckUsage](const std::unique_ptr<Node>& rxChild) {
+ Node* pChild = rxChild.get();
+ return pChild->mbIsItemIgnorable &&
+ (!bCheckUsage ||
+ ( pChild->hasItemSet( bCheckUsage /* == true */ ) ||
+ pChild->hasIgnorableChildren( bCheckUsage /* == true */ ) ));
+ });
+ }
+
+ std::shared_ptr<SfxItemSet> Node::getItemSetOfIgnorableChild(
+ const bool bSkipUnusedItemSets ) const
+ {
+ DBG_ASSERT( hasIgnorableChildren( bSkipUnusedItemSets ),
+ "<Node::getItemSetOfIgnorableChild> - node has no ignorable children" );
+
+ for( const auto& rxChild : mChildren )
+ {
+ Node* pChild = rxChild.get();
+ if ( pChild->mbIsItemIgnorable )
+ {
+ if ( pChild->hasItemSet( bSkipUnusedItemSets ) )
+ {
+ return pChild->getUsedOrLastAddedItemSet();
+ }
+ else
+ {
+ pChild = pChild->nextItemSet( nullptr, bSkipUnusedItemSets, false );
+ if ( pChild )
+ {
+ return pChild->getUsedOrLastAddedItemSet();
+ }
+ }
+ }
+ }
+
+ std::shared_ptr<SfxItemSet> pReturn;
+ return pReturn;
+ }
+
+ class Iterator : public IStylePoolIteratorAccess
+ {
+ std::map< const SfxItemSet*, Node >& mrRoot;
+ std::map< const SfxItemSet*, Node >::iterator mpCurrNode;
+ Node* mpNode;
+ const bool mbSkipUnusedItemSets;
+ const bool mbSkipIgnorable;
+ /// List of item set parents, ordered by their name.
+ std::vector<const SfxItemSet*> maParents;
+ /// The iterator's current position.
+ std::vector<const SfxItemSet*>::iterator mpCurrParent;
+ public:
+ // #i86923#
+ Iterator( std::map< const SfxItemSet*, Node >& rR,
+ const bool bSkipUnusedItemSets,
+ const bool bSkipIgnorable,
+ const std::map< const SfxItemSet*, OUString>& rParentNames )
+ : mrRoot( rR ),
+ mpNode(nullptr),
+ mbSkipUnusedItemSets( bSkipUnusedItemSets ),
+ mbSkipIgnorable( bSkipIgnorable )
+ {
+ // Collect the parent pointers into a vector we can sort.
+ for (const auto& rParent : mrRoot)
+ maParents.push_back(rParent.first);
+
+ // Sort the parents using their name, if they have one.
+ if (!rParentNames.empty())
+ {
+ std::stable_sort(maParents.begin(), maParents.end(),
+ [&rParentNames](const SfxItemSet* pA, const SfxItemSet* pB) {
+ OUString aA;
+ OUString aB;
+ auto it = rParentNames.find(pA);
+ if (it != rParentNames.end())
+ aA = it->second;
+ it = rParentNames.find(pB);
+ if (it != rParentNames.end())
+ aB = it->second;
+ return aA < aB;
+ });
+ }
+
+ // Start the iteration.
+ mpCurrParent = maParents.begin();
+ if (mpCurrParent != maParents.end())
+ mpCurrNode = mrRoot.find(*mpCurrParent);
+ }
+ virtual std::shared_ptr<SfxItemSet> getNext() override;
+ };
+
+ std::shared_ptr<SfxItemSet> Iterator::getNext()
+ {
+ std::shared_ptr<SfxItemSet> pReturn;
+ while( mpNode || mpCurrParent != maParents.end() )
+ {
+ if( !mpNode )
+ {
+ mpNode = &mpCurrNode->second;
+ // Perform the actual increment.
+ ++mpCurrParent;
+ if (mpCurrParent != maParents.end())
+ mpCurrNode = mrRoot.find(*mpCurrParent);
+ // #i86923#
+ if ( mpNode->hasItemSet( mbSkipUnusedItemSets ) )
+ {
+ // #i87808#
+ return mpNode->getUsedOrLastAddedItemSet();
+ }
+ }
+ // #i86923#
+ mpNode = mpNode->nextItemSet( mpNode, mbSkipUnusedItemSets, mbSkipIgnorable );
+ if ( mpNode && mpNode->hasItemSet( mbSkipUnusedItemSets ) )
+ {
+ // #i87808#
+ return mpNode->getUsedOrLastAddedItemSet();
+ }
+ if ( mbSkipIgnorable &&
+ mpNode && mpNode->hasIgnorableChildren( mbSkipUnusedItemSets ) )
+ {
+ return mpNode->getItemSetOfIgnorableChild( mbSkipUnusedItemSets );
+ }
+ }
+ return pReturn;
+ }
+
+}
+
+/**
+ * This static method creates a unique name from a shared pointer to a SfxItemSet
+ * The name is the memory address of the SfxItemSet itself.
+ */
+OUString StylePool::nameOf( const std::shared_ptr<SfxItemSet>& pSet )
+{
+ return OUString::number( reinterpret_cast<sal_IntPtr>( pSet.get() ), 16 );
+}
+
+/**
+ * class StylePoolImpl organized a tree-structure where every node represents a SfxItemSet.
+ * The insertItemSet method adds a SfxItemSet into the tree if necessary and returns a shared_ptr
+ * to a copy of the SfxItemSet.
+ * The aRoot-Node represents an empty SfxItemSet.
+ */
+class StylePoolImpl
+{
+private:
+ std::map< const SfxItemSet*, Node > maRoot;
+ /// Names of maRoot keys.
+ std::map< const SfxItemSet*, OUString> maParentNames;
+ // #i86923#
+ std::unique_ptr<SfxItemSet> mpIgnorableItems;
+#ifdef DEBUG
+ sal_Int32 mnCount;
+#endif
+public:
+ // #i86923#
+ explicit StylePoolImpl( SfxItemSet const * pIgnorableItems )
+ :
+#ifdef DEBUG
+ mnCount(0),
+#endif
+ mpIgnorableItems( pIgnorableItems != nullptr
+ ? pIgnorableItems->Clone( false )
+ : nullptr )
+ {
+ DBG_ASSERT( !pIgnorableItems || !pIgnorableItems->Count(),
+ "<StylePoolImpl::StylePoolImpl(..)> - misusage: item set for ignorable item should be empty. Please correct usage." );
+ DBG_ASSERT( !mpIgnorableItems || !mpIgnorableItems->Count(),
+ "<StylePoolImpl::StylePoolImpl(..)> - <SfxItemSet::Clone( sal_False )> does not work as expected - <mpIgnorableItems> is not empty." );
+ }
+
+ std::shared_ptr<SfxItemSet> insertItemSet( const SfxItemSet& rSet, const OUString* pParentName = nullptr );
+
+ // #i86923#
+ std::unique_ptr<IStylePoolIteratorAccess> createIterator( bool bSkipUnusedItemSets,
+ bool bSkipIgnorableItems );
+};
+
+
+std::shared_ptr<SfxItemSet> StylePoolImpl::insertItemSet( const SfxItemSet& rSet, const OUString* pParentName )
+{
+ bool bNonShareable(false);
+ Node* pCurNode = &maRoot[ rSet.GetParent() ];
+ if (pParentName)
+ maParentNames[ rSet.GetParent() ] = *pParentName;
+ SfxItemIter aIter( rSet );
+ const SfxPoolItem* pItem = aIter.GetCurItem();
+ // Every SfxPoolItem in the SfxItemSet causes a step deeper into the tree,
+ // a complete empty SfxItemSet would stay at the root node.
+ // #i86923# insert ignorable items to the tree leaves.
+ std::optional<SfxItemSet> xFoundIgnorableItems;
+ if ( mpIgnorableItems )
+ {
+ xFoundIgnorableItems.emplace( *mpIgnorableItems );
+ }
+ while( pItem )
+ {
+ if (!rSet.GetPool()->Shareable(pItem->Which()))
+ bNonShareable = true;
+ if (!xFoundIgnorableItems || (xFoundIgnorableItems->Put(*pItem) == nullptr))
+ {
+ pCurNode = pCurNode->findChildNode( *pItem, false );
+ }
+ pItem = aIter.NextItem();
+ }
+ if ( xFoundIgnorableItems && xFoundIgnorableItems->Count() > 0 )
+ {
+ SfxItemIter aIgnorableItemsIter( *xFoundIgnorableItems );
+ pItem = aIgnorableItemsIter.GetCurItem();
+ while( pItem )
+ {
+ if (!rSet.GetPool()->Shareable(pItem->Which()))
+ bNonShareable = true;
+ pCurNode = pCurNode->findChildNode( *pItem, true );
+ pItem = aIgnorableItemsIter.NextItem();
+ }
+ }
+ // Every leaf node represents an inserted item set, but "non-leaf" nodes represents subsets
+ // of inserted itemsets.
+ // These nodes could have but does not need to have a shared_ptr to an item set.
+ if( !pCurNode->hasItemSet( false ) )
+ {
+ pCurNode->setItemSet( rSet );
+ bNonShareable = false; // to avoid a double insertion
+#ifdef DEBUG
+ ++mnCount;
+#endif
+ }
+ // If rSet contains at least one non poolable item, a new itemset has to be inserted
+ if( bNonShareable )
+ pCurNode->setItemSet( rSet );
+#ifdef DEBUG
+ {
+ sal_Int32 nCheck = -1;
+ std::unique_ptr<IStylePoolIteratorAccess> pIter = createIterator(false,false);
+ std::shared_ptr<SfxItemSet> pTemp;
+ do
+ {
+ ++nCheck;
+ pTemp = pIter->getNext();
+ } while( pTemp.get() );
+ DBG_ASSERT( mnCount == nCheck, "Wrong counting");
+ }
+#endif
+ return pCurNode->getItemSet();
+}
+
+// #i86923#
+std::unique_ptr<IStylePoolIteratorAccess> StylePoolImpl::createIterator( bool bSkipUnusedItemSets,
+ bool bSkipIgnorableItems )
+{
+ return std::make_unique<Iterator>( maRoot, bSkipUnusedItemSets, bSkipIgnorableItems, maParentNames );
+}
+// Ctor, Dtor and redirected methods of class StylePool, nearly inline ;-)
+
+// #i86923#
+StylePool::StylePool( SfxItemSet const * pIgnorableItems )
+ : pImpl( new StylePoolImpl( pIgnorableItems ) )
+{}
+
+std::shared_ptr<SfxItemSet> StylePool::insertItemSet( const SfxItemSet& rSet, const OUString* pParentName )
+{ return pImpl->insertItemSet( rSet, pParentName ); }
+
+// #i86923#
+std::unique_ptr<IStylePoolIteratorAccess> StylePool::createIterator( const bool bSkipUnusedItemSets,
+ const bool bSkipIgnorableItems )
+{
+ return pImpl->createIterator( bSkipUnusedItemSets, bSkipIgnorableItems );
+}
+
+StylePool::~StylePool()
+{}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/items/visitem.cxx b/svl/source/items/visitem.cxx
new file mode 100644
index 0000000000..c1788c32d1
--- /dev/null
+++ b/svl/source/items/visitem.cxx
@@ -0,0 +1,71 @@
+/* -*- 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 <svl/visitem.hxx>
+#include <com/sun/star/uno/Any.hxx>
+#include <osl/diagnose.h>
+
+
+// virtual
+bool SfxVisibilityItem::operator ==(const SfxPoolItem & rItem) const
+{
+ assert(SfxPoolItem::operator==(rItem));
+ return m_nValue.bVisible == static_cast< const SfxVisibilityItem * >(&rItem)->
+ m_nValue.bVisible;
+}
+
+// virtual
+bool SfxVisibilityItem::GetPresentation(SfxItemPresentation,
+ MapUnit, MapUnit,
+ OUString & rText,
+ const IntlWrapper&) const
+{
+ rText = m_nValue.bVisible ? std::u16string_view(u"TRUE") : std::u16string_view(u"FALSE");
+ return true;
+}
+
+
+// virtual
+bool SfxVisibilityItem::QueryValue(css::uno::Any& rVal, sal_uInt8) const
+{
+ rVal <<= m_nValue;
+ return true;
+}
+
+// virtual
+bool SfxVisibilityItem::PutValue(const css::uno::Any& rVal, sal_uInt8)
+{
+ if (rVal >>= m_nValue)
+ return true;
+
+ OSL_FAIL( "SfxInt16Item::PutValue - Wrong type!" );
+ return false;
+}
+
+// virtual
+SfxVisibilityItem* SfxVisibilityItem::Clone(SfxItemPool *) const
+{
+ return new SfxVisibilityItem(*this);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/items/voiditem.cxx b/svl/source/items/voiditem.cxx
new file mode 100644
index 0000000000..32057e1e2c
--- /dev/null
+++ b/svl/source/items/voiditem.cxx
@@ -0,0 +1,70 @@
+/* -*- 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 <svl/voiditem.hxx>
+#include <libxml/xmlwriter.h>
+
+SfxPoolItem* SfxVoidItem::CreateDefault() { return new SfxVoidItem(0); }
+
+SfxVoidItem::SfxVoidItem(sal_uInt16 which)
+ : SfxPoolItem(which)
+{
+ setIsVoidItem();
+}
+
+SfxVoidItem::SfxVoidItem(const SfxVoidItem& rCopy)
+ : SfxPoolItem(rCopy.Which())
+{
+ setIsVoidItem();
+}
+
+SfxVoidItem::SfxVoidItem(SfxVoidItem&& rOrig)
+ : SfxPoolItem(rOrig)
+{
+ setIsVoidItem();
+}
+
+bool SfxVoidItem::operator==(const SfxPoolItem& rCmp) const
+{
+ assert(SfxPoolItem::operator==(rCmp));
+ (void)rCmp;
+ return true;
+}
+
+bool SfxVoidItem::GetPresentation(SfxItemPresentation /*ePresentation*/, MapUnit /*eCoreMetric*/,
+ MapUnit /*ePresentationMetric*/, OUString& rText,
+ const IntlWrapper&) const
+{
+ rText = "Void";
+ return true;
+}
+
+void SfxVoidItem::dumpAsXml(xmlTextWriterPtr pWriter) const
+{
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SfxVoidItem"));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("whichId"),
+ BAD_CAST(OString::number(Which()).getStr()));
+ (void)xmlTextWriterEndElement(pWriter);
+}
+
+SfxVoidItem* SfxVoidItem::Clone(SfxItemPool*) const { return new SfxVoidItem(*this); }
+
+SfxVoidItem::~SfxVoidItem() {}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/items/whiter.cxx b/svl/source/items/whiter.cxx
new file mode 100644
index 0000000000..13915415df
--- /dev/null
+++ b/svl/source/items/whiter.cxx
@@ -0,0 +1,93 @@
+/* -*- 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 <svl/itemset.hxx>
+#include <svl/whiter.hxx>
+
+SfxWhichIter::SfxWhichIter(const SfxItemSet& rSet)
+ : m_rItemSet(rSet)
+ , m_pCurrentWhichPair(rSet.m_pWhichRanges.begin())
+ , m_nOffsetFromStartOfCurrentWhichPair(0)
+ , m_nItemsOffset(0)
+{
+}
+
+sal_uInt16 SfxWhichIter::GetCurWhich() const
+{
+ const WhichRangesContainer& rWhichRanges = m_rItemSet.m_pWhichRanges;
+ if (m_pCurrentWhichPair >= (rWhichRanges.begin() + rWhichRanges.size()))
+ return 0;
+ return m_pCurrentWhichPair->first + m_nOffsetFromStartOfCurrentWhichPair;
+}
+
+sal_uInt16 SfxWhichIter::NextWhich()
+{
+ const WhichRangesContainer& rWhichRanges = m_rItemSet.m_pWhichRanges;
+ if (m_pCurrentWhichPair >= (rWhichRanges.begin() + rWhichRanges.size()))
+ return 0;
+
+ const sal_uInt16 nLastWhich = m_pCurrentWhichPair->first + m_nOffsetFromStartOfCurrentWhichPair;
+ ++m_nOffsetFromStartOfCurrentWhichPair;
+ if (m_pCurrentWhichPair->second == nLastWhich)
+ {
+ m_nItemsOffset += m_pCurrentWhichPair->second - m_pCurrentWhichPair->first + 1;
+ ++m_pCurrentWhichPair;
+ m_nOffsetFromStartOfCurrentWhichPair = 0;
+ }
+ if (m_pCurrentWhichPair >= (rWhichRanges.begin() + rWhichRanges.size()))
+ return 0;
+ return m_pCurrentWhichPair->first + m_nOffsetFromStartOfCurrentWhichPair;
+}
+
+sal_uInt16 SfxWhichIter::FirstWhich()
+{
+ m_pCurrentWhichPair = m_rItemSet.m_pWhichRanges.begin();
+ m_nOffsetFromStartOfCurrentWhichPair = 0;
+ m_nItemsOffset = 0;
+ return m_pCurrentWhichPair->first;
+}
+
+SfxItemState SfxWhichIter::GetItemState(bool bSrchInParent, const SfxPoolItem** ppItem) const
+{
+ const sal_uInt16 nOffset(m_nItemsOffset + m_nOffsetFromStartOfCurrentWhichPair);
+
+ // we have the offset, so use it to profit. It is always valid, so no need
+ // to check if smaller than TotalCount()
+ SfxItemState eState(m_rItemSet.GetItemState_ForOffset(nOffset, ppItem));
+
+ // search in parent?
+ if (bSrchInParent && nullptr != m_rItemSet.GetParent() && (SfxItemState::UNKNOWN == eState || SfxItemState::DEFAULT == eState))
+ {
+ // nOffset was only valid for *local* SfxItemSet, need to continue with WhichID
+ // Use the *highest* SfxItemState as result
+ const sal_uInt16 nWhich(m_pCurrentWhichPair->first + m_nOffsetFromStartOfCurrentWhichPair);
+ return m_rItemSet.GetParent()->GetItemState_ForWhichID( eState, nWhich, true, ppItem);
+ }
+
+ return eState;
+}
+
+void SfxWhichIter::ClearItem()
+{
+ // we have the offset, so use it to profit. It is always valid, so no need
+ // to check if smaller than TotalCount()
+ const_cast<SfxItemSet&>(m_rItemSet).ClearSingleItem_ForOffset(m_nItemsOffset + m_nOffsetFromStartOfCurrentWhichPair);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/misc/PasswordHelper.cxx b/svl/source/misc/PasswordHelper.cxx
new file mode 100644
index 0000000000..cfae72f649
--- /dev/null
+++ b/svl/source/misc/PasswordHelper.cxx
@@ -0,0 +1,180 @@
+/* -*- 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 <svl/PasswordHelper.hxx>
+#include <comphelper/hash.hxx>
+#include <rtl/digest.h>
+#include <memory>
+#include <unicode/regex.h>
+#include <unicode/unistr.h>
+#include <unicode/errorcode.h>
+#include <zxcvbn.h>
+#include <sal/log.hxx>
+
+using namespace com::sun::star;
+
+void SvPasswordHelper::GetHashPasswordSHA256(uno::Sequence<sal_Int8>& rPassHash, std::u16string_view rPassword)
+{
+ OString const tmp(OUStringToOString(rPassword, RTL_TEXTENCODING_UTF8));
+ ::std::vector<unsigned char> const hash(::comphelper::Hash::calculateHash(
+ reinterpret_cast<unsigned char const*>(tmp.getStr()), tmp.getLength(),
+ ::comphelper::HashType::SHA256));
+ rPassHash.realloc(hash.size());
+ ::std::copy(hash.begin(), hash.end(), rPassHash.getArray());
+ rtl_secureZeroMemory(const_cast<char *>(tmp.getStr()), tmp.getLength());
+}
+
+void SvPasswordHelper::GetHashPasswordSHA1UTF8(uno::Sequence<sal_Int8>& rPassHash, std::u16string_view rPassword)
+{
+ OString const tmp(OUStringToOString(rPassword, RTL_TEXTENCODING_UTF8));
+ ::std::vector<unsigned char> const hash(::comphelper::Hash::calculateHash(
+ reinterpret_cast<unsigned char const*>(tmp.getStr()), tmp.getLength(),
+ ::comphelper::HashType::SHA1));
+ rPassHash.realloc(hash.size());
+ ::std::copy(hash.begin(), hash.end(), rPassHash.getArray());
+ rtl_secureZeroMemory(const_cast<char *>(tmp.getStr()), tmp.getLength());
+}
+
+void SvPasswordHelper::GetHashPassword(uno::Sequence<sal_Int8>& rPassHash, const char* pPass, sal_uInt32 nLen)
+{
+ rPassHash.realloc(RTL_DIGEST_LENGTH_SHA1);
+
+ rtlDigestError aError = rtl_digest_SHA1 (pPass, nLen, reinterpret_cast<sal_uInt8*>(rPassHash.getArray()), rPassHash.getLength());
+ if (aError != rtl_Digest_E_None)
+ {
+ rPassHash.realloc(0);
+ }
+}
+
+void SvPasswordHelper::GetHashPasswordLittleEndian(uno::Sequence<sal_Int8>& rPassHash, std::u16string_view sPass)
+{
+ sal_Int32 nSize(sPass.size());
+ std::unique_ptr<char[]> pCharBuffer(new char[nSize * sizeof(sal_Unicode)]);
+
+ for (sal_Int32 i = 0; i < nSize; ++i)
+ {
+ sal_Unicode ch(sPass[ i ]);
+ pCharBuffer[2 * i] = static_cast< char >(ch & 0xFF);
+ pCharBuffer[2 * i + 1] = static_cast< char >(ch >> 8);
+ }
+
+ GetHashPassword(rPassHash, pCharBuffer.get(), nSize * sizeof(sal_Unicode));
+ rtl_secureZeroMemory(pCharBuffer.get(), nSize * sizeof(sal_Unicode));
+}
+
+void SvPasswordHelper::GetHashPasswordBigEndian(uno::Sequence<sal_Int8>& rPassHash, std::u16string_view sPass)
+{
+ sal_Int32 nSize(sPass.size());
+ std::unique_ptr<char[]> pCharBuffer(new char[nSize * sizeof(sal_Unicode)]);
+
+ for (sal_Int32 i = 0; i < nSize; ++i)
+ {
+ sal_Unicode ch(sPass[ i ]);
+ pCharBuffer[2 * i] = static_cast< char >(ch >> 8);
+ pCharBuffer[2 * i + 1] = static_cast< char >(ch & 0xFF);
+ }
+
+ GetHashPassword(rPassHash, pCharBuffer.get(), nSize * sizeof(sal_Unicode));
+ rtl_secureZeroMemory(pCharBuffer.get(), nSize * sizeof(sal_Unicode));
+}
+
+void SvPasswordHelper::GetHashPassword(uno::Sequence<sal_Int8>& rPassHash, std::u16string_view sPass)
+{
+ GetHashPasswordLittleEndian(rPassHash, sPass);
+}
+
+bool SvPasswordHelper::CompareHashPassword(const uno::Sequence<sal_Int8>& rOldPassHash, std::u16string_view sNewPass)
+{
+ bool bResult = false;
+
+ if (rOldPassHash.getLength() == RTL_DIGEST_LENGTH_SHA1)
+ {
+ uno::Sequence<sal_Int8> aNewPass(RTL_DIGEST_LENGTH_SHA1);
+ GetHashPasswordSHA1UTF8(aNewPass, sNewPass);
+ if (aNewPass == rOldPassHash)
+ {
+ bResult = true;
+ }
+ else
+ {
+ GetHashPasswordLittleEndian(aNewPass, sNewPass);
+ if (aNewPass == rOldPassHash)
+ bResult = true;
+ else
+ {
+ GetHashPasswordBigEndian(aNewPass, sNewPass);
+ bResult = (aNewPass == rOldPassHash);
+ }
+ }
+ }
+ else if (rOldPassHash.getLength() == 32)
+ {
+ uno::Sequence<sal_Int8> aNewPass;
+ GetHashPasswordSHA256(aNewPass, sNewPass);
+ bResult = aNewPass == rOldPassHash;
+ }
+
+ return bResult;
+}
+
+double SvPasswordHelper::GetPasswordStrengthPercentage(const char* pPassword)
+{
+ // Entropy bits corresponding to 100% password strength
+ static constexpr double fMaxPassStrengthEntorpyBits = 112.0;
+ return std::min(100.0,
+ ZxcvbnMatch(pPassword, nullptr, nullptr) * 100.0 / fMaxPassStrengthEntorpyBits);
+}
+
+double SvPasswordHelper::GetPasswordStrengthPercentage(const OUString& aPassword)
+{
+ OString aPasswordUtf8 = aPassword.toUtf8();
+ return GetPasswordStrengthPercentage(aPasswordUtf8.getStr());
+}
+
+bool SvPasswordHelper::PasswordMeetsPolicy(const char* pPassword,
+ const std::optional<OUString>& oPasswordPolicy)
+{
+ if (oPasswordPolicy)
+ {
+ icu::ErrorCode aStatus;
+ icu::UnicodeString sPassword(pPassword);
+ icu::UnicodeString sRegex(oPasswordPolicy->getStr());
+ icu::RegexMatcher aRegexMatcher(sRegex, sPassword, 0, aStatus);
+
+ if (aRegexMatcher.matches(aStatus))
+ return true;
+
+ SAL_WARN_IF(
+ aStatus.isFailure(), "svl.misc",
+ "Password policy regular expression failed with error: " << aStatus.errorName());
+
+ return false;
+ }
+ return true;
+}
+
+bool SvPasswordHelper::PasswordMeetsPolicy(const OUString& aPassword,
+ const std::optional<OUString>& oPasswordPolicy)
+{
+ OString aPasswordUtf8 = aPassword.toUtf8();
+ return PasswordMeetsPolicy(aPasswordUtf8.getStr(), oPasswordPolicy);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/misc/adrparse.cxx b/svl/source/misc/adrparse.cxx
new file mode 100644
index 0000000000..19e869a092
--- /dev/null
+++ b/svl/source/misc/adrparse.cxx
@@ -0,0 +1,556 @@
+/* -*- 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 <svl/adrparse.hxx>
+
+namespace
+{
+
+enum ElementType { ELEMENT_START, ELEMENT_DELIM, ELEMENT_ITEM, ELEMENT_END };
+
+struct ParsedAddrSpec
+{
+ sal_Unicode const * m_pBegin;
+ sal_Unicode const * m_pEnd;
+ ElementType m_eLastElem;
+ bool m_bAtFound;
+ bool m_bReparse;
+
+ ParsedAddrSpec() { reset(); }
+
+ bool isPoorlyValid() const { return m_eLastElem >= ELEMENT_ITEM; }
+
+ bool isValid() const { return isPoorlyValid() && m_bAtFound; }
+
+ void reset();
+
+ void finish();
+};
+
+void ParsedAddrSpec::reset()
+{
+ m_pBegin = nullptr;
+ m_pEnd = nullptr;
+ m_eLastElem = ELEMENT_START;
+ m_bAtFound = false;
+ m_bReparse = false;
+}
+
+void ParsedAddrSpec::finish()
+{
+ if (isPoorlyValid())
+ m_eLastElem = ELEMENT_END;
+ else
+ reset();
+}
+
+}
+
+class SvAddressParser_Impl
+{
+ enum State { BEFORE_COLON, BEFORE_LESS, AFTER_LESS, AFTER_GREATER };
+
+ enum TokenType: sal_uInt32 {
+ TOKEN_QUOTED = 0x80000000, TOKEN_DOMAIN, TOKEN_COMMENT, TOKEN_ATOM };
+
+ sal_Unicode const * m_pInputPos;
+ sal_Unicode const * m_pInputEnd;
+ sal_uInt32 m_nCurToken;
+ sal_Unicode const * m_pCurTokenBegin;
+ sal_Unicode const * m_pCurTokenEnd;
+ ParsedAddrSpec m_aOuterAddrSpec;
+ ParsedAddrSpec m_aInnerAddrSpec;
+ ParsedAddrSpec * m_pAddrSpec;
+ State m_eState;
+ TokenType m_eType;
+
+ inline void reset();
+
+ void addTokenToAddrSpec(ElementType eTokenElem);
+
+ bool readToken();
+
+ static OUString reparse(sal_Unicode const * pBegin,
+ sal_Unicode const * pEnd);
+
+public:
+ SvAddressParser_Impl(SvAddressParser * pParser, const OUString& rIn);
+};
+
+inline void SvAddressParser_Impl::reset()
+{
+ m_aOuterAddrSpec.reset();
+ m_aInnerAddrSpec.reset();
+ m_pAddrSpec = &m_aOuterAddrSpec;
+ m_eState = BEFORE_COLON;
+ m_eType = TOKEN_ATOM;
+}
+
+void SvAddressParser_Impl::addTokenToAddrSpec(ElementType eTokenElem)
+{
+ if (!m_pAddrSpec->m_pBegin)
+ m_pAddrSpec->m_pBegin = m_pCurTokenBegin;
+ else if (m_pAddrSpec->m_pEnd < m_pCurTokenBegin)
+ m_pAddrSpec->m_bReparse = true;
+ m_pAddrSpec->m_pEnd = m_pCurTokenEnd;
+ m_pAddrSpec->m_eLastElem = eTokenElem;
+}
+
+
+// SvAddressParser_Impl
+
+
+bool SvAddressParser_Impl::readToken()
+{
+ m_nCurToken = m_eType;
+ switch (m_eType)
+ {
+ case TOKEN_QUOTED:
+ {
+ m_pCurTokenBegin = m_pInputPos - 1;
+ bool bEscaped = false;
+ for (;;)
+ {
+ if (m_pInputPos >= m_pInputEnd)
+ return false;
+ sal_Unicode cChar = *m_pInputPos++;
+ if (bEscaped)
+ {
+ bEscaped = false;
+ }
+ else if (cChar == '"')
+ {
+ m_pCurTokenEnd = m_pInputPos;
+ return true;
+ }
+ else if (cChar == '\\')
+ bEscaped = true;
+ }
+ }
+
+ case TOKEN_DOMAIN:
+ {
+ m_pCurTokenBegin = m_pInputPos - 1;
+ bool bEscaped = false;
+ for (;;)
+ {
+ if (m_pInputPos >= m_pInputEnd)
+ return false;
+ sal_Unicode cChar = *m_pInputPos++;
+ if (bEscaped)
+ bEscaped = false;
+ else if (cChar == ']')
+ {
+ m_pCurTokenEnd = m_pInputPos;
+ return true;
+ }
+ else if (cChar == '\\')
+ bEscaped = true;
+ }
+ }
+
+ case TOKEN_COMMENT:
+ {
+ m_pCurTokenBegin = m_pInputPos - 1;
+ bool bEscaped = false;
+ int nLevel = 0;
+ for (;;)
+ {
+ if (m_pInputPos >= m_pInputEnd)
+ return false;
+ sal_Unicode cChar = *m_pInputPos++;
+ if (bEscaped)
+ {
+ bEscaped = false;
+ }
+ else if (cChar == '(')
+ {
+ ++nLevel;
+ }
+ else if (cChar == ')')
+ if (nLevel)
+ {
+ --nLevel;
+ }
+ else
+ return true;
+ else if (cChar == '\\')
+ {
+ bEscaped = true;
+ }
+ }
+ }
+
+ default:
+ {
+ sal_Unicode cChar;
+ for (;;)
+ {
+ if (m_pInputPos >= m_pInputEnd)
+ return false;
+ cChar = *m_pInputPos++;
+ if (cChar > ' ' && cChar != 0x7F) // DEL
+ break;
+ }
+ m_pCurTokenBegin = m_pInputPos - 1;
+ if (cChar == '"' || cChar == '(' || cChar == ')' || cChar == ','
+ || cChar == '.' || cChar == ':' || cChar == ';'
+ || cChar == '<' || cChar == '>' || cChar == '@'
+ || cChar == '[' || cChar == '\\' || cChar == ']')
+ {
+ m_nCurToken = cChar;
+ m_pCurTokenEnd = m_pInputPos;
+ return true;
+ }
+ else
+ for (;;)
+ {
+ if (m_pInputPos >= m_pInputEnd)
+ {
+ m_pCurTokenEnd = m_pInputPos;
+ return true;
+ }
+ cChar = *m_pInputPos++;
+ if (cChar <= ' ' || cChar == '"' || cChar == '('
+ || cChar == ')' || cChar == ',' || cChar == '.'
+ || cChar == ':' || cChar == ';' || cChar == '<'
+ || cChar == '>' || cChar == '@' || cChar == '['
+ || cChar == '\\' || cChar == ']'
+ || cChar == 0x7F) // DEL
+ {
+ m_pCurTokenEnd = --m_pInputPos;
+ return true;
+ }
+ }
+ }
+ }
+}
+
+// static
+OUString SvAddressParser_Impl::reparse(sal_Unicode const * pBegin,
+ sal_Unicode const * pEnd)
+{
+ OUStringBuffer aResult;
+ TokenType eMode = TOKEN_ATOM;
+ bool bEscaped = false;
+ int nLevel = 0;
+ while (pBegin < pEnd)
+ {
+ sal_Unicode cChar = *pBegin++;
+ switch (eMode)
+ {
+ case TOKEN_QUOTED:
+ if (bEscaped)
+ {
+ aResult.append(cChar);
+ bEscaped = false;
+ }
+ else if (cChar == '"')
+ {
+ aResult.append(cChar);
+ eMode = TOKEN_ATOM;
+ }
+ else if (cChar == '\\')
+ {
+ aResult.append(cChar);
+ bEscaped = true;
+ }
+ else
+ aResult.append(cChar);
+ break;
+
+ case TOKEN_DOMAIN:
+ if (bEscaped)
+ {
+ aResult.append(cChar);
+ bEscaped = false;
+ }
+ else if (cChar == ']')
+ {
+ aResult.append(cChar);
+ eMode = TOKEN_ATOM;
+ }
+ else if (cChar == '\\')
+ {
+ aResult.append(cChar);
+ bEscaped = true;
+ }
+ else
+ aResult.append(cChar);
+ break;
+
+ case TOKEN_COMMENT:
+ if (bEscaped)
+ bEscaped = false;
+ else if (cChar == '(')
+ ++nLevel;
+ else if (cChar == ')')
+ if (nLevel)
+ --nLevel;
+ else
+ eMode = TOKEN_ATOM;
+ else if (cChar == '\\')
+ bEscaped = true;
+ break;
+
+ case TOKEN_ATOM:
+ if (cChar <= ' ' || cChar == 0x7F) // DEL
+ {
+ }
+ else if (cChar == '(')
+ {
+ eMode = TOKEN_COMMENT;
+ }
+ else
+ {
+ if (cChar == '"')
+ {
+ aResult.append(cChar);
+ eMode = TOKEN_QUOTED;
+ }
+ else if (cChar == '[')
+ {
+ aResult.append(cChar);
+ eMode = TOKEN_QUOTED;
+ }
+ else
+ aResult.append(cChar);
+ }
+ break;
+ }
+ }
+ return aResult.makeStringAndClear();
+}
+
+SvAddressParser_Impl::SvAddressParser_Impl(SvAddressParser * pParser,
+ const OUString& rInput)
+ : m_pCurTokenBegin(nullptr)
+ , m_pCurTokenEnd(nullptr)
+{
+ m_pInputPos = rInput.getStr();
+ m_pInputEnd = m_pInputPos + rInput.getLength();
+
+ reset();
+ bool bDone = false;
+ for (;;)
+ {
+ if (!readToken())
+ {
+ if (m_eState == AFTER_LESS)
+ m_nCurToken = '>';
+ else
+ {
+ m_nCurToken = ',';
+ bDone = true;
+ }
+ }
+ switch (m_nCurToken)
+ {
+ case TOKEN_QUOTED:
+ if (m_pAddrSpec->m_eLastElem != ELEMENT_END)
+ {
+ if (m_pAddrSpec->m_bAtFound
+ || m_pAddrSpec->m_eLastElem <= ELEMENT_DELIM)
+ m_pAddrSpec->reset();
+ addTokenToAddrSpec(ELEMENT_ITEM);
+ }
+ m_eType = TOKEN_ATOM;
+ break;
+
+ case TOKEN_DOMAIN:
+ if (m_pAddrSpec->m_eLastElem != ELEMENT_END)
+ {
+ if (m_pAddrSpec->m_bAtFound && m_pAddrSpec->m_eLastElem == ELEMENT_DELIM)
+ addTokenToAddrSpec(ELEMENT_ITEM);
+ else
+ m_pAddrSpec->reset();
+ }
+ m_eType = TOKEN_ATOM;
+ break;
+
+ case TOKEN_COMMENT:
+ m_eType = TOKEN_ATOM;
+ break;
+
+ case TOKEN_ATOM:
+ if (m_pAddrSpec->m_eLastElem != ELEMENT_END)
+ {
+ if (m_pAddrSpec->m_eLastElem != ELEMENT_DELIM)
+ m_pAddrSpec->reset();
+ addTokenToAddrSpec(ELEMENT_ITEM);
+ }
+ break;
+
+ case '(':
+ m_eType = TOKEN_COMMENT;
+ break;
+
+ case ')':
+ case '\\':
+ case ']':
+ m_pAddrSpec->finish();
+ break;
+
+ case '<':
+ switch (m_eState)
+ {
+ case BEFORE_COLON:
+ case BEFORE_LESS:
+ m_aOuterAddrSpec.finish();
+ m_pAddrSpec = &m_aInnerAddrSpec;
+ m_eState = AFTER_LESS;
+ break;
+
+ case AFTER_LESS:
+ m_aInnerAddrSpec.finish();
+ break;
+
+ case AFTER_GREATER:
+ m_aOuterAddrSpec.finish();
+ break;
+ }
+ break;
+
+ case '>':
+ if (m_eState == AFTER_LESS)
+ {
+ m_aInnerAddrSpec.finish();
+ if (m_aInnerAddrSpec.isValid())
+ m_aOuterAddrSpec.m_eLastElem = ELEMENT_END;
+ m_pAddrSpec = &m_aOuterAddrSpec;
+ m_eState = AFTER_GREATER;
+ }
+ else
+ {
+ m_aOuterAddrSpec.finish();
+ }
+ break;
+
+ case '@':
+ if (m_pAddrSpec->m_eLastElem != ELEMENT_END)
+ {
+ if (!m_pAddrSpec->m_bAtFound
+ && m_pAddrSpec->m_eLastElem == ELEMENT_ITEM)
+ {
+ addTokenToAddrSpec(ELEMENT_DELIM);
+ m_pAddrSpec->m_bAtFound = true;
+ }
+ else
+ m_pAddrSpec->reset();
+ }
+ break;
+
+ case ',':
+ case ';':
+ if (m_eState == AFTER_LESS)
+ if (m_nCurToken == ',')
+ {
+ if (m_aInnerAddrSpec.m_eLastElem != ELEMENT_END)
+ m_aInnerAddrSpec.reset();
+ }
+ else
+ m_aInnerAddrSpec.finish();
+ else
+ {
+ if(m_aInnerAddrSpec.isValid() || (!m_aOuterAddrSpec.isValid() && m_aInnerAddrSpec.isPoorlyValid()))
+ {
+ m_pAddrSpec = &m_aInnerAddrSpec;
+ }
+ else if(m_aOuterAddrSpec.isPoorlyValid())
+ {
+ m_pAddrSpec = &m_aOuterAddrSpec;
+ }
+ else
+ {
+ m_pAddrSpec = nullptr;
+ }
+
+ if (m_pAddrSpec)
+ {
+ OUString aTheAddrSpec;
+ if (m_pAddrSpec->m_bReparse)
+ aTheAddrSpec = reparse(m_pAddrSpec->m_pBegin, m_pAddrSpec->m_pEnd);
+ else
+ {
+ sal_Int32 nLen = m_pAddrSpec->m_pEnd - m_pAddrSpec->m_pBegin;
+ if (nLen == rInput.getLength())
+ aTheAddrSpec = rInput;
+ else
+ aTheAddrSpec = rInput.copy( (m_pAddrSpec->m_pBegin - rInput.getStr()),
+ nLen);
+ }
+ pParser->m_vAddresses.emplace_back( aTheAddrSpec );
+ }
+ if (bDone)
+ return;
+ reset();
+ }
+ break;
+
+ case ':':
+ switch (m_eState)
+ {
+ case BEFORE_COLON:
+ m_aOuterAddrSpec.reset();
+ m_eState = BEFORE_LESS;
+ break;
+
+ case BEFORE_LESS:
+ case AFTER_GREATER:
+ m_aOuterAddrSpec.finish();
+ break;
+
+ case AFTER_LESS:
+ m_aInnerAddrSpec.reset();
+ break;
+ }
+ break;
+
+ case '"':
+ m_eType = TOKEN_QUOTED;
+ break;
+
+ case '.':
+ if (m_pAddrSpec->m_eLastElem != ELEMENT_END)
+ {
+ if (m_pAddrSpec->m_eLastElem != ELEMENT_DELIM)
+ addTokenToAddrSpec(ELEMENT_DELIM);
+ else
+ m_pAddrSpec->reset();
+ }
+ break;
+
+ case '[':
+ m_eType = TOKEN_DOMAIN;
+ break;
+ }
+ }
+}
+
+SvAddressParser::SvAddressParser(const OUString& rInput)
+{
+ SvAddressParser_Impl aDoParse(this, rInput);
+}
+
+SvAddressParser::~SvAddressParser()
+{
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/misc/documentlockfile.cxx b/svl/source/misc/documentlockfile.cxx
new file mode 100644
index 0000000000..31cbd19693
--- /dev/null
+++ b/svl/source/misc/documentlockfile.cxx
@@ -0,0 +1,226 @@
+/* -*- 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/ucb/XCommandEnvironment.hpp>
+#include <com/sun/star/ucb/InsertCommandArgument.hpp>
+#include <com/sun/star/ucb/NameClashException.hpp>
+#include <com/sun/star/io/WrongFormatException.hpp>
+#include <com/sun/star/io/TempFile.hpp>
+#include <com/sun/star/io/XStream.hpp>
+#include <com/sun/star/io/XInputStream.hpp>
+#include <com/sun/star/io/XOutputStream.hpp>
+#include <com/sun/star/io/XSeekable.hpp>
+#include <com/sun/star/io/XTruncate.hpp>
+
+#include <o3tl/enumrange.hxx>
+
+#include <rtl/string.hxx>
+#include <rtl/ustring.hxx>
+#include <rtl/ustrbuf.hxx>
+
+#include <comphelper/processfactory.hxx>
+
+#include <ucbhelper/content.hxx>
+
+#include <svl/documentlockfile.hxx>
+
+using namespace ::com::sun::star;
+
+namespace svt {
+
+GenDocumentLockFile::GenDocumentLockFile(const OUString& aLockFileURL)
+ : LockFileCommon(aLockFileURL)
+{
+}
+
+
+GenDocumentLockFile::~GenDocumentLockFile()
+{
+}
+
+uno::Reference< io::XInputStream > GenDocumentLockFile::OpenStream(std::unique_lock<std::mutex>& /*rGuard*/)
+{
+ uno::Reference < css::ucb::XCommandEnvironment > xEnv;
+ ::ucbhelper::Content aSourceContent( GetURL(), xEnv, comphelper::getProcessComponentContext() );
+
+ // the file can be opened readonly, no locking will be done
+ return aSourceContent.openStream();
+}
+
+bool GenDocumentLockFile::CreateOwnLockFile()
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ try
+ {
+ uno::Reference< io::XStream > xTempFile(
+ io::TempFile::create( comphelper::getProcessComponentContext() ),
+ uno::UNO_QUERY_THROW );
+ uno::Reference< io::XSeekable > xSeekable( xTempFile, uno::UNO_QUERY_THROW );
+
+ uno::Reference< io::XInputStream > xInput = xTempFile->getInputStream();
+ uno::Reference< io::XOutputStream > xOutput = xTempFile->getOutputStream();
+
+ if ( !xInput.is() || !xOutput.is() )
+ throw uno::RuntimeException();
+
+ LockFileEntry aNewEntry = GenerateOwnEntry();
+ WriteEntryToStream( aGuard, aNewEntry, xOutput );
+ xOutput->closeOutput();
+
+ xSeekable->seek( 0 );
+
+ uno::Reference < css::ucb::XCommandEnvironment > xEnv;
+ ::ucbhelper::Content aTargetContent( GetURL(), xEnv, comphelper::getProcessComponentContext() );
+
+ ucb::InsertCommandArgument aInsertArg;
+ aInsertArg.Data = xInput;
+ aInsertArg.ReplaceExisting = false;
+ uno::Any aCmdArg;
+ aCmdArg <<= aInsertArg;
+ aTargetContent.executeCommand( "insert", aCmdArg );
+
+ // try to let the file be hidden if possible
+ try {
+ aTargetContent.setPropertyValue("IsHidden", uno::Any( true ) );
+ } catch( uno::Exception& ) {}
+ }
+ catch( ucb::NameClashException& )
+ {
+ return false;
+ }
+
+ return true;
+}
+
+bool GenDocumentLockFile::OverwriteOwnLockFile()
+{
+ std::unique_lock aGuard(m_aMutex);
+
+ // allows to overwrite the lock file with the current data
+ try
+ {
+ uno::Reference < css::ucb::XCommandEnvironment > xEnv;
+ ::ucbhelper::Content aTargetContent( GetURL(), xEnv, comphelper::getProcessComponentContext() );
+
+ LockFileEntry aNewEntry = GenerateOwnEntry();
+
+ uno::Reference< io::XStream > xStream = aTargetContent.openWriteableStreamNoLock();
+ uno::Reference< io::XOutputStream > xOutput = xStream->getOutputStream();
+ uno::Reference< io::XTruncate > xTruncate( xOutput, uno::UNO_QUERY_THROW );
+
+ xTruncate->truncate();
+ WriteEntryToStream( aGuard, aNewEntry, xOutput );
+ xOutput->closeOutput();
+ }
+ catch( uno::Exception& )
+ {
+ return false;
+ }
+
+ return true;
+}
+
+void GenDocumentLockFile::RemoveFile()
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ // TODO/LATER: the removing is not atomic, is it possible in general to make it atomic?
+ LockFileEntry aNewEntry = GenerateOwnEntry();
+ LockFileEntry aFileData = GetLockDataImpl(aGuard);
+
+ if ( aFileData[LockFileComponent::SYSUSERNAME] != aNewEntry[LockFileComponent::SYSUSERNAME]
+ || aFileData[LockFileComponent::LOCALHOST] != aNewEntry[LockFileComponent::LOCALHOST]
+ || aFileData[LockFileComponent::USERURL] != aNewEntry[LockFileComponent::USERURL] )
+ throw io::IOException(); // not the owner, access denied
+
+ RemoveFileDirectly();
+}
+
+void GenDocumentLockFile::RemoveFileDirectly()
+{
+ uno::Reference < css::ucb::XCommandEnvironment > xEnv;
+ ::ucbhelper::Content aCnt(GetURL(), xEnv, comphelper::getProcessComponentContext());
+ aCnt.executeCommand("delete",
+ uno::Any(true));
+}
+
+LockFileEntry GenDocumentLockFile::GetLockData()
+{
+ std::unique_lock aGuard(m_aMutex);
+ return GetLockDataImpl(aGuard);
+}
+
+DocumentLockFile::DocumentLockFile( std::u16string_view aOrigURL )
+ : GenDocumentLockFile(GenerateOwnLockFileURL(aOrigURL, u".~lock."))
+{
+}
+
+
+DocumentLockFile::~DocumentLockFile()
+{
+}
+
+
+void DocumentLockFile::WriteEntryToStream(
+ std::unique_lock<std::mutex>& /*rGuard*/,
+ const LockFileEntry& aEntry, const uno::Reference< io::XOutputStream >& xOutput )
+{
+ OUStringBuffer aBuffer(256);
+
+ for ( LockFileComponent lft : o3tl::enumrange<LockFileComponent>() )
+ {
+ aBuffer.append( EscapeCharacters( aEntry[lft] ) );
+ if ( lft < LockFileComponent::LAST )
+ aBuffer.append( ',' );
+ else
+ aBuffer.append( ';' );
+ }
+
+ OString aStringData( OUStringToOString( aBuffer, RTL_TEXTENCODING_UTF8 ) );
+ uno::Sequence< sal_Int8 > aData( reinterpret_cast<sal_Int8 const *>(aStringData.getStr()), aStringData.getLength() );
+ xOutput->writeBytes( aData );
+}
+
+LockFileEntry DocumentLockFile::GetLockDataImpl(std::unique_lock<std::mutex>& rGuard)
+{
+ uno::Reference< io::XInputStream > xInput = OpenStream(rGuard);
+ if ( !xInput.is() )
+ throw uno::RuntimeException();
+
+ const sal_Int32 nBufLen = 32000;
+ uno::Sequence< sal_Int8 > aBuffer( nBufLen );
+
+ sal_Int32 nRead = xInput->readBytes( aBuffer, nBufLen );
+ xInput->closeInput();
+
+ if ( nRead == nBufLen )
+ throw io::WrongFormatException();
+
+ sal_Int32 nCurPos = 0;
+ return ParseEntry( aBuffer, nCurPos );
+}
+
+
+
+
+} // namespace svt
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/misc/filenotation.cxx b/svl/source/misc/filenotation.cxx
new file mode 100644
index 0000000000..c4337708bc
--- /dev/null
+++ b/svl/source/misc/filenotation.cxx
@@ -0,0 +1,122 @@
+/* -*- 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 <svl/filenotation.hxx>
+#include <osl/file.h>
+#include <osl/diagnose.h>
+#include <tools/urlobj.hxx>
+
+namespace svt
+{
+
+ OFileNotation::OFileNotation( const OUString& _rUrlOrPath )
+ {
+ construct( _rUrlOrPath );
+ }
+
+ OFileNotation::OFileNotation( const OUString& _rUrlOrPath, NOTATION _eInputNotation )
+ {
+ if ( _eInputNotation == N_URL )
+ {
+ INetURLObject aParser( _rUrlOrPath );
+ if ( aParser.GetProtocol() == INetProtocol::File )
+ implInitWithURLNotation( _rUrlOrPath );
+ else
+ m_sSystem = m_sFileURL = _rUrlOrPath;
+ }
+ else
+ implInitWithSystemNotation( _rUrlOrPath );
+ }
+
+ bool OFileNotation::implInitWithSystemNotation( const OUString& _rSystemPath )
+ {
+ bool bSuccess = false;
+
+ m_sSystem = _rSystemPath;
+ if ( ( osl_File_E_None != osl_getFileURLFromSystemPath( m_sSystem.pData, &m_sFileURL.pData ) )
+ && ( m_sFileURL.isEmpty() )
+ )
+ {
+ if ( !_rSystemPath.isEmpty() )
+ {
+ INetURLObject aSmartParser;
+ aSmartParser.SetSmartProtocol( INetProtocol::File );
+ if ( aSmartParser.SetSmartURL( _rSystemPath ) )
+ {
+ m_sFileURL = aSmartParser.GetMainURL( INetURLObject::DecodeMechanism::NONE );
+ osl_getSystemPathFromFileURL( m_sFileURL.pData, &m_sSystem.pData );
+ bSuccess = true;
+ }
+ }
+ }
+ else
+ bSuccess = true;
+ return bSuccess;
+ }
+
+ void OFileNotation::implInitWithURLNotation( const OUString& _rURL )
+ {
+ m_sFileURL = _rURL;
+ osl_getSystemPathFromFileURL( _rURL.pData, &m_sSystem.pData );
+ }
+
+ void OFileNotation::construct( const OUString& _rUrlOrPath )
+ {
+ bool bSuccess = false;
+ // URL notation?
+ INetURLObject aParser( _rUrlOrPath );
+ switch ( aParser.GetProtocol() )
+ {
+ case INetProtocol::File:
+ // file URL
+ implInitWithURLNotation( _rUrlOrPath );
+ bSuccess = true;
+ break;
+
+ case INetProtocol::NotValid:
+ // assume system notation
+ bSuccess = implInitWithSystemNotation( _rUrlOrPath );
+ break;
+
+ default:
+ // it's a known scheme, but no file-URL -> assume that both the URL representation and the
+ // system representation are the URL itself
+ m_sSystem = m_sFileURL = _rUrlOrPath;
+ bSuccess = true;
+ break;
+ }
+
+ OSL_ENSURE( bSuccess, "OFileNotation::OFileNotation: could not detect the format!" );
+ }
+
+ OUString OFileNotation::get(NOTATION _eOutputNotation) const
+ {
+ switch (_eOutputNotation)
+ {
+ case N_SYSTEM: return m_sSystem;
+ case N_URL: return m_sFileURL;
+ }
+
+ OSL_FAIL("OFileNotation::get: invalid enum value!");
+ return OUString();
+ }
+
+} // namespace svt
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/misc/fstathelper.cxx b/svl/source/misc/fstathelper.cxx
new file mode 100644
index 0000000000..6530ff7244
--- /dev/null
+++ b/svl/source/misc/fstathelper.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 <com/sun/star/util/DateTime.hpp>
+#include <comphelper/processfactory.hxx>
+#include <o3tl/any.hxx>
+#include <rtl/ustring.hxx>
+#include <svl/fstathelper.hxx>
+#include <tools/date.hxx>
+#include <tools/time.hxx>
+#include <ucbhelper/content.hxx>
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::ucb;
+
+bool FStatHelper::GetModifiedDateTimeOfFile( const OUString& rURL,
+ Date* pDate, tools::Time* pTime )
+{
+ bool bRet = false;
+ try
+ {
+ ::ucbhelper::Content aTestContent( rURL,
+ uno::Reference< XCommandEnvironment > (),
+ comphelper::getProcessComponentContext());
+ uno::Any aAny = aTestContent.getPropertyValue(
+ "DateModified" );
+ if( aAny.hasValue() )
+ {
+ bRet = true;
+ auto pDT = o3tl::doAccess<util::DateTime>(aAny);
+ if( pDate )
+ *pDate = Date( pDT->Day, pDT->Month, pDT->Year );
+ if( pTime )
+ *pTime = tools::Time( pDT->Hours, pDT->Minutes,
+ pDT->Seconds, pDT->NanoSeconds );
+ }
+ }
+ catch(...)
+ {
+ }
+
+ return bRet;
+}
+
+bool FStatHelper::IsDocument( const OUString& rURL )
+{
+ bool bExist = false;
+ try
+ {
+ ::ucbhelper::Content aTestContent( rURL,
+ uno::Reference< XCommandEnvironment > (),
+ comphelper::getProcessComponentContext());
+ bExist = aTestContent.isDocument();
+ }
+ catch(...)
+ {
+ }
+ return bExist;
+}
+
+bool FStatHelper::IsFolder( const OUString& rURL )
+{
+ bool bExist = false;
+ try
+ {
+ ::ucbhelper::Content aTestContent( rURL,
+ uno::Reference< XCommandEnvironment > (),
+ comphelper::getProcessComponentContext());
+ bExist = aTestContent.isFolder();
+ }
+ catch(...)
+ {
+ }
+ return bExist;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/misc/getstringresource.cxx b/svl/source/misc/getstringresource.cxx
new file mode 100644
index 0000000000..6b066c24da
--- /dev/null
+++ b/svl/source/misc/getstringresource.cxx
@@ -0,0 +1,31 @@
+/* -*- 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/ustring.hxx>
+#include <svl/svlresid.hxx>
+#include <unotools/resmgr.hxx>
+
+OUString SvlResId(TranslateId sContextAndId)
+{
+ return Translate::get(sContextAndId, Translate::Create("svl"));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/misc/gridprinter.cxx b/svl/source/misc/gridprinter.cxx
new file mode 100644
index 0000000000..c6a1bdd5f9
--- /dev/null
+++ b/svl/source/misc/gridprinter.cxx
@@ -0,0 +1,133 @@
+/* -*- 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 <svl/gridprinter.hxx>
+#include <rtl/ustrbuf.hxx>
+
+#include <mdds/multi_type_vector/types.hpp>
+#include <mdds/multi_type_vector/macro.hpp>
+#include <mdds/multi_type_matrix.hpp>
+
+#include <iostream>
+
+namespace svl {
+
+// String ID
+const mdds::mtv::element_t element_type_string = mdds::mtv::element_type_user_start;
+// String block
+typedef mdds::mtv::default_element_block<element_type_string, OUString> string_block;
+
+namespace {
+
+struct matrix_traits
+{
+ typedef string_block string_element_block;
+ typedef mdds::mtv::uint16_element_block integer_element_block;
+};
+
+}
+
+}
+
+namespace rtl {
+
+// Callbacks for the string block. This needs to be in the same namespace as
+// OUString for argument dependent lookup.
+MDDS_MTV_DEFINE_ELEMENT_CALLBACKS(OUString, svl::element_type_string, OUString(), svl::string_block)
+
+}
+
+namespace svl {
+
+typedef mdds::multi_type_matrix<matrix_traits> MatrixImplType;
+
+struct GridPrinter::Impl
+{
+ MatrixImplType maMatrix;
+ bool mbPrint;
+
+ Impl( size_t nRows, size_t nCols, bool bPrint ) :
+ maMatrix(nRows, nCols, OUString()), mbPrint(bPrint) {}
+};
+
+GridPrinter::GridPrinter( size_t nRows, size_t nCols, bool bPrint ) :
+ mpImpl(new Impl(nRows, nCols, bPrint)) {}
+
+GridPrinter::~GridPrinter()
+{
+}
+
+void GridPrinter::set( size_t nRow, size_t nCol, const OUString& rStr )
+{
+#if defined __GNUC__ && !defined __clang__ && __GNUC__ == 12 && __cplusplus == 202002L
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Warray-bounds"
+#endif
+ mpImpl->maMatrix.set(nRow, nCol, rStr);
+#if defined __GNUC__ && !defined __clang__ && __GNUC__ == 12 && __cplusplus == 202002L
+#pragma GCC diagnostic pop
+#endif
+}
+
+void GridPrinter::print( const char* pHeader ) const
+{
+ if (!mpImpl->mbPrint)
+ return;
+
+ if (pHeader)
+ std::cout << pHeader << std::endl;
+
+ MatrixImplType::size_pair_type ns = mpImpl->maMatrix.size();
+ std::vector<sal_Int32> aColWidths(ns.column, 0);
+
+ // Calculate column widths first.
+ for (size_t row = 0; row < ns.row; ++row)
+ {
+ for (size_t col = 0; col < ns.column; ++col)
+ {
+ OUString aStr = mpImpl->maMatrix.get_string(row, col);
+ if (aColWidths[col] < aStr.getLength())
+ aColWidths[col] = aStr.getLength();
+ }
+ }
+
+ // Make the row separator string.
+ OUStringBuffer aBuf("+");
+ for (size_t col = 0; col < ns.column; ++col)
+ {
+ aBuf.append("-");
+ for (sal_Int32 i = 0; i < aColWidths[col]; ++i)
+ aBuf.append(u'-');
+ aBuf.append("-+");
+ }
+
+ OUString aSep = aBuf.makeStringAndClear();
+
+ // Now print to stdout.
+ std::cout << aSep << std::endl;
+ for (size_t row = 0; row < ns.row; ++row)
+ {
+ std::cout << "| ";
+ for (size_t col = 0; col < ns.column; ++col)
+ {
+ OUString aStr = mpImpl->maMatrix.get_string(row, col);
+ size_t nPadding = aColWidths[col] - aStr.getLength();
+ aBuf.append(aStr);
+ for (size_t i = 0; i < nPadding; ++i)
+ aBuf.append(u' ');
+ std::cout << aBuf.makeStringAndClear() << " | ";
+ }
+ std::cout << std::endl;
+ std::cout << aSep << std::endl;
+ }
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/misc/inethist.cxx b/svl/source/misc/inethist.cxx
new file mode 100644
index 0000000000..dc25d6b0b5
--- /dev/null
+++ b/svl/source/misc/inethist.cxx
@@ -0,0 +1,371 @@
+/* -*- 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 <svl/inethist.hxx>
+
+#include <algorithm>
+#include <string.h>
+
+#include <rtl/crc.h>
+#include <tools/debug.hxx>
+#include <tools/urlobj.hxx>
+
+/*
+ * INetURLHistory internals.
+ */
+#define INETHIST_DEF_FTP_PORT 21
+#define INETHIST_DEF_HTTP_PORT 80
+#define INETHIST_DEF_HTTPS_PORT 443
+
+#define INETHIST_SIZE_LIMIT 1024
+#define INETHIST_MAGIC_HEAD 0x484D4849UL
+
+class INetURLHistory_Impl
+{
+ struct head_entry
+ {
+ /** Representation.
+ */
+ sal_uInt32 m_nMagic;
+ sal_uInt16 m_nNext;
+
+ /** Initialization.
+ */
+ void initialize()
+ {
+ m_nMagic = INETHIST_MAGIC_HEAD;
+ m_nNext = 0;
+ }
+ };
+
+ struct hash_entry
+ {
+ /** Representation.
+ */
+ sal_uInt32 m_nHash;
+ sal_uInt16 m_nLru;
+
+ /** Initialization.
+ */
+ void initialize (sal_uInt16 nLru)
+ {
+ m_nHash = 0;
+ m_nLru = nLru;
+ }
+
+ /** Comparison.
+ */
+ bool operator== (sal_uInt32 nHash) const
+ {
+ return (m_nHash == nHash);
+ }
+ bool operator< (sal_uInt32 nHash) const
+ {
+ return (m_nHash < nHash);
+ }
+ };
+
+ struct lru_entry
+ {
+ /** Representation.
+ */
+ sal_uInt32 m_nHash;
+ sal_uInt16 m_nNext;
+ sal_uInt16 m_nPrev;
+
+ /** Initialization.
+ */
+ void initialize (sal_uInt16 nThis)
+ {
+ m_nHash = 0;
+ m_nNext = nThis;
+ m_nPrev = nThis;
+ }
+ };
+
+ /** Representation.
+ */
+ head_entry m_aHead;
+ hash_entry m_pHash[INETHIST_SIZE_LIMIT];
+ lru_entry m_pList[INETHIST_SIZE_LIMIT];
+
+ /** Initialization.
+ */
+ void initialize();
+
+ static sal_uInt16 capacity()
+ {
+ return sal_uInt16(INETHIST_SIZE_LIMIT);
+ }
+
+ static sal_uInt32 crc32 (OUString const & rData)
+ {
+ return rtl_crc32 (0, rData.getStr(), rData.getLength() * sizeof(sal_Unicode));
+ }
+
+ sal_uInt16 find (sal_uInt32 nHash) const;
+
+ void move (sal_uInt16 nSI, sal_uInt16 nDI);
+
+ void backlink (sal_uInt16 nThis, sal_uInt16 nTail)
+ {
+ lru_entry &rThis = m_pList[nThis];
+ lru_entry &rTail = m_pList[nTail];
+
+ rTail.m_nNext = nThis;
+ rTail.m_nPrev = rThis.m_nPrev;
+ rThis.m_nPrev = nTail;
+ m_pList[rTail.m_nPrev].m_nNext = nTail;
+ }
+
+ void unlink (sal_uInt16 nThis)
+ {
+ lru_entry &rThis = m_pList[nThis];
+
+ m_pList[rThis.m_nPrev].m_nNext = rThis.m_nNext;
+ m_pList[rThis.m_nNext].m_nPrev = rThis.m_nPrev;
+ rThis.m_nNext = nThis;
+ rThis.m_nPrev = nThis;
+ }
+
+public:
+ INetURLHistory_Impl();
+ INetURLHistory_Impl(const INetURLHistory_Impl&) = delete;
+ INetURLHistory_Impl& operator=(const INetURLHistory_Impl&) = delete;
+
+ /** putUrl/queryUrl.
+ */
+ void putUrl (const OUString &rUrl);
+ bool queryUrl (const OUString &rUrl) const;
+};
+
+INetURLHistory_Impl::INetURLHistory_Impl()
+{
+ initialize();
+}
+
+void INetURLHistory_Impl::initialize()
+{
+ m_aHead.initialize();
+
+ sal_uInt16 i, n = capacity();
+ for (i = 0; i < n; i++)
+ m_pHash[i].initialize(i);
+ for (i = 0; i < n; i++)
+ m_pList[i].initialize(i);
+ for (i = 1; i < n; i++)
+ backlink (m_aHead.m_nNext, i);
+}
+
+sal_uInt16 INetURLHistory_Impl::find (sal_uInt32 nHash) const
+{
+ sal_uInt16 l = 0;
+ sal_uInt16 r = capacity() - 1;
+ sal_uInt16 c = capacity();
+
+ while ((l < r) && (r < c))
+ {
+ sal_uInt16 m = (l + r) / 2;
+ if (m_pHash[m] == nHash)
+ return m;
+
+ if (m_pHash[m] < nHash)
+ l = m + 1;
+ else
+ r = m - 1;
+ }
+ return l;
+}
+
+void INetURLHistory_Impl::move (sal_uInt16 nSI, sal_uInt16 nDI)
+{
+ hash_entry e = m_pHash[nSI];
+ if (nSI < nDI)
+ {
+ // shift left.
+ memmove (
+ &m_pHash[nSI ],
+ &m_pHash[nSI + 1],
+ (nDI - nSI) * sizeof(hash_entry));
+ }
+ if (nSI > nDI)
+ {
+ // shift right.
+ memmove (
+ &m_pHash[nDI + 1],
+ &m_pHash[nDI ],
+ (nSI - nDI) * sizeof(hash_entry));
+ }
+ m_pHash[nDI] = e;
+}
+
+void INetURLHistory_Impl::putUrl (const OUString &rUrl)
+{
+ sal_uInt32 h = crc32 (rUrl);
+ sal_uInt16 k = find (h);
+ if ((k < capacity()) && (m_pHash[k] == h))
+ {
+ // Cache hit.
+ sal_uInt16 nMRU = m_pHash[k].m_nLru;
+ if (nMRU != m_aHead.m_nNext)
+ {
+ // Update LRU chain.
+ unlink (nMRU);
+ backlink (m_aHead.m_nNext, nMRU);
+
+ // Rotate LRU chain.
+ m_aHead.m_nNext = m_pList[m_aHead.m_nNext].m_nPrev;
+ }
+ }
+ else
+ {
+ // Cache miss. Obtain least recently used.
+ sal_uInt16 nLRU = m_pList[m_aHead.m_nNext].m_nPrev;
+
+ sal_uInt16 nSI = find (m_pList[nLRU].m_nHash);
+ if (nLRU != m_pHash[nSI].m_nLru)
+ {
+ // Update LRU chain.
+ nLRU = m_pHash[nSI].m_nLru;
+ unlink (nLRU);
+ backlink (m_aHead.m_nNext, nLRU);
+ }
+
+ // Rotate LRU chain.
+ m_aHead.m_nNext = m_pList[m_aHead.m_nNext].m_nPrev;
+
+ // Check source and destination.
+ sal_uInt16 nDI = std::min (k, sal_uInt16(capacity() - 1));
+ if (nSI < nDI && !(m_pHash[nDI] < h))
+ nDI -= 1;
+ if (nDI < nSI && m_pHash[nDI] < h)
+ nDI += 1;
+
+ // Assign data.
+ m_pList[m_aHead.m_nNext].m_nHash = m_pHash[nSI].m_nHash = h;
+ move (nSI, nDI);
+ }
+}
+
+bool INetURLHistory_Impl::queryUrl (const OUString &rUrl) const
+{
+ sal_uInt32 h = crc32 (rUrl);
+ sal_uInt16 k = find (h);
+ // true if cache hit
+ return (k < capacity()) && (m_pHash[k] == h);
+}
+
+INetURLHistory::INetURLHistory() : m_pImpl (new INetURLHistory_Impl())
+{
+}
+
+INetURLHistory::~INetURLHistory()
+{
+}
+
+/*
+ * GetOrCreate.
+ */
+INetURLHistory* INetURLHistory::GetOrCreate()
+{
+ static INetURLHistory instance;
+ return &instance;
+}
+
+void INetURLHistory::NormalizeUrl_Impl (INetURLObject &rUrl)
+{
+ switch (rUrl.GetProtocol())
+ {
+ case INetProtocol::File:
+ if (!INetURLObject::IsCaseSensitive())
+ {
+ OUString aPath (rUrl.GetURLPath(INetURLObject::DecodeMechanism::NONE).toAsciiLowerCase());
+ rUrl.SetURLPath (aPath, INetURLObject::EncodeMechanism::NotCanonical);
+ }
+ break;
+
+ case INetProtocol::Ftp:
+ if (!rUrl.HasPort())
+ rUrl.SetPort (INETHIST_DEF_FTP_PORT);
+ break;
+
+ case INetProtocol::Http:
+ if (!rUrl.HasPort())
+ rUrl.SetPort (INETHIST_DEF_HTTP_PORT);
+ if (!rUrl.HasURLPath())
+ rUrl.SetURLPath(u"/");
+ break;
+
+ case INetProtocol::Https:
+ if (!rUrl.HasPort())
+ rUrl.SetPort (INETHIST_DEF_HTTPS_PORT);
+ if (!rUrl.HasURLPath())
+ rUrl.SetURLPath(u"/");
+ break;
+
+ default:
+ break;
+ }
+}
+
+void INetURLHistory::PutUrl_Impl (const INetURLObject &rUrl)
+{
+ DBG_ASSERT (m_pImpl, "PutUrl_Impl(): no Implementation");
+ if (!m_pImpl)
+ return;
+
+ INetURLObject aHistUrl (rUrl);
+ NormalizeUrl_Impl (aHistUrl);
+
+ m_pImpl->putUrl (aHistUrl.GetMainURL(INetURLObject::DecodeMechanism::NONE));
+ Broadcast (INetURLHistoryHint (&rUrl));
+
+ if (aHistUrl.HasMark())
+ {
+ aHistUrl.SetURL (aHistUrl.GetURLNoMark(INetURLObject::DecodeMechanism::NONE),
+ INetURLObject::EncodeMechanism::NotCanonical);
+
+ m_pImpl->putUrl (aHistUrl.GetMainURL(INetURLObject::DecodeMechanism::NONE));
+ Broadcast (INetURLHistoryHint (&aHistUrl));
+ }
+}
+
+bool INetURLHistory::QueryUrl(std::u16string_view rUrl) const
+{
+ INetProtocol eProto = INetURLObject::CompareProtocolScheme (rUrl);
+ if (!QueryProtocol (eProto))
+ return false;
+ return QueryUrl_Impl( INetURLObject(rUrl) );
+}
+
+
+bool INetURLHistory::QueryUrl_Impl (INetURLObject rUrl) const
+{
+ DBG_ASSERT (m_pImpl, "QueryUrl_Impl(): no Implementation");
+ if (m_pImpl)
+ {
+ NormalizeUrl_Impl (rUrl);
+
+ return m_pImpl->queryUrl (rUrl.GetMainURL(INetURLObject::DecodeMechanism::NONE));
+ }
+ return false;
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/misc/inettype.cxx b/svl/source/misc/inettype.cxx
new file mode 100644
index 0000000000..da6c86f5d9
--- /dev/null
+++ b/svl/source/misc/inettype.cxx
@@ -0,0 +1,443 @@
+/* -*- 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 <array>
+
+#include <tools/debug.hxx>
+#include <tools/wldcrd.hxx>
+#include <tools/inetmime.hxx>
+#include <o3tl/string_view.hxx>
+#include <osl/diagnose.h>
+#include <svl/inettype.hxx>
+
+namespace
+{
+
+struct MediaTypeEntry
+{
+ OUString m_pTypeName;
+ INetContentType m_eTypeID;
+};
+
+
+MediaTypeEntry const * seekEntry(OUString const & rTypeName,
+ MediaTypeEntry const * pMap, std::size_t nSize);
+
+/** A mapping from type names to type ids and extensions. Sorted by type
+ name.
+ */
+MediaTypeEntry const aStaticTypeNameMap[CONTENT_TYPE_LAST + 1]
+ = { { " ", CONTENT_TYPE_UNKNOWN },
+ { CONTENT_TYPE_STR_X_CNT_FSYSBOX, CONTENT_TYPE_X_CNT_FSYSBOX },
+ { CONTENT_TYPE_STR_X_CNT_FSYSFOLDER, CONTENT_TYPE_X_CNT_FSYSFOLDER },
+ { CONTENT_TYPE_STR_X_CNT_FSYSSPECIALFOLDER, CONTENT_TYPE_X_CNT_FSYSSPECIALFOLDER },
+ { CONTENT_TYPE_STR_APP_OCTSTREAM, CONTENT_TYPE_APP_OCTSTREAM },
+ { CONTENT_TYPE_STR_APP_PDF, CONTENT_TYPE_APP_PDF },
+ { CONTENT_TYPE_STR_APP_RTF, CONTENT_TYPE_APP_RTF },
+ { CONTENT_TYPE_STR_APP_VND_CALC, CONTENT_TYPE_APP_VND_CALC },
+ { CONTENT_TYPE_STR_APP_VND_CHART, CONTENT_TYPE_APP_VND_CHART },
+ { CONTENT_TYPE_STR_APP_VND_DRAW, CONTENT_TYPE_APP_VND_DRAW },
+ { CONTENT_TYPE_STR_APP_VND_IMAGE, CONTENT_TYPE_APP_VND_IMAGE },
+ { CONTENT_TYPE_STR_APP_VND_IMPRESS, CONTENT_TYPE_APP_VND_IMPRESS },
+ { CONTENT_TYPE_STR_APP_VND_IMPRESSPACKED, CONTENT_TYPE_APP_VND_IMPRESSPACKED },
+ { CONTENT_TYPE_STR_APP_VND_MAIL, CONTENT_TYPE_APP_VND_MAIL },
+ { CONTENT_TYPE_STR_APP_VND_MATH, CONTENT_TYPE_APP_VND_MATH },
+ { CONTENT_TYPE_STR_APP_VND_NEWS, CONTENT_TYPE_APP_VND_NEWS },
+ { CONTENT_TYPE_STR_APP_VND_OUTTRAY, CONTENT_TYPE_APP_VND_OUTTRAY },
+ { CONTENT_TYPE_STR_APP_VND_TEMPLATE, CONTENT_TYPE_APP_VND_TEMPLATE },
+ { CONTENT_TYPE_STR_APP_VND_WRITER, CONTENT_TYPE_APP_VND_WRITER },
+ { CONTENT_TYPE_STR_APP_VND_WRITER_GLOBAL, CONTENT_TYPE_APP_VND_WRITER_GLOBAL },
+ { CONTENT_TYPE_STR_APP_VND_WRITER_WEB, CONTENT_TYPE_APP_VND_WRITER_WEB },
+ { CONTENT_TYPE_STR_APP_VND_SUN_XML_CALC, CONTENT_TYPE_APP_VND_SUN_XML_CALC },
+ { CONTENT_TYPE_STR_APP_VND_SUN_XML_CHART, CONTENT_TYPE_APP_VND_SUN_XML_CHART },
+ { CONTENT_TYPE_STR_APP_VND_SUN_XML_DRAW, CONTENT_TYPE_APP_VND_SUN_XML_DRAW },
+ { CONTENT_TYPE_STR_APP_VND_SUN_XML_IMPRESS, CONTENT_TYPE_APP_VND_SUN_XML_IMPRESS },
+ { CONTENT_TYPE_STR_APP_VND_SUN_XML_IMPRESSPACKED, CONTENT_TYPE_APP_VND_SUN_XML_IMPRESSPACKED },
+ { CONTENT_TYPE_STR_APP_VND_SUN_XML_MATH, CONTENT_TYPE_APP_VND_SUN_XML_MATH },
+ { CONTENT_TYPE_STR_APP_VND_SUN_XML_WRITER, CONTENT_TYPE_APP_VND_SUN_XML_WRITER },
+ { CONTENT_TYPE_STR_APP_VND_SUN_XML_WRITER_GLOBAL, CONTENT_TYPE_APP_VND_SUN_XML_WRITER_GLOBAL },
+ { CONTENT_TYPE_STR_APP_FRAMESET, CONTENT_TYPE_APP_FRAMESET },
+ { CONTENT_TYPE_STR_APP_GALLERY, CONTENT_TYPE_APP_GALLERY },
+ { CONTENT_TYPE_STR_APP_GALLERY_THEME, CONTENT_TYPE_APP_GALLERY_THEME },
+ { CONTENT_TYPE_STR_APP_JAR, CONTENT_TYPE_APP_JAR },
+ { CONTENT_TYPE_STR_APP_MACRO, CONTENT_TYPE_APP_MACRO },
+ { CONTENT_TYPE_STR_APP_MSEXCEL, CONTENT_TYPE_APP_MSEXCEL },
+ { CONTENT_TYPE_STR_APP_MSEXCEL_TEMPL, CONTENT_TYPE_APP_MSEXCEL_TEMPL },
+ { CONTENT_TYPE_STR_APP_MSPPOINT, CONTENT_TYPE_APP_MSPPOINT },
+ { CONTENT_TYPE_STR_APP_MSPPOINT_TEMPL, CONTENT_TYPE_APP_MSPPOINT_TEMPL },
+ { CONTENT_TYPE_STR_APP_MSWORD, CONTENT_TYPE_APP_MSWORD },
+ { CONTENT_TYPE_STR_APP_MSWORD_TEMPL, CONTENT_TYPE_APP_MSWORD_TEMPL },
+ { CONTENT_TYPE_STR_APP_STARCALC, CONTENT_TYPE_APP_STARCALC },
+ { CONTENT_TYPE_STR_APP_STARCHART, CONTENT_TYPE_APP_STARCHART },
+ { CONTENT_TYPE_STR_APP_STARDRAW, CONTENT_TYPE_APP_STARDRAW },
+ { CONTENT_TYPE_STR_APP_STARHELP, CONTENT_TYPE_APP_STARHELP },
+ { CONTENT_TYPE_STR_APP_STARIMAGE, CONTENT_TYPE_APP_STARIMAGE },
+ { CONTENT_TYPE_STR_APP_STARIMPRESS, CONTENT_TYPE_APP_STARIMPRESS },
+ { CONTENT_TYPE_STR_APP_STARMAIL_SDM, CONTENT_TYPE_APP_STARMAIL_SDM },
+ { CONTENT_TYPE_STR_APP_STARMAIL_SMD, CONTENT_TYPE_APP_STARMAIL_SMD },
+ { CONTENT_TYPE_STR_APP_STARMATH, CONTENT_TYPE_APP_STARMATH },
+ { CONTENT_TYPE_STR_APP_STARWRITER, CONTENT_TYPE_APP_STARWRITER },
+ { CONTENT_TYPE_STR_APP_STARWRITER_GLOB, CONTENT_TYPE_APP_STARWRITER_GLOB },
+ { CONTENT_TYPE_STR_APP_CDE_CALENDAR_APP, CONTENT_TYPE_APP_CDE_CALENDAR_APP },
+ { CONTENT_TYPE_STR_APP_ZIP, CONTENT_TYPE_APP_ZIP },
+ { CONTENT_TYPE_STR_AUDIO_AIFF, CONTENT_TYPE_AUDIO_AIFF },
+ { CONTENT_TYPE_STR_AUDIO_BASIC, CONTENT_TYPE_AUDIO_BASIC },
+ { CONTENT_TYPE_STR_AUDIO_MIDI, CONTENT_TYPE_AUDIO_MIDI },
+ { CONTENT_TYPE_STR_AUDIO_VORBIS, CONTENT_TYPE_AUDIO_VORBIS },
+ { CONTENT_TYPE_STR_AUDIO_WAV, CONTENT_TYPE_AUDIO_WAV },
+ { CONTENT_TYPE_STR_AUDIO_WEBM, CONTENT_TYPE_AUDIO_WEBM },
+ { CONTENT_TYPE_STR_IMAGE_GENERIC, CONTENT_TYPE_IMAGE_GENERIC },
+ { CONTENT_TYPE_STR_IMAGE_GIF, CONTENT_TYPE_IMAGE_GIF },
+ { CONTENT_TYPE_STR_IMAGE_JPEG, CONTENT_TYPE_IMAGE_JPEG },
+ { CONTENT_TYPE_STR_IMAGE_PCX, CONTENT_TYPE_IMAGE_PCX },
+ { CONTENT_TYPE_STR_IMAGE_PNG, CONTENT_TYPE_IMAGE_PNG },
+ { CONTENT_TYPE_STR_IMAGE_TIFF, CONTENT_TYPE_IMAGE_TIFF },
+ { CONTENT_TYPE_STR_IMAGE_BMP, CONTENT_TYPE_IMAGE_BMP },
+ { CONTENT_TYPE_STR_INET_MSG_RFC822, CONTENT_TYPE_INET_MESSAGE_RFC822 },
+ { CONTENT_TYPE_STR_INET_MULTI_ALTERNATIVE, CONTENT_TYPE_INET_MULTIPART_ALTERNATIVE },
+ { CONTENT_TYPE_STR_INET_MULTI_DIGEST, CONTENT_TYPE_INET_MULTIPART_DIGEST },
+ { CONTENT_TYPE_STR_INET_MULTI_MIXED, CONTENT_TYPE_INET_MULTIPART_MIXED },
+ { CONTENT_TYPE_STR_INET_MULTI_PARALLEL, CONTENT_TYPE_INET_MULTIPART_PARALLEL },
+ { CONTENT_TYPE_STR_INET_MULTI_RELATED, CONTENT_TYPE_INET_MULTIPART_RELATED },
+ { CONTENT_TYPE_STR_TEXT_ICALENDAR, CONTENT_TYPE_TEXT_ICALENDAR },
+ { CONTENT_TYPE_STR_TEXT_HTML, CONTENT_TYPE_TEXT_HTML },
+ { CONTENT_TYPE_STR_TEXT_PLAIN, CONTENT_TYPE_TEXT_PLAIN },
+ { CONTENT_TYPE_STR_TEXT_XMLICALENDAR, CONTENT_TYPE_TEXT_XMLICALENDAR },
+ { CONTENT_TYPE_STR_TEXT_URL, CONTENT_TYPE_TEXT_URL },
+ { CONTENT_TYPE_STR_TEXT_VCALENDAR, CONTENT_TYPE_TEXT_VCALENDAR },
+ { CONTENT_TYPE_STR_TEXT_VCARD, CONTENT_TYPE_TEXT_VCARD },
+ { CONTENT_TYPE_STR_VIDEO_MSVIDEO, CONTENT_TYPE_VIDEO_MSVIDEO },
+ { CONTENT_TYPE_STR_VIDEO_THEORA, CONTENT_TYPE_VIDEO_THEORA },
+ { CONTENT_TYPE_STR_VIDEO_VDO, CONTENT_TYPE_VIDEO_VDO },
+ { CONTENT_TYPE_STR_VIDEO_WEBM, CONTENT_TYPE_VIDEO_WEBM },
+ { CONTENT_TYPE_STR_X_STARMAIL, CONTENT_TYPE_X_STARMAIL },
+ { CONTENT_TYPE_STR_X_VRML, CONTENT_TYPE_X_VRML }
+};
+
+
+/** A mapping from extensions to type IDs. Sorted by extension.
+ */
+MediaTypeEntry const aStaticExtensionMap[]
+ = { { "aif", CONTENT_TYPE_AUDIO_AIFF },
+ { "aiff", CONTENT_TYPE_AUDIO_AIFF },
+ { "appt", CONTENT_TYPE_APP_CDE_CALENDAR_APP },
+ { "au", CONTENT_TYPE_AUDIO_BASIC },
+ { "avi", CONTENT_TYPE_VIDEO_MSVIDEO },
+ { "bmp", CONTENT_TYPE_IMAGE_BMP },
+ { "cgm", CONTENT_TYPE_IMAGE_GENERIC },
+ { "doc", CONTENT_TYPE_APP_MSWORD },
+ { "dot", CONTENT_TYPE_APP_MSWORD_TEMPL },
+ { "dxf", CONTENT_TYPE_IMAGE_GENERIC },
+ { "eps", CONTENT_TYPE_IMAGE_GENERIC },
+ { "gal", CONTENT_TYPE_APP_GALLERY },
+ { "gif", CONTENT_TYPE_IMAGE_GIF },
+ { "htm", CONTENT_TYPE_TEXT_HTML },
+ { "html", CONTENT_TYPE_TEXT_HTML },
+ { "ics", CONTENT_TYPE_TEXT_ICALENDAR },
+ { "jar", CONTENT_TYPE_APP_JAR },
+ { "jpeg", CONTENT_TYPE_IMAGE_JPEG },
+ { "jpg", CONTENT_TYPE_IMAGE_JPEG },
+ { "met", CONTENT_TYPE_IMAGE_GENERIC },
+ { "mid", CONTENT_TYPE_AUDIO_MIDI },
+ { "midi", CONTENT_TYPE_AUDIO_MIDI },
+ { "ogg", CONTENT_TYPE_AUDIO_VORBIS },
+ { "pbm", CONTENT_TYPE_IMAGE_GENERIC },
+ { "pcd", CONTENT_TYPE_IMAGE_GENERIC },
+ { "pct", CONTENT_TYPE_IMAGE_GENERIC },
+ { "pcx", CONTENT_TYPE_IMAGE_PCX },
+ { "pdf", CONTENT_TYPE_APP_PDF },
+ { "pgm", CONTENT_TYPE_IMAGE_GENERIC },
+ { "png", CONTENT_TYPE_IMAGE_PNG },
+ { "pot", CONTENT_TYPE_APP_MSPPOINT_TEMPL },
+ { "ppm", CONTENT_TYPE_IMAGE_GENERIC },
+ { "ppt", CONTENT_TYPE_APP_MSPPOINT },
+ { "psd", CONTENT_TYPE_IMAGE_GENERIC },
+ { "ras", CONTENT_TYPE_IMAGE_GENERIC },
+ { "rtf", CONTENT_TYPE_APP_RTF },
+ { "sda", CONTENT_TYPE_APP_VND_DRAW },
+ { "sdc", CONTENT_TYPE_APP_VND_CALC },
+ { "sdd", CONTENT_TYPE_APP_VND_IMPRESS },
+ { "sdm", CONTENT_TYPE_APP_VND_MAIL },
+ { "sdp", CONTENT_TYPE_APP_VND_IMPRESSPACKED },
+ { "sds", CONTENT_TYPE_APP_VND_CHART },
+ { "sdw", CONTENT_TYPE_APP_VND_WRITER },
+ { "sd~", CONTENT_TYPE_X_STARMAIL },
+ { "sfs", CONTENT_TYPE_APP_FRAMESET },
+ { "sgl", CONTENT_TYPE_APP_VND_WRITER_GLOBAL },
+ { "sim", CONTENT_TYPE_APP_VND_IMAGE },
+ { "smd", CONTENT_TYPE_APP_STARMAIL_SMD }, //CONTENT_TYPE_X_STARMAIL
+ { "smf", CONTENT_TYPE_APP_VND_MATH },
+ { "svh", CONTENT_TYPE_APP_STARHELP },
+ { "svm", CONTENT_TYPE_IMAGE_GENERIC },
+ { "sxc", CONTENT_TYPE_APP_VND_SUN_XML_CALC },
+ { "sxd", CONTENT_TYPE_APP_VND_SUN_XML_DRAW },
+ { "sxg", CONTENT_TYPE_APP_VND_SUN_XML_WRITER_GLOBAL },
+ { "sxi", CONTENT_TYPE_APP_VND_SUN_XML_IMPRESS },
+ { "sxm", CONTENT_TYPE_APP_VND_SUN_XML_MATH },
+ { "sxp", CONTENT_TYPE_APP_VND_SUN_XML_IMPRESSPACKED },
+ { "sxs", CONTENT_TYPE_APP_VND_SUN_XML_CHART },
+ { "sxw", CONTENT_TYPE_APP_VND_SUN_XML_WRITER },
+ { "tga", CONTENT_TYPE_IMAGE_GENERIC },
+ { "thm", CONTENT_TYPE_APP_GALLERY_THEME },
+ { "tif", CONTENT_TYPE_IMAGE_TIFF },
+ { "tiff", CONTENT_TYPE_IMAGE_TIFF },
+ { "txt", CONTENT_TYPE_TEXT_PLAIN },
+ { "url", CONTENT_TYPE_TEXT_URL },
+ { "vcf", CONTENT_TYPE_TEXT_VCARD },
+ { "vcs", CONTENT_TYPE_TEXT_VCALENDAR },
+ { "vdo", CONTENT_TYPE_VIDEO_VDO },
+ { "vor", CONTENT_TYPE_APP_VND_TEMPLATE },
+ { "wav", CONTENT_TYPE_AUDIO_WAV },
+ { "webm", CONTENT_TYPE_VIDEO_WEBM },
+ { "wmf", CONTENT_TYPE_IMAGE_GENERIC },
+ { "wrl", CONTENT_TYPE_X_VRML },
+ { "xbm", CONTENT_TYPE_IMAGE_GENERIC },
+ { "xcs", CONTENT_TYPE_TEXT_XMLICALENDAR },
+ { "xls", CONTENT_TYPE_APP_MSEXCEL },
+ { "xlt", CONTENT_TYPE_APP_MSEXCEL_TEMPL },
+ { "xlw", CONTENT_TYPE_APP_MSEXCEL },
+ { "xpm", CONTENT_TYPE_IMAGE_GENERIC },
+ { "zip", CONTENT_TYPE_APP_ZIP } };
+
+}
+
+
+// seekEntry
+
+
+namespace
+{
+
+MediaTypeEntry const * seekEntry(OUString const & rTypeName,
+ MediaTypeEntry const * pMap, std::size_t nSize)
+{
+#if defined DBG_UTIL
+ for (std::size_t i = 0; i < nSize - 1; ++i)
+ DBG_ASSERT(
+ pMap[i].m_pTypeName < pMap[i + 1].m_pTypeName,
+ "seekEntry(): Bad map");
+#endif
+
+ std::size_t nLow = 0;
+ std::size_t nHigh = nSize;
+ while (nLow != nHigh)
+ {
+ std::size_t nMiddle = (nLow + nHigh) / 2;
+ MediaTypeEntry const * pEntry = pMap + nMiddle;
+ sal_Int32 nCmp = rTypeName.compareToIgnoreAsciiCase(pEntry->m_pTypeName);
+ if (nCmp < 0)
+ nHigh = nMiddle;
+ else if (nCmp == 0)
+ return pEntry;
+
+ else
+ nLow = nMiddle + 1;
+ }
+ return nullptr;
+}
+
+}
+
+// static
+INetContentType INetContentTypes::GetContentType(OUString const & rTypeName)
+{
+ OUString aType;
+ OUString aSubType;
+ if (parse(rTypeName, aType, aSubType))
+ {
+ aType += "/" + aSubType;
+ MediaTypeEntry const * pEntry = seekEntry(aType, aStaticTypeNameMap,
+ CONTENT_TYPE_LAST + 1);
+ return pEntry ? pEntry->m_eTypeID : CONTENT_TYPE_UNKNOWN;
+ }
+ else
+ return rTypeName.equalsIgnoreAsciiCase(CONTENT_TYPE_STR_X_STARMAIL) ?
+ CONTENT_TYPE_X_STARMAIL : CONTENT_TYPE_UNKNOWN;
+ // the content type "x-starmail" has no sub type
+}
+
+//static
+OUString INetContentTypes::GetContentType(INetContentType eTypeID)
+{
+ static std::array<OUString, CONTENT_TYPE_LAST + 1> aMap = []()
+ {
+ std::array<OUString, CONTENT_TYPE_LAST + 1> tmp;
+ for (std::size_t i = 0; i <= CONTENT_TYPE_LAST; ++i)
+ tmp[aStaticTypeNameMap[i].m_eTypeID] = aStaticTypeNameMap[i].m_pTypeName;
+ tmp[CONTENT_TYPE_UNKNOWN] = CONTENT_TYPE_STR_APP_OCTSTREAM;
+ tmp[CONTENT_TYPE_TEXT_PLAIN] = CONTENT_TYPE_STR_TEXT_PLAIN +
+ "; charset=iso-8859-1";
+ return tmp;
+ }();
+
+ OUString aTypeName = eTypeID <= CONTENT_TYPE_LAST ? aMap[eTypeID]
+ : OUString();
+ if (aTypeName.isEmpty())
+ {
+ OSL_FAIL("INetContentTypes::GetContentType(): Bad ID");
+ return CONTENT_TYPE_STR_APP_OCTSTREAM;
+ }
+ return aTypeName;
+}
+
+//static
+INetContentType INetContentTypes::GetContentType4Extension(OUString const & rExtension)
+{
+ MediaTypeEntry const * pEntry = seekEntry(rExtension, aStaticExtensionMap,
+ SAL_N_ELEMENTS(aStaticExtensionMap));
+ if (pEntry)
+ return pEntry->m_eTypeID;
+ return CONTENT_TYPE_APP_OCTSTREAM;
+}
+
+//static
+INetContentType INetContentTypes::GetContentTypeFromURL(std::u16string_view rURL)
+{
+ INetContentType eTypeID = CONTENT_TYPE_UNKNOWN;
+ std::size_t nIdx{ 0 };
+ OUString aToken( o3tl::getToken(rURL, u':', nIdx) );
+ if (!aToken.isEmpty())
+ {
+ if (aToken.equalsIgnoreAsciiCase(INETTYPE_URL_PROT_FILE))
+ if (rURL[ rURL.size() - 1 ] == '/') // folder
+ if (rURL.size() > RTL_CONSTASCII_LENGTH("file:///"))
+ if (WildCard(u"*/{*}/").Matches(rURL)) // special folder
+ eTypeID = CONTENT_TYPE_X_CNT_FSYSSPECIALFOLDER;
+ else
+ // drive? -> "file:///?|/"
+ if (rURL.size() == 11
+ && rURL[ rURL.size() - 2 ] == '|')
+ {
+ // Drives need further processing, because of
+ // dynamic type according to underlying volume,
+ // which cannot be determined here.
+ }
+ else // normal folder
+ eTypeID = CONTENT_TYPE_X_CNT_FSYSFOLDER;
+ else // file system root
+ eTypeID = CONTENT_TYPE_X_CNT_FSYSBOX;
+ else // file
+ {
+ //@@@
+ }
+ else if (aToken.equalsIgnoreAsciiCase(INETTYPE_URL_PROT_HTTP)
+ || aToken.equalsIgnoreAsciiCase(INETTYPE_URL_PROT_HTTPS))
+ eTypeID = CONTENT_TYPE_TEXT_HTML;
+ else if (aToken.equalsIgnoreAsciiCase(INETTYPE_URL_PROT_PRIVATE))
+ {
+ aToken = o3tl::getToken(rURL, u'/', nIdx);
+ if (aToken == "factory")
+ {
+ aToken = o3tl::getToken(rURL, u'/', nIdx);
+ if (aToken == "swriter")
+ {
+ aToken = o3tl::getToken(rURL, u'/', nIdx);
+ eTypeID = aToken == "web" ?
+ CONTENT_TYPE_APP_VND_WRITER_WEB :
+ aToken == "GlobalDocument" ?
+ CONTENT_TYPE_APP_VND_WRITER_GLOBAL :
+ CONTENT_TYPE_APP_VND_WRITER;
+ }
+ else if (aToken == "scalc")
+ eTypeID = CONTENT_TYPE_APP_VND_CALC;
+ else if (aToken == "sdraw")
+ eTypeID = CONTENT_TYPE_APP_VND_DRAW;
+ else if (aToken == "simpress")
+ eTypeID = CONTENT_TYPE_APP_VND_IMPRESS;
+ else if (aToken == "schart")
+ eTypeID = CONTENT_TYPE_APP_VND_CHART;
+ else if (aToken == "simage")
+ eTypeID = CONTENT_TYPE_APP_VND_IMAGE;
+ else if (aToken == "smath")
+ eTypeID = CONTENT_TYPE_APP_VND_MATH;
+ else if (aToken == "frameset")
+ eTypeID = CONTENT_TYPE_APP_FRAMESET;
+ }
+ else if (aToken == "helpid")
+ eTypeID = CONTENT_TYPE_APP_STARHELP;
+ }
+ else if (aToken.equalsIgnoreAsciiCase(INETTYPE_URL_PROT_MAILTO))
+ eTypeID = CONTENT_TYPE_APP_VND_OUTTRAY;
+ else if (aToken.equalsIgnoreAsciiCase(INETTYPE_URL_PROT_MACRO))
+ eTypeID = CONTENT_TYPE_APP_MACRO;
+ else if (aToken.equalsIgnoreAsciiCase(INETTYPE_URL_PROT_DATA))
+ {
+ aToken = o3tl::getToken(rURL, u',', nIdx);
+ eTypeID = GetContentType(aToken);
+ }
+ }
+ if (eTypeID == CONTENT_TYPE_UNKNOWN)
+ {
+ OUString aExtension;
+ if (GetExtensionFromURL(rURL, aExtension))
+ eTypeID = GetContentType4Extension(aExtension);
+ }
+ return eTypeID;
+}
+
+//static
+bool INetContentTypes::GetExtensionFromURL(std::u16string_view rURL,
+ OUString & rExtension)
+{
+ size_t nSlashPos = 0;
+ size_t i = 0;
+ while (i != std::u16string_view::npos)
+ {
+ nSlashPos = i;
+ i = rURL.find('/', i + 1);
+ }
+ if (nSlashPos != 0)
+ {
+ size_t nLastDotPos = i = rURL.find('.', nSlashPos);
+ while (i != std::u16string_view::npos)
+ {
+ nLastDotPos = i;
+ i = rURL.find('.', i + 1);
+ }
+ if (nLastDotPos >- 0)
+ rExtension = rURL.substr(nLastDotPos + 1);
+ return true;
+ }
+ return false;
+}
+
+bool INetContentTypes::parse(
+ OUString const & rMediaType, OUString & rType, OUString & rSubType,
+ INetContentTypeParameterList * pParameters)
+{
+ sal_Unicode const * b = rMediaType.getStr();
+ sal_Unicode const * e = b + rMediaType.getLength();
+ OUString t;
+ OUString s;
+ INetContentTypeParameterList p;
+ if (INetMIME::scanContentType(rMediaType, &t, &s, pParameters == nullptr ? nullptr : &p) == e) {
+ rType = t;
+ rSubType = s;
+ if (pParameters != nullptr) {
+ *pParameters = p;
+ }
+ return true;
+ } else {
+ return false;
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/misc/lngmisc.cxx b/svl/source/misc/lngmisc.cxx
new file mode 100644
index 0000000000..7ccf0aed72
--- /dev/null
+++ b/svl/source/misc/lngmisc.cxx
@@ -0,0 +1,132 @@
+/* -*- 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 <svl/lngmisc.hxx>
+
+#include <comphelper/string.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <tools/debug.hxx>
+
+namespace linguistic
+{
+ sal_Int32 GetNumControlChars(std::u16string_view rTxt)
+ {
+ sal_Int32 nCnt = 0;
+ for (size_t i = 0; i < rTxt.size(); ++i)
+ if (IsControlChar(rTxt[i]))
+ ++nCnt;
+ return nCnt;
+ }
+
+ bool RemoveHyphens(OUString &rTxt)
+ {
+ sal_Int32 n = rTxt.getLength();
+ rTxt = rTxt.replaceAll(OUStringChar(SVT_SOFT_HYPHEN), "");
+ rTxt = rTxt.replaceAll(OUStringChar(SVT_HARD_HYPHEN), "");
+ return n != rTxt.getLength();
+ }
+
+ bool RemoveControlChars(OUString &rTxt)
+ {
+ sal_Int32 nSize = rTxt.getLength() - GetNumControlChars(rTxt);
+ if(nSize == rTxt.getLength())
+ return false;
+
+ OUStringBuffer aBuf(nSize);
+ aBuf.setLength(nSize);
+ for (sal_Int32 i = 0, j = 0; i < rTxt.getLength() && j < nSize; ++i)
+ if (!IsControlChar(rTxt[i]))
+ aBuf[j++] = rTxt[i];
+
+ rTxt = aBuf.makeStringAndClear();
+ DBG_ASSERT(rTxt.getLength() == nSize, "GetNumControlChars returned a different number of control characters than were actually removed.");
+
+ return true;
+ }
+
+ bool ReplaceControlChars(OUString &rTxt)
+ {
+ // non breaking field character
+ static const char CH_TXTATR_INWORD = static_cast<char>(0x02);
+
+ // the resulting string looks like this:
+ // 1. non breaking field characters get removed
+ // 2. remaining control characters will be replaced by ' '
+
+ if (GetNumControlChars(rTxt) == 0)
+ return false;
+
+ sal_Int32 n = rTxt.getLength();
+
+ OUStringBuffer aBuf(n);
+ aBuf.setLength(n);
+
+ sal_Int32 j = 0;
+ for (sal_Int32 i = 0; i < n && j < n; ++i)
+ {
+ if (CH_TXTATR_INWORD == rTxt[i])
+ continue;
+
+ aBuf[j++] = IsControlChar(rTxt[i]) ? ' ' : rTxt[i];
+ }
+
+ aBuf.setLength(j);
+ rTxt = aBuf.makeStringAndClear();
+
+ return true;
+ }
+
+ OUString GetThesaurusReplaceText(const OUString &rText)
+ {
+ // The strings for synonyms returned by the thesaurus sometimes have some
+ // explanation text put in between '(' and ')' or a trailing '*'.
+ // These parts should not be put in the ReplaceEdit Text that may get
+ // inserted into the document. Thus we strip them from the text.
+
+ OUString aText(rText);
+
+ sal_Int32 nPos = aText.indexOf('(');
+ while (nPos >= 0)
+ {
+ sal_Int32 nEnd = aText.indexOf(')', nPos);
+ if (nEnd >= 0)
+ {
+ OUStringBuffer aTextBuf(aText);
+ aTextBuf.remove(nPos, nEnd - nPos + 1);
+ aText = aTextBuf.makeStringAndClear();
+ }
+ else
+ break;
+ nPos = aText.indexOf('(');
+ }
+
+ nPos = aText.indexOf('*');
+ if(nPos == 0)
+ return OUString();
+ else if(nPos > 0)
+ aText = aText.copy(0, nPos);
+
+ // remove any possible remaining ' ' that may confuse the thesaurus
+ // when it gets called with the text
+ return comphelper::string::strip(aText, ' ');
+ }
+} // namespace linguistic
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/misc/lockfilecommon.cxx b/svl/source/misc/lockfilecommon.cxx
new file mode 100644
index 0000000000..bcf568b70a
--- /dev/null
+++ b/svl/source/misc/lockfilecommon.cxx
@@ -0,0 +1,243 @@
+/* -*- 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/lang/IllegalArgumentException.hpp>
+#include <com/sun/star/io/WrongFormatException.hpp>
+
+#include <osl/time.h>
+#include <osl/security.hxx>
+#include <osl/socket.hxx>
+#include <osl/file.hxx>
+#include <o3tl/enumrange.hxx>
+#include <o3tl/sprintf.hxx>
+
+#include <rtl/ustring.hxx>
+#include <rtl/strbuf.hxx>
+#include <rtl/ustrbuf.hxx>
+
+#include <tools/urlobj.hxx>
+#include <unotools/bootstrap.hxx>
+
+#include <unotools/useroptions.hxx>
+
+#include <salhelper/linkhelper.hxx>
+
+#include <svl/lockfilecommon.hxx>
+#include <utility>
+
+using namespace ::com::sun::star;
+
+namespace svt {
+
+
+LockFileCommon::LockFileCommon(OUString aLockFileURL)
+ : m_aURL(std::move(aLockFileURL))
+{
+}
+
+LockFileCommon::~LockFileCommon()
+{
+}
+
+
+const OUString& LockFileCommon::GetURL() const
+{
+ return m_aURL;
+}
+
+
+void LockFileCommon::SetURL(const OUString& aURL)
+{
+ m_aURL = aURL;
+}
+
+
+OUString LockFileCommon::GenerateOwnLockFileURL(
+ std::u16string_view aOrigURL, std::u16string_view aPrefix)
+{
+ INetURLObject aURL = ResolveLinks(INetURLObject(aOrigURL));
+ aURL.setName(Concat2View(aPrefix + aURL.GetLastName() + "%23" /*'#'*/));
+ return aURL.GetMainURL(INetURLObject::DecodeMechanism::NONE);
+}
+
+
+INetURLObject LockFileCommon::ResolveLinks( const INetURLObject& aDocURL )
+{
+ if ( aDocURL.HasError() )
+ throw lang::IllegalArgumentException();
+
+ OUString aURLToCheck = aDocURL.GetMainURL(INetURLObject::DecodeMechanism::NONE);
+
+ // there is currently no UCB functionality to resolve the symbolic links;
+ // since the lock files are used only for local file systems the osl
+ // functionality is used directly
+ salhelper::LinkResolver aResolver(osl_FileStatus_Mask_FileName);
+ osl::FileBase::RC eStatus = aResolver.fetchFileStatus(aURLToCheck);
+ if (eStatus == osl::FileBase::E_None)
+ aURLToCheck = aResolver.m_aStatus.getFileURL();
+ else if (eStatus == osl::FileBase::E_MULTIHOP)
+ {
+ // do not allow too deep links
+ throw io::IOException();
+ }
+
+ return INetURLObject( aURLToCheck );
+}
+
+
+void LockFileCommon::ParseList( const uno::Sequence< sal_Int8 >& aBuffer, std::vector< LockFileEntry > & aResult )
+{
+ sal_Int32 nCurPos = 0;
+ while ( nCurPos < aBuffer.getLength() )
+ {
+ aResult.push_back( ParseEntry( aBuffer, nCurPos ) );
+ }
+}
+
+
+LockFileEntry LockFileCommon::ParseEntry( const uno::Sequence< sal_Int8 >& aBuffer, sal_Int32& io_nCurPos )
+{
+ LockFileEntry aResult;
+
+ for ( LockFileComponent nInd : o3tl::enumrange<LockFileComponent>() )
+ {
+ aResult[nInd] = ParseName( aBuffer, io_nCurPos );
+ if ( io_nCurPos >= aBuffer.getLength()
+ || ( nInd < LockFileComponent::LAST && aBuffer[io_nCurPos++] != ',' )
+ || ( nInd == LockFileComponent::LAST && aBuffer[io_nCurPos++] != ';' ) )
+ throw io::WrongFormatException();
+ }
+
+ return aResult;
+}
+
+
+OUString LockFileCommon::ParseName( const uno::Sequence< sal_Int8 >& aBuffer, sal_Int32& io_nCurPos )
+{
+ OStringBuffer aResult(128);
+ bool bHaveName = false;
+ bool bEscape = false;
+
+ while( !bHaveName )
+ {
+ if ( io_nCurPos >= aBuffer.getLength() )
+ throw io::WrongFormatException();
+
+ if ( bEscape )
+ {
+ if ( aBuffer[io_nCurPos] != ',' && aBuffer[io_nCurPos] != ';' && aBuffer[io_nCurPos] != '\\' )
+ throw io::WrongFormatException();
+
+ aResult.append( static_cast<char>(aBuffer[io_nCurPos]) );
+
+ bEscape = false;
+ io_nCurPos++;
+ }
+ else if ( aBuffer[io_nCurPos] == ',' || aBuffer[io_nCurPos] == ';' )
+ bHaveName = true;
+ else
+ {
+ if ( aBuffer[io_nCurPos] == '\\' )
+ bEscape = true;
+ else
+ aResult.append( static_cast<char>(aBuffer[io_nCurPos]) );
+
+ io_nCurPos++;
+ }
+ }
+
+ return OStringToOUString( aResult, RTL_TEXTENCODING_UTF8 );
+}
+
+
+OUString LockFileCommon::EscapeCharacters( const OUString& aSource )
+{
+ OUStringBuffer aBuffer(aSource.getLength()*2);
+ const sal_Unicode* pStr = aSource.getStr();
+ for ( sal_Int32 nInd = 0; nInd < aSource.getLength() && pStr[nInd] != 0; nInd++ )
+ {
+ if ( pStr[nInd] == '\\' || pStr[nInd] == ';' || pStr[nInd] == ',' )
+ aBuffer.append( '\\' );
+ aBuffer.append( pStr[nInd] );
+ }
+
+ return aBuffer.makeStringAndClear();
+}
+
+
+OUString LockFileCommon::GetOOOUserName()
+{
+ SvtUserOptions aUserOpt;
+ OUString aName = aUserOpt.GetFirstName();
+ if ( !aName.isEmpty() )
+ aName += " ";
+ aName += aUserOpt.GetLastName();
+
+ return aName;
+}
+
+
+OUString LockFileCommon::GetCurrentLocalTime()
+{
+ OUString aTime;
+
+ TimeValue aSysTime;
+ if ( osl_getSystemTime( &aSysTime ) )
+ {
+ TimeValue aLocTime;
+ if ( osl_getLocalTimeFromSystemTime( &aSysTime, &aLocTime ) )
+ {
+ oslDateTime aDateTime;
+ if ( osl_getDateTimeFromTimeValue( &aLocTime, &aDateTime ) )
+ {
+ char pDateTime[sizeof("65535.65535.-32768 65535:65535")];
+ // reserve enough space for hypothetical max length
+ o3tl::sprintf( pDateTime, "%02" SAL_PRIuUINT32 ".%02" SAL_PRIuUINT32 ".%4" SAL_PRIdINT32 " %02" SAL_PRIuUINT32 ":%02" SAL_PRIuUINT32, sal_uInt32(aDateTime.Day), sal_uInt32(aDateTime.Month), sal_Int32(aDateTime.Year), sal_uInt32(aDateTime.Hours), sal_uInt32(aDateTime.Minutes) );
+ aTime = OUString::createFromAscii( pDateTime );
+ }
+ }
+ }
+
+ return aTime;
+}
+
+
+LockFileEntry LockFileCommon::GenerateOwnEntry()
+{
+ LockFileEntry aResult;
+
+ aResult[LockFileComponent::OOOUSERNAME] = GetOOOUserName();
+
+ ::osl::Security aSecurity;
+ aSecurity.getUserName( aResult[LockFileComponent::SYSUSERNAME] );
+
+ aResult[LockFileComponent::LOCALHOST] = ::osl::SocketAddr::getLocalHostname();
+
+ aResult[LockFileComponent::EDITTIME] = GetCurrentLocalTime();
+
+ ::utl::Bootstrap::locateUserInstallation( aResult[LockFileComponent::USERURL] );
+
+
+ return aResult;
+}
+
+} // namespace svt
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/misc/msodocumentlockfile.cxx b/svl/source/misc/msodocumentlockfile.cxx
new file mode 100644
index 0000000000..dab0486e43
--- /dev/null
+++ b/svl/source/misc/msodocumentlockfile.cxx
@@ -0,0 +1,269 @@
+/* -*- 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 <svl/msodocumentlockfile.hxx>
+#include <algorithm>
+#include <ucbhelper/content.hxx>
+#include <comphelper/processfactory.hxx>
+#include <o3tl/string_view.hxx>
+
+#include <com/sun/star/io/IOException.hpp>
+#include <com/sun/star/io/XOutputStream.hpp>
+#include <com/sun/star/io/XInputStream.hpp>
+#include <com/sun/star/ucb/XCommandEnvironment.hpp>
+
+namespace svt
+{
+namespace
+{
+bool isWordFormat(std::u16string_view sExt)
+{
+ return o3tl::equalsIgnoreAsciiCase(sExt, u"DOC") || o3tl::equalsIgnoreAsciiCase(sExt, u"DOCX")
+ || o3tl::equalsIgnoreAsciiCase(sExt, u"RTF")
+ || o3tl::equalsIgnoreAsciiCase(sExt, u"ODT");
+}
+
+bool isExcelFormat(std::u16string_view sExt)
+{
+ return //sExt.equalsIgnoreAsciiCase("XLS") || // MSO does not create lockfile for XLS
+ o3tl::equalsIgnoreAsciiCase(sExt, u"XLSX") || o3tl::equalsIgnoreAsciiCase(sExt, u"ODS");
+}
+
+bool isPowerPointFormat(std::u16string_view sExt)
+{
+ return o3tl::equalsIgnoreAsciiCase(sExt, u"PPTX") || o3tl::equalsIgnoreAsciiCase(sExt, u"PPT")
+ || o3tl::equalsIgnoreAsciiCase(sExt, u"ODP");
+}
+
+// Need to generate different lock file name for MSO.
+OUString GenerateMSOLockFileURL(std::u16string_view aOrigURL)
+{
+ INetURLObject aURL = LockFileCommon::ResolveLinks(INetURLObject(aOrigURL));
+
+ // For text documents MSO Word cuts some of the first characters of the file name
+ OUString sFileName = aURL.GetLastName();
+ const OUString sExt = aURL.GetFileExtension();
+
+ if (isWordFormat(sExt))
+ {
+ const sal_Int32 nFileNameLength = sFileName.getLength() - sExt.getLength() - 1;
+ if (nFileNameLength >= 8)
+ sFileName = sFileName.copy(2);
+ else if (nFileNameLength == 7)
+ sFileName = sFileName.copy(1);
+ }
+ aURL.setName(Concat2View("~$" + sFileName));
+ return aURL.GetMainURL(INetURLObject::DecodeMechanism::NONE);
+}
+}
+
+// static
+MSODocumentLockFile::AppType MSODocumentLockFile::getAppType(std::u16string_view sOrigURL)
+{
+ AppType eResult = AppType::PowerPoint;
+ INetURLObject aDocURL = LockFileCommon::ResolveLinks(INetURLObject(sOrigURL));
+ const OUString sExt = aDocURL.GetFileExtension();
+ if (isWordFormat(sExt))
+ eResult = AppType::Word;
+ else if (isExcelFormat(sExt))
+ eResult = AppType::Excel;
+
+ return eResult;
+}
+
+MSODocumentLockFile::MSODocumentLockFile(std::u16string_view aOrigURL)
+ : GenDocumentLockFile(GenerateMSOLockFileURL(aOrigURL))
+ , m_eAppType(getAppType(aOrigURL))
+{
+}
+
+MSODocumentLockFile::~MSODocumentLockFile() {}
+
+void MSODocumentLockFile::WriteEntryToStream(
+ std::unique_lock<std::mutex>& /*rGuard*/, const LockFileEntry& aEntry,
+ const css::uno::Reference<css::io::XOutputStream>& xOutput)
+{
+ // Reallocate the date with the right size, different lock file size for different components
+ int nLockFileSize = m_eAppType == AppType::Word ? MSO_WORD_LOCKFILE_SIZE
+ : MSO_EXCEL_AND_POWERPOINT_LOCKFILE_SIZE;
+ css::uno::Sequence<sal_Int8> aData(nLockFileSize);
+ auto pData = aData.getArray();
+
+ // Write out the user name's length as a single byte integer
+ // The maximum length is 52 in MSO, so we'll need to truncate the user name if it's longer
+ OUString aUserName = aEntry[LockFileComponent::OOOUSERNAME];
+ int nIndex = 0;
+ pData[nIndex] = static_cast<sal_Int8>(
+ std::min(aUserName.getLength(), sal_Int32(MSO_USERNAME_MAX_LENGTH)));
+
+ if (aUserName.getLength() > MSO_USERNAME_MAX_LENGTH)
+ aUserName = aUserName.copy(0, MSO_USERNAME_MAX_LENGTH);
+
+ // From the second position write out the user name using one byte characters.
+ nIndex = 1;
+ for (int nChar = 0; nChar < aUserName.getLength(); ++nChar)
+ {
+ pData[nIndex] = static_cast<sal_Int8>(aUserName[nChar]);
+ ++nIndex;
+ }
+
+ // Fill up the remaining bytes with dummy data
+ switch (m_eAppType)
+ {
+ case AppType::Word:
+ while (nIndex < MSO_USERNAME_MAX_LENGTH + 2)
+ {
+ pData[nIndex] = static_cast<sal_Int8>(0);
+ ++nIndex;
+ }
+ break;
+ case AppType::PowerPoint:
+ pData[nIndex] = static_cast<sal_Int8>(0);
+ ++nIndex;
+ [[fallthrough]];
+ case AppType::Excel:
+ while (nIndex < MSO_USERNAME_MAX_LENGTH + 3)
+ {
+ pData[nIndex] = static_cast<sal_Int8>(0x20);
+ ++nIndex;
+ }
+ break;
+ }
+
+ // At the next position we have the user name's length again, but now as a 2 byte integer
+ pData[nIndex] = static_cast<sal_Int8>(
+ std::min(aUserName.getLength(), sal_Int32(MSO_USERNAME_MAX_LENGTH)));
+ ++nIndex;
+ pData[nIndex] = 0;
+ ++nIndex;
+
+ // And the user name again with unicode characters
+ for (int nChar = 0; nChar < aUserName.getLength(); ++nChar)
+ {
+ pData[nIndex] = static_cast<sal_Int8>(aUserName[nChar] & 0xff);
+ ++nIndex;
+ pData[nIndex] = static_cast<sal_Int8>(aUserName[nChar] >> 8);
+ ++nIndex;
+ }
+
+ // Fill the remaining part with dummy bits
+ switch (m_eAppType)
+ {
+ case AppType::Word:
+ while (nIndex < nLockFileSize)
+ {
+ pData[nIndex] = static_cast<sal_Int8>(0);
+ ++nIndex;
+ }
+ break;
+ case AppType::Excel:
+ case AppType::PowerPoint:
+ while (nIndex < nLockFileSize)
+ {
+ pData[nIndex] = static_cast<sal_Int8>(0x20);
+ ++nIndex;
+ if (nIndex < nLockFileSize)
+ {
+ pData[nIndex] = static_cast<sal_Int8>(0);
+ ++nIndex;
+ }
+ }
+ break;
+ }
+
+ xOutput->writeBytes(aData);
+}
+
+css::uno::Reference<css::io::XInputStream>
+MSODocumentLockFile::OpenStream(std::unique_lock<std::mutex>& /*rGuard*/)
+{
+ css::uno::Reference<css::ucb::XCommandEnvironment> xEnv;
+ ::ucbhelper::Content aSourceContent(GetURL(), xEnv, comphelper::getProcessComponentContext());
+
+ // the file can be opened readonly, no locking will be done
+ return aSourceContent.openStreamNoLock();
+}
+
+LockFileEntry MSODocumentLockFile::GetLockDataImpl(std::unique_lock<std::mutex>& rGuard)
+{
+ LockFileEntry aResult;
+ css::uno::Reference<css::io::XInputStream> xInput = OpenStream(rGuard);
+ if (!xInput.is())
+ throw css::uno::RuntimeException();
+
+ const sal_Int32 nBufLen = 256;
+ css::uno::Sequence<sal_Int8> aBuf(nBufLen);
+ const sal_Int32 nRead = xInput->readBytes(aBuf, nBufLen);
+ xInput->closeInput();
+ if (nRead >= 162)
+ {
+ // Reverse engineering of MS Office Owner Files format (MS Office 2016 tested).
+ // It starts with a single byte with name length, after which characters of username go
+ // in current Windows 8-bit codepage.
+ // For Word lockfiles, the name is followed by zero bytes up to position 54.
+ // For PowerPoint lockfiles, the name is followed by a single zero byte, and then 0x20
+ // bytes up to position 55.
+ // For Excel lockfiles, the name is followed by 0x20 bytes up to position 55.
+ // At those positions in each type of lockfile, a name length 2-byte word goes, followed
+ // by UTF-16-LE-encoded copy of username. Spaces or some garbage follow up to the end of
+ // the lockfile (total 162 bytes for Word, 165 bytes for Excel/PowerPoint).
+ // Apparently MS Office does not allow username to be longer than 52 characters (trying
+ // to enter more in its options dialog results in error messages stating this limit).
+ const int nACPLen = aBuf[0];
+ if (nACPLen > 0 && nACPLen <= 52) // skip wrong format
+ {
+ const sal_Int8* pBuf = aBuf.getConstArray() + 54;
+ int nUTF16Len = *pBuf; // try Word position
+ // If UTF-16 length is 0x20, then ACP length is also less than maximal, which means
+ // that in Word lockfile case, at least two preceding bytes would be zero. Both
+ // Excel and PowerPoint lockfiles would have at least one of those bytes non-zero.
+ if (nUTF16Len == 0x20 && (*(pBuf - 1) != 0 || *(pBuf - 2) != 0))
+ nUTF16Len = *++pBuf; // use Excel/PowerPoint position
+
+ if (nUTF16Len > 0 && nUTF16Len <= 52) // skip wrong format
+ {
+ OUStringBuffer str(nUTF16Len);
+ sal_uInt8 const* p = reinterpret_cast<sal_uInt8 const*>(pBuf + 2);
+ for (int i = 0; i != nUTF16Len; ++i)
+ {
+ str.append(sal_Unicode(p[0] | (sal_uInt32(p[1]) << 8)));
+ p += 2;
+ }
+ aResult[LockFileComponent::OOOUSERNAME] = str.makeStringAndClear();
+ }
+ }
+ }
+ return aResult;
+}
+
+void MSODocumentLockFile::RemoveFile()
+{
+ std::unique_lock aGuard(m_aMutex);
+
+ // TODO/LATER: the removing is not atomic, is it possible in general to make it atomic?
+ LockFileEntry aNewEntry = GenerateOwnEntry();
+ LockFileEntry aFileData = GetLockDataImpl(aGuard);
+
+ if (aFileData[LockFileComponent::OOOUSERNAME] != aNewEntry[LockFileComponent::OOOUSERNAME])
+ throw css::io::IOException(); // not the owner, access denied
+
+ RemoveFileDirectly();
+}
+
+bool MSODocumentLockFile::IsMSOSupportedFileFormat(std::u16string_view aURL)
+{
+ INetURLObject aDocURL = LockFileCommon::ResolveLinks(INetURLObject(aURL));
+ const OUString sExt = aDocURL.GetFileExtension();
+
+ return isWordFormat(sExt) || isExcelFormat(sExt) || isPowerPointFormat(sExt);
+}
+
+} // namespace svt
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/svl/source/misc/ownlist.cxx b/svl/source/misc/ownlist.cxx
new file mode 100644
index 0000000000..22b9d21fcc
--- /dev/null
+++ b/svl/source/misc/ownlist.cxx
@@ -0,0 +1,70 @@
+/* -*- 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 <com/sun/star/beans/PropertyValue.hpp>
+#include <com/sun/star/beans/PropertyState.hpp>
+
+#include <svl/ownlist.hxx>
+
+using namespace com::sun::star;
+
+
+/**
+ * An object of the type SvCommand is created and the list is
+ * attached.
+*/
+void SvCommandList::Append
+(
+ const OUString & rCommand, /* The command */
+ const OUString & rArg /* The command's argument */
+)
+{
+ aCommandList.emplace_back( rCommand, rArg );
+}
+
+void SvCommandList::FillFromSequence( const css::uno::Sequence < css::beans::PropertyValue >& aCommandSequence )
+{
+ OUString aCommand, aArg;
+ OUString aApiArg;
+ for( const auto& rCommand : aCommandSequence )
+ {
+ aCommand = rCommand.Name;
+ if( !( rCommand.Value >>= aApiArg ) )
+ return;
+ aArg = aApiArg;
+ Append( aCommand, aArg );
+ }
+}
+
+void SvCommandList::FillSequence( css::uno::Sequence < css::beans::PropertyValue >& aCommandSequence ) const
+{
+ const sal_Int32 nCount = aCommandList.size();
+ aCommandSequence.realloc( nCount );
+ auto pCommandSequence = aCommandSequence.getArray();
+ for( sal_Int32 nIndex = 0; nIndex < nCount; nIndex++ )
+ {
+ pCommandSequence[nIndex].Name = aCommandList[ nIndex ].GetCommand();
+ pCommandSequence[nIndex].Handle = -1;
+ pCommandSequence[nIndex].Value <<= aCommandList[ nIndex ].GetArgument();
+ pCommandSequence[nIndex].State = beans::PropertyState_DIRECT_VALUE;
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/misc/sharecontrolfile.cxx b/svl/source/misc/sharecontrolfile.cxx
new file mode 100644
index 0000000000..7c8fd854b2
--- /dev/null
+++ b/svl/source/misc/sharecontrolfile.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 <com/sun/star/ucb/SimpleFileAccess.hpp>
+#include <com/sun/star/ucb/XCommandEnvironment.hpp>
+#include <com/sun/star/ucb/XContent.hpp>
+#include <com/sun/star/ucb/InsertCommandArgument.hpp>
+#include <com/sun/star/ucb/InteractiveIOException.hpp>
+#include <com/sun/star/io/NotConnectedException.hpp>
+
+#include <o3tl/enumrange.hxx>
+
+#include <rtl/string.hxx>
+#include <rtl/ustring.hxx>
+#include <rtl/ustrbuf.hxx>
+
+#include <comphelper/processfactory.hxx>
+#include <ucbhelper/content.hxx>
+
+#include <tools/stream.hxx>
+#include <unotools/streamwrap.hxx>
+
+#include <svl/sharecontrolfile.hxx>
+
+using namespace ::com::sun::star;
+
+namespace svt {
+
+
+ShareControlFile::ShareControlFile( std::u16string_view aOrigURL )
+ : LockFileCommon(GenerateOwnLockFileURL(aOrigURL, u".~sharing."))
+{
+ if ( !m_xStream.is() && !GetURL().isEmpty() )
+ {
+ uno::Reference< ucb::XCommandEnvironment > xDummyEnv;
+ ::ucbhelper::Content aContent( GetURL(), xDummyEnv, comphelper::getProcessComponentContext() );
+
+ uno::Reference< ucb::XContentIdentifier > xContId( aContent.get().is() ? aContent.get()->getIdentifier() : nullptr );
+ if ( !xContId.is() || xContId->getContentProviderScheme() != "file" )
+ throw io::IOException(); // the implementation supports only local files for now
+
+ uno::Reference< io::XStream > xStream;
+
+ // Currently the locking of the original document is intended to be used.
+ // That means that the shared file should be accessed only when the original document is locked and only by user who has locked the document.
+ // TODO/LATER: should the own file locking be used?
+
+ try
+ {
+ xStream = aContent.openWriteableStreamNoLock();
+ }
+ catch ( ucb::InteractiveIOException const & e )
+ {
+ if ( e.Code == ucb::IOErrorCode_NOT_EXISTING )
+ {
+ // Create file...
+ SvMemoryStream aStream(0,0);
+ uno::Reference< io::XInputStream > xInput( new ::utl::OInputStreamWrapper( aStream ) );
+ ucb::InsertCommandArgument aInsertArg;
+ aInsertArg.Data = xInput;
+ aInsertArg.ReplaceExisting = false;
+ aContent.executeCommand( "insert", uno::Any( aInsertArg ) );
+
+ // try to let the file be hidden if possible
+ try {
+ aContent.setPropertyValue("IsHidden", uno::Any( true ) );
+ } catch( uno::Exception& ) {}
+
+ // Try to open one more time
+ xStream = aContent.openWriteableStreamNoLock();
+ }
+ else
+ throw;
+ }
+
+ m_xSeekable.set( xStream, uno::UNO_QUERY_THROW );
+ m_xInputStream.set( xStream->getInputStream(), uno::UNO_SET_THROW );
+ m_xOutputStream.set( xStream->getOutputStream(), uno::UNO_SET_THROW );
+ m_xTruncate.set( m_xOutputStream, uno::UNO_QUERY_THROW );
+ m_xStream = xStream;
+ }
+
+ if ( !IsValid() )
+ throw io::NotConnectedException();
+}
+
+ShareControlFile::~ShareControlFile()
+{
+ try
+ {
+ Close();
+ }
+ catch( uno::Exception& )
+ {}
+}
+
+void ShareControlFile::Close()
+{
+ // if it is called outside of destructor the mutex must be locked
+
+ if ( !m_xStream.is() )
+ return;
+
+ try
+ {
+ if ( m_xInputStream.is() )
+ m_xInputStream->closeInput();
+ if ( m_xOutputStream.is() )
+ m_xOutputStream->closeOutput();
+ }
+ catch( uno::Exception& )
+ {}
+
+ m_xStream.clear();
+ m_xInputStream.clear();
+ m_xOutputStream.clear();
+ m_xSeekable.clear();
+ m_xTruncate.clear();
+ m_aUsersData.clear();
+}
+
+
+std::vector< o3tl::enumarray< LockFileComponent, OUString > > ShareControlFile::GetUsersData()
+{
+ std::unique_lock aGuard(m_aMutex);
+ return GetUsersDataImpl(aGuard);
+}
+
+std::vector< o3tl::enumarray< LockFileComponent, OUString > > ShareControlFile::GetUsersDataImpl(std::unique_lock<std::mutex>& /*rGuard*/)
+{
+ if ( !IsValid() )
+ throw io::NotConnectedException();
+
+ if ( m_aUsersData.empty() )
+ {
+ sal_Int64 nLength = m_xSeekable->getLength();
+ if ( nLength > SAL_MAX_INT32 )
+ throw uno::RuntimeException();
+
+ uno::Sequence< sal_Int8 > aBuffer( static_cast<sal_Int32>(nLength) );
+ m_xSeekable->seek( 0 );
+
+ sal_Int32 nRead = m_xInputStream->readBytes( aBuffer, static_cast<sal_Int32>(nLength) );
+ auto aBufferRange = asNonConstRange(aBuffer);
+ nLength -= nRead;
+ while ( nLength > 0 )
+ {
+ uno::Sequence< sal_Int8 > aTmpBuf( static_cast<sal_Int32>(nLength) );
+ nRead = m_xInputStream->readBytes( aTmpBuf, static_cast<sal_Int32>(nLength) );
+ if ( nRead > nLength )
+ throw uno::RuntimeException();
+
+ for ( sal_Int32 nInd = 0; nInd < nRead; nInd++ )
+ aBufferRange[aBuffer.getLength() - static_cast<sal_Int32>(nLength) + nInd] = aTmpBuf[nInd];
+ nLength -= nRead;
+ }
+
+ ParseList( aBuffer, m_aUsersData );
+ }
+
+ return m_aUsersData;
+}
+
+
+void ShareControlFile::SetUsersDataAndStore( std::unique_lock<std::mutex>& /*rGuard*/, std::vector< LockFileEntry >&& aUsersData )
+{
+ if ( !IsValid() )
+ throw io::NotConnectedException();
+
+ if ( !m_xTruncate.is() || !m_xOutputStream.is() || !m_xSeekable.is() )
+ throw uno::RuntimeException();
+
+ m_xTruncate->truncate();
+ m_xSeekable->seek( 0 );
+
+ OUStringBuffer aBuffer;
+ for (const auto & rData : aUsersData)
+ {
+ for ( LockFileComponent nEntryInd : o3tl::enumrange<LockFileComponent>() )
+ {
+ aBuffer.append( EscapeCharacters( rData[nEntryInd] ) );
+ if ( nEntryInd < LockFileComponent::LAST )
+ aBuffer.append( ',' );
+ else
+ aBuffer.append( ';' );
+ }
+ }
+
+ OString aStringData( OUStringToOString( aBuffer, RTL_TEXTENCODING_UTF8 ) );
+ uno::Sequence< sal_Int8 > aData( reinterpret_cast<sal_Int8 const *>(aStringData.getStr()), aStringData.getLength() );
+ m_xOutputStream->writeBytes( aData );
+ m_aUsersData = aUsersData;
+}
+
+
+LockFileEntry ShareControlFile::InsertOwnEntry()
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ if ( !IsValid() )
+ throw io::NotConnectedException();
+
+ GetUsersDataImpl(aGuard);
+ std::vector< LockFileEntry > aNewData( m_aUsersData );
+ LockFileEntry aNewEntry = GenerateOwnEntry();
+
+ bool bExists = false;
+ sal_Int32 nNewInd = 0;
+ for (LockFileEntry & rEntry : m_aUsersData)
+ {
+ if ( rEntry[LockFileComponent::LOCALHOST] == aNewEntry[LockFileComponent::LOCALHOST]
+ && rEntry[LockFileComponent::SYSUSERNAME] == aNewEntry[LockFileComponent::SYSUSERNAME]
+ && rEntry[LockFileComponent::USERURL] == aNewEntry[LockFileComponent::USERURL] )
+ {
+ if ( !bExists )
+ {
+ aNewData[nNewInd] = aNewEntry;
+ bExists = true;
+ }
+ }
+ else
+ {
+ aNewData[nNewInd] = rEntry;
+ }
+
+ nNewInd++;
+ }
+
+ if ( !bExists )
+ aNewData.push_back( aNewEntry );
+
+ SetUsersDataAndStore( aGuard, std::move(aNewData) );
+
+ return aNewEntry;
+}
+
+
+bool ShareControlFile::HasOwnEntry()
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ if ( !IsValid() )
+ {
+ throw io::NotConnectedException();
+ }
+
+ GetUsersDataImpl(aGuard);
+ LockFileEntry aEntry = GenerateOwnEntry();
+
+ for (LockFileEntry & rEntry : m_aUsersData)
+ {
+ if ( rEntry[LockFileComponent::LOCALHOST] == aEntry[LockFileComponent::LOCALHOST] &&
+ rEntry[LockFileComponent::SYSUSERNAME] == aEntry[LockFileComponent::SYSUSERNAME] &&
+ rEntry[LockFileComponent::USERURL] == aEntry[LockFileComponent::USERURL] )
+ {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+
+void ShareControlFile::RemoveEntry()
+{
+ RemoveEntry(GenerateOwnEntry());
+}
+
+void ShareControlFile::RemoveEntry( const LockFileEntry& aEntry )
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ if ( !IsValid() )
+ throw io::NotConnectedException();
+
+ GetUsersDataImpl(aGuard);
+
+ std::vector< LockFileEntry > aNewData;
+
+ for (LockFileEntry & rEntry : m_aUsersData)
+ {
+ if ( rEntry[LockFileComponent::LOCALHOST] != aEntry[LockFileComponent::LOCALHOST]
+ || rEntry[LockFileComponent::SYSUSERNAME] != aEntry[LockFileComponent::SYSUSERNAME]
+ || rEntry[LockFileComponent::USERURL] != aEntry[LockFileComponent::USERURL] )
+ {
+ aNewData.push_back( rEntry );
+ }
+ }
+
+ const bool bNewDataEmpty = aNewData.empty();
+ SetUsersDataAndStore( aGuard, std::move(aNewData) );
+
+ if ( bNewDataEmpty )
+ {
+ // try to remove the file if it is empty
+ RemoveFileImpl(aGuard);
+ }
+}
+
+
+void ShareControlFile::RemoveFile()
+{
+ std::unique_lock aGuard(m_aMutex);
+ return RemoveFileImpl(aGuard);
+}
+
+void ShareControlFile::RemoveFileImpl(std::unique_lock<std::mutex>& /*rGuard*/)
+{
+ if ( !IsValid() )
+ throw io::NotConnectedException();
+
+ Close();
+
+ uno::Reference<ucb::XSimpleFileAccess3> xSimpleFileAccess(ucb::SimpleFileAccess::create(comphelper::getProcessComponentContext()));
+ xSimpleFileAccess->kill( GetURL() );
+}
+
+} // namespace svt
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/misc/sharedstring.cxx b/svl/source/misc/sharedstring.cxx
new file mode 100644
index 0000000000..499136da97
--- /dev/null
+++ b/svl/source/misc/sharedstring.cxx
@@ -0,0 +1,70 @@
+/* -*- 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 <svl/sharedstring.hxx>
+
+namespace svl {
+
+const OUString SharedString::EMPTY_STRING;
+
+const SharedString & SharedString::getEmptyString()
+{
+ // ref-counting traffic associated with SharedString temporaries can be significant,
+ // so use a singleton here, so we can return a const& from getEmptyString.
+ // unicode string array for empty string is globally shared in OUString.
+ // Let's take advantage of that.
+ static const SharedString EMPTY_SHARED_STRING(EMPTY_STRING.pData, EMPTY_STRING.pData);
+ return EMPTY_SHARED_STRING;
+}
+
+SharedString& SharedString::operator= ( const SharedString& r )
+{
+ if(this == &r)
+ return *this;
+
+ if (mpData)
+ rtl_uString_release(mpData);
+ if (mpDataIgnoreCase)
+ rtl_uString_release(mpDataIgnoreCase);
+
+ mpData = r.mpData;
+ mpDataIgnoreCase = r.mpDataIgnoreCase;
+
+ if (mpData)
+ rtl_uString_acquire(mpData);
+ if (mpDataIgnoreCase)
+ rtl_uString_acquire(mpDataIgnoreCase);
+
+ return *this;
+}
+
+bool SharedString::operator== ( const SharedString& r ) const
+{
+ // Compare only the original (not case-folded) string.
+
+ if (mpData == r.mpData)
+ return true;
+
+ if (mpData)
+ {
+ if (!r.mpData)
+ return false;
+
+ if (mpData->length != r.mpData->length)
+ return false;
+
+ return rtl_ustr_reverseCompare_WithLength(mpData->buffer, mpData->length, r.mpData->buffer, r.mpData->length) == 0;
+ }
+
+ return !r.mpData;
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/misc/sharedstringpool.cxx b/svl/source/misc/sharedstringpool.cxx
new file mode 100644
index 0000000000..06cf8a5cef
--- /dev/null
+++ b/svl/source/misc/sharedstringpool.cxx
@@ -0,0 +1,184 @@
+/* -*- 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 <svl/sharedstringpool.hxx>
+#include <svl/sharedstring.hxx>
+#include <unotools/charclass.hxx>
+
+#include <mutex>
+#include <unordered_map>
+#include <unordered_set>
+
+/** create a key class that caches the hashcode */
+namespace
+{
+struct StringWithHash
+{
+ OUString str;
+ sal_Int32 hashCode;
+ StringWithHash(OUString s)
+ : str(s)
+ , hashCode(s.hashCode())
+ {
+ }
+
+ bool operator==(StringWithHash const& rhs) const
+ {
+ if (hashCode != rhs.hashCode)
+ return false;
+ return str == rhs.str;
+ }
+};
+}
+
+namespace std
+{
+template <> struct hash<StringWithHash>
+{
+ std::size_t operator()(const StringWithHash& k) const { return k.hashCode; }
+};
+}
+
+namespace svl
+{
+namespace
+{
+sal_Int32 getRefCount(const rtl_uString* p) { return (p->refCount & 0x3FFFFFFF); }
+}
+
+struct SharedStringPool::Impl
+{
+ mutable std::mutex maMutex;
+ // We use this map for two purposes - to store lower->upper case mappings
+ // and to retrieve a shared uppercase object, so the management logic
+ // is quite complex.
+ std::unordered_map<StringWithHash, OUString> maStrMap;
+ const CharClass& mrCharClass;
+
+ explicit Impl(const CharClass& rCharClass)
+ : mrCharClass(rCharClass)
+ {
+ }
+};
+
+SharedStringPool::SharedStringPool(const CharClass& rCharClass)
+ : mpImpl(new Impl(rCharClass))
+{
+ // make sure the one empty string instance is shared in this pool as well
+ intern(SharedString::EMPTY_STRING);
+ assert(intern(SharedString::EMPTY_STRING) == SharedString::getEmptyString());
+}
+
+SharedStringPool::~SharedStringPool() {}
+
+SharedString SharedStringPool::intern(const OUString& rStr)
+{
+ StringWithHash aStrWithHash(rStr);
+ std::scoped_lock<std::mutex> aGuard(mpImpl->maMutex);
+
+ auto[mapIt, bInserted] = mpImpl->maStrMap.emplace(aStrWithHash, rStr);
+ if (!bInserted)
+ // there is already a mapping
+ return SharedString(mapIt->first.str.pData, mapIt->second.pData);
+
+ // This is a new string insertion. Establish mapping to upper-case variant.
+ OUString aUpper = mpImpl->mrCharClass.uppercase(rStr);
+ if (aUpper == rStr)
+ // no need to do anything more, because we inserted an upper->upper mapping
+ return SharedString(mapIt->first.str.pData, mapIt->second.pData);
+
+ // We need to insert a lower->upper mapping, so also insert
+ // an upper->upper mapping, which we can use both for when an upper string
+ // is interned, and to look up a shared upper string.
+ StringWithHash aUpperWithHash(aUpper);
+ auto mapIt2 = mpImpl->maStrMap.find(aUpperWithHash);
+ if (mapIt2 != mpImpl->maStrMap.end())
+ {
+ // there is an already existing upper string
+ mapIt->second = mapIt2->first.str;
+ return SharedString(mapIt->first.str.pData, mapIt->second.pData);
+ }
+
+ // There is no already existing upper string.
+ // First, update using the iterator, can't do this later because
+ // the iterator will be invalid.
+ mapIt->second = aUpper;
+ mpImpl->maStrMap.emplace_hint(mapIt2, aUpperWithHash, aUpper);
+ return SharedString(rStr.pData, aUpper.pData);
+}
+
+void SharedStringPool::purge()
+{
+ std::scoped_lock<std::mutex> aGuard(mpImpl->maMutex);
+
+ // Because we can have an uppercase entry mapped to itself,
+ // and then a bunch of lowercase entries mapped to that same
+ // upper-case entry, we need to scan the map twice - the first
+ // time to remove lowercase entries, and then only can we
+ // check for unused uppercase entries.
+
+ auto it = mpImpl->maStrMap.begin();
+ auto itEnd = mpImpl->maStrMap.end();
+ while (it != itEnd)
+ {
+ rtl_uString* p1 = it->first.str.pData;
+ rtl_uString* p2 = it->second.pData;
+ if (p1 != p2)
+ {
+ // normal case - lowercase mapped to uppercase, which
+ // means that the lowercase entry has one ref-counted
+ // entry as the key in the map
+ if (getRefCount(p1) == 1)
+ {
+ it = mpImpl->maStrMap.erase(it);
+ continue;
+ }
+ }
+ ++it;
+ }
+
+ it = mpImpl->maStrMap.begin();
+ itEnd = mpImpl->maStrMap.end();
+ while (it != itEnd)
+ {
+ rtl_uString* p1 = it->first.str.pData;
+ rtl_uString* p2 = it->second.pData;
+ if (p1 == p2)
+ {
+ // uppercase which is mapped to itself, which means
+ // one ref-counted entry as the key in the map, and
+ // one ref-counted entry in the value in the map
+ if (getRefCount(p1) == 2)
+ {
+ it = mpImpl->maStrMap.erase(it);
+ continue;
+ }
+ }
+ ++it;
+ }
+}
+
+size_t SharedStringPool::getCount() const
+{
+ std::scoped_lock<std::mutex> aGuard(mpImpl->maMutex);
+ return mpImpl->maStrMap.size();
+}
+
+size_t SharedStringPool::getCountIgnoreCase() const
+{
+ std::scoped_lock<std::mutex> aGuard(mpImpl->maMutex);
+ // this is only called from unit tests, so no need to be efficient
+ std::unordered_set<OUString> aUpperSet;
+ for (auto const& pair : mpImpl->maStrMap)
+ aUpperSet.insert(pair.second);
+ return aUpperSet.size();
+}
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/misc/strmadpt.cxx b/svl/source/misc/strmadpt.cxx
new file mode 100644
index 0000000000..7a755d9249
--- /dev/null
+++ b/svl/source/misc/strmadpt.cxx
@@ -0,0 +1,653 @@
+/* -*- 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 <algorithm>
+#include <limits>
+#include <set>
+#include <string.h>
+
+#include <com/sun/star/io/IOException.hpp>
+#include <com/sun/star/io/XInputStream.hpp>
+#include <com/sun/star/io/XOutputStream.hpp>
+#include <com/sun/star/io/XSeekable.hpp>
+#include <o3tl/safeint.hxx>
+#include <osl/diagnose.h>
+#include <svl/instrm.hxx>
+#include <svl/outstrm.hxx>
+#include <utility>
+
+using namespace com::sun::star;
+
+class SvDataPipe_Impl
+{
+public:
+ enum SeekResult { SEEK_BEFORE_MARKED, SEEK_OK, SEEK_PAST_END };
+
+private:
+ struct Page
+ {
+ Page * m_pPrev;
+ Page * m_pNext;
+ sal_Int8 * m_pStart;
+ sal_Int8 * m_pRead;
+ sal_Int8 * m_pEnd;
+ sal_uInt32 m_nOffset;
+ sal_Int8 m_aBuffer[1];
+ };
+ static const sal_uInt32 m_nPageSize = 1000;
+
+ std::multiset< sal_uInt32 > m_aMarks;
+ Page * m_pFirstPage;
+ Page * m_pReadPage;
+ Page * m_pWritePage;
+ sal_Int8 * m_pReadBuffer;
+ sal_uInt32 m_nReadBufferSize;
+ sal_uInt32 m_nReadBufferFilled;
+ sal_uInt32 m_nPages;
+ bool m_bEOF;
+
+ void remove(Page * pPage);
+
+public:
+ inline SvDataPipe_Impl();
+
+ ~SvDataPipe_Impl();
+
+ inline void setReadBuffer(sal_Int8 * pBuffer, sal_uInt32 nSize);
+
+ sal_uInt32 read();
+
+ void clearReadBuffer() { m_pReadBuffer = nullptr; }
+
+ void write(sal_Int8 const * pBuffer, sal_uInt32 nSize);
+
+ void setEOF() { m_bEOF = true; }
+
+ inline bool isEOF() const;
+
+ SeekResult setReadPosition(sal_uInt32 nPosition);
+};
+
+SvDataPipe_Impl::SvDataPipe_Impl()
+ : m_pFirstPage( nullptr )
+ , m_pReadPage( nullptr )
+ , m_pWritePage( nullptr )
+ , m_pReadBuffer( nullptr )
+ , m_nReadBufferSize( 0 )
+ , m_nReadBufferFilled( 0 )
+ , m_nPages( 0 )
+ , m_bEOF( false )
+{}
+
+inline void SvDataPipe_Impl::setReadBuffer(sal_Int8 * pBuffer,
+ sal_uInt32 nSize)
+{
+ m_pReadBuffer = pBuffer;
+ m_nReadBufferSize = nSize;
+ m_nReadBufferFilled = 0;
+}
+
+inline bool SvDataPipe_Impl::isEOF() const
+{
+ return m_bEOF && m_pReadPage == m_pWritePage
+ && (!m_pReadPage || m_pReadPage->m_pRead == m_pReadPage->m_pEnd);
+}
+
+
+
+// SvInputStream
+
+bool SvInputStream::open()
+{
+ if (GetError() != ERRCODE_NONE)
+ return false;
+ if (!(m_xSeekable.is() || m_pPipe))
+ {
+ if (!m_xStream.is())
+ {
+ SetError(ERRCODE_IO_INVALIDDEVICE);
+ return false;
+ }
+ m_xSeekable.set(m_xStream, uno::UNO_QUERY);
+ if (!m_xSeekable.is())
+ m_pPipe.reset( new SvDataPipe_Impl );
+ }
+ return true;
+}
+
+// virtual
+std::size_t SvInputStream::GetData(void * pData, std::size_t const nSize)
+{
+ if (!open())
+ {
+ SetError(ERRCODE_IO_CANTREAD);
+ return 0;
+ }
+ // check if a truncated STREAM_SEEK_TO_END was passed
+ assert(m_nSeekedFrom != SAL_MAX_UINT32);
+ sal_uInt32 nRead = 0;
+ if (m_xSeekable.is())
+ {
+ if (m_nSeekedFrom != STREAM_SEEK_TO_END)
+ {
+ try
+ {
+ m_xSeekable->seek(m_nSeekedFrom);
+ }
+ catch (const io::IOException&)
+ {
+ SetError(ERRCODE_IO_CANTREAD);
+ return 0;
+ }
+ m_nSeekedFrom = STREAM_SEEK_TO_END;
+ }
+ for (;;)
+ {
+ sal_Int32 nRemain
+ = sal_Int32(
+ std::min(std::size_t(nSize - nRead),
+ std::size_t(std::numeric_limits<sal_Int32>::max())));
+ if (nRemain == 0)
+ break;
+ uno::Sequence< sal_Int8 > aBuffer;
+ sal_Int32 nCount;
+ try
+ {
+ nCount = m_xStream->readBytes(aBuffer, nRemain);
+ }
+ catch (const io::IOException&)
+ {
+ SetError(ERRCODE_IO_CANTREAD);
+ return nRead;
+ }
+ memcpy(static_cast< sal_Int8 * >(pData) + nRead,
+ aBuffer.getConstArray(), sal_uInt32(nCount));
+ nRead += nCount;
+ if (nCount < nRemain)
+ break;
+ }
+ }
+ else
+ {
+ if (m_nSeekedFrom != STREAM_SEEK_TO_END)
+ {
+ SetError(ERRCODE_IO_CANTREAD);
+ return 0;
+ }
+ m_pPipe->setReadBuffer(static_cast< sal_Int8 * >(pData), nSize);
+ nRead = m_pPipe->read();
+ if (nRead < nSize && !m_pPipe->isEOF())
+ for (;;)
+ {
+ sal_Int32 nRemain
+ = sal_Int32(
+ std::min(
+ std::size_t(nSize - nRead),
+ std::size_t(std::numeric_limits<sal_Int32>::max())));
+ if (nRemain == 0)
+ break;
+ uno::Sequence< sal_Int8 > aBuffer;
+ sal_Int32 nCount;
+ try
+ {
+ nCount = m_xStream->readBytes(aBuffer, nRemain);
+ }
+ catch (const io::IOException&)
+ {
+ SetError(ERRCODE_IO_CANTREAD);
+ break;
+ }
+ m_pPipe->write(aBuffer.getConstArray(), sal_uInt32(nCount));
+ nRead += m_pPipe->read();
+ if (nCount < nRemain)
+ {
+ m_xStream->closeInput();
+ m_pPipe->setEOF();
+ break;
+ }
+ }
+ m_pPipe->clearReadBuffer();
+ }
+ return nRead;
+}
+
+// virtual
+std::size_t SvInputStream::PutData(void const *, std::size_t)
+{
+ SetError(ERRCODE_IO_NOTSUPPORTED);
+ return 0;
+}
+
+// virtual
+void SvInputStream::FlushData()
+{}
+
+// virtual
+sal_uInt64 SvInputStream::SeekPos(sal_uInt64 const nPos)
+{
+ // check if a truncated STREAM_SEEK_TO_END was passed
+ assert(nPos != SAL_MAX_UINT32);
+ if (open())
+ {
+ if (nPos == STREAM_SEEK_TO_END)
+ {
+ if (m_nSeekedFrom == STREAM_SEEK_TO_END)
+ {
+ if (m_xSeekable.is())
+ try
+ {
+ sal_Int64 nLength = m_xSeekable->getLength();
+ OSL_ASSERT(nLength >= 0);
+ if (o3tl::make_unsigned(nLength)
+ < STREAM_SEEK_TO_END)
+ {
+ m_nSeekedFrom = Tell();
+ return sal_uInt64(nLength);
+ }
+ }
+ catch (const io::IOException&)
+ {
+ }
+ else
+ return Tell(); //@@@
+ }
+ else
+ return Tell();
+ }
+ else if (nPos == m_nSeekedFrom)
+ {
+ m_nSeekedFrom = STREAM_SEEK_TO_END;
+ return nPos;
+ }
+ else if (m_xSeekable.is())
+ {
+ try
+ {
+ m_xSeekable->seek(nPos);
+ m_nSeekedFrom = STREAM_SEEK_TO_END;
+ return nPos;
+ }
+ catch (const io::IOException&)
+ {
+ }
+ }
+ else if (m_pPipe->setReadPosition(nPos) == SvDataPipe_Impl::SEEK_OK)
+ {
+ m_nSeekedFrom = STREAM_SEEK_TO_END;
+ return nPos;
+ }
+ else if ( nPos > Tell() )
+ {
+ // Read out the bytes
+ sal_Int32 nRead = nPos - Tell();
+ uno::Sequence< sal_Int8 > aBuffer;
+ m_xStream->readBytes( aBuffer, nRead );
+ return nPos;
+ }
+ else if ( nPos == Tell() )
+ return nPos;
+ }
+ SetError(ERRCODE_IO_CANTSEEK);
+ return Tell();
+}
+
+// virtual
+void SvInputStream::SetSize(sal_uInt64)
+{
+ SetError(ERRCODE_IO_NOTSUPPORTED);
+}
+
+SvInputStream::SvInputStream( css::uno::Reference< css::io::XInputStream > xTheStream):
+ m_xStream(std::move(xTheStream)),
+ m_nSeekedFrom(STREAM_SEEK_TO_END)
+{
+ SetBufferSize(0);
+}
+
+// virtual
+SvInputStream::~SvInputStream()
+{
+ if (m_xStream.is())
+ {
+ try
+ {
+ m_xStream->closeInput();
+ }
+ catch (const io::IOException&)
+ {
+ }
+ }
+}
+
+// SvOutputStream
+
+// virtual
+std::size_t SvOutputStream::GetData(void *, std::size_t)
+{
+ SetError(ERRCODE_IO_NOTSUPPORTED);
+ return 0;
+}
+
+// virtual
+std::size_t SvOutputStream::PutData(void const * pData, std::size_t nSize)
+{
+ if (!m_xStream.is())
+ {
+ SetError(ERRCODE_IO_CANTWRITE);
+ return 0;
+ }
+ std::size_t nWritten = 0;
+ for (;;)
+ {
+ sal_Int32 nRemain
+ = sal_Int32(
+ std::min(std::size_t(nSize - nWritten),
+ std::size_t(std::numeric_limits<sal_Int32>::max())));
+ if (nRemain == 0)
+ break;
+ try
+ {
+ m_xStream->writeBytes(uno::Sequence< sal_Int8 >(
+ static_cast<const sal_Int8 * >(pData)
+ + nWritten,
+ nRemain));
+ }
+ catch (const io::IOException&)
+ {
+ SetError(ERRCODE_IO_CANTWRITE);
+ break;
+ }
+ nWritten += nRemain;
+ }
+ return nWritten;
+}
+
+// virtual
+sal_uInt64 SvOutputStream::SeekPos(sal_uInt64)
+{
+ SetError(ERRCODE_IO_NOTSUPPORTED);
+ return 0;
+}
+
+// virtual
+void SvOutputStream::FlushData()
+{
+ if (!m_xStream.is())
+ {
+ SetError(ERRCODE_IO_INVALIDDEVICE);
+ return;
+ }
+ try
+ {
+ m_xStream->flush();
+ }
+ catch (const io::IOException&)
+ {
+ }
+}
+
+// virtual
+void SvOutputStream::SetSize(sal_uInt64)
+{
+ SetError(ERRCODE_IO_NOTSUPPORTED);
+}
+
+SvOutputStream::SvOutputStream(uno::Reference< io::XOutputStream > xTheStream):
+ m_xStream(std::move(xTheStream))
+{
+ SetBufferSize(0);
+}
+
+// virtual
+SvOutputStream::~SvOutputStream()
+{
+ if (m_xStream.is())
+ {
+ try
+ {
+ m_xStream->closeOutput();
+ }
+ catch (const io::IOException&)
+ {
+ }
+ }
+}
+
+
+// SvDataPipe_Impl
+
+
+void SvDataPipe_Impl::remove(Page * pPage)
+{
+ if (
+ pPage != m_pFirstPage ||
+ m_pReadPage == m_pFirstPage ||
+ (
+ !m_aMarks.empty() &&
+ *m_aMarks.begin() < m_pFirstPage->m_nOffset + m_nPageSize
+ )
+ )
+ {
+ return;
+ }
+
+ m_pFirstPage = m_pFirstPage->m_pNext;
+
+ if (m_nPages <= 100) // min pages
+ return;
+
+ pPage->m_pPrev->m_pNext = pPage->m_pNext;
+ pPage->m_pNext->m_pPrev = pPage->m_pPrev;
+ std::free(pPage);
+ --m_nPages;
+}
+
+SvDataPipe_Impl::~SvDataPipe_Impl()
+{
+ if (m_pFirstPage != nullptr)
+ for (Page * pPage = m_pFirstPage;;)
+ {
+ Page * pNext = pPage->m_pNext;
+ std::free(pPage);
+ if (pNext == m_pFirstPage)
+ break;
+ pPage = pNext;
+ }
+}
+
+sal_uInt32 SvDataPipe_Impl::read()
+{
+ if (m_pReadBuffer == nullptr || m_nReadBufferSize == 0 || m_pReadPage == nullptr)
+ return 0;
+
+ sal_uInt32 nSize = m_nReadBufferSize;
+ sal_uInt32 nRemain = m_nReadBufferSize - m_nReadBufferFilled;
+
+ m_pReadBuffer += m_nReadBufferFilled;
+ m_nReadBufferSize -= m_nReadBufferFilled;
+ m_nReadBufferFilled = 0;
+
+ while (nRemain > 0)
+ {
+ sal_uInt32 nBlock = std::min(sal_uInt32(m_pReadPage->m_pEnd
+ - m_pReadPage->m_pRead),
+ nRemain);
+ memcpy(m_pReadBuffer, m_pReadPage->m_pRead, nBlock);
+ m_pReadPage->m_pRead += nBlock;
+ m_pReadBuffer += nBlock;
+ m_nReadBufferSize -= nBlock;
+ m_nReadBufferFilled = 0;
+ nRemain -= nBlock;
+
+ if (m_pReadPage == m_pWritePage)
+ break;
+
+ if (m_pReadPage->m_pRead == m_pReadPage->m_pEnd)
+ {
+ Page * pRemove = m_pReadPage;
+ m_pReadPage = pRemove->m_pNext;
+ remove(pRemove);
+ }
+ }
+
+ return nSize - nRemain;
+}
+
+void SvDataPipe_Impl::write(sal_Int8 const * pBuffer, sal_uInt32 nSize)
+{
+ if (nSize == 0)
+ return;
+
+ if (m_pWritePage == nullptr)
+ {
+ m_pFirstPage
+ = static_cast< Page * >(std::malloc(sizeof (Page)
+ + m_nPageSize
+ - 1));
+ m_pFirstPage->m_pPrev = m_pFirstPage;
+ m_pFirstPage->m_pNext = m_pFirstPage;
+ m_pFirstPage->m_pStart = m_pFirstPage->m_aBuffer;
+ m_pFirstPage->m_pRead = m_pFirstPage->m_aBuffer;
+ m_pFirstPage->m_pEnd = m_pFirstPage->m_aBuffer;
+ m_pFirstPage->m_nOffset = 0;
+ m_pReadPage = m_pFirstPage;
+ m_pWritePage = m_pFirstPage;
+ ++m_nPages;
+ }
+
+ sal_uInt32 nRemain = nSize;
+
+ if (m_pReadBuffer != nullptr && m_pReadPage == m_pWritePage
+ && m_pReadPage->m_pRead == m_pWritePage->m_pEnd)
+ {
+ sal_uInt32 nBlock = std::min(nRemain,
+ sal_uInt32(m_nReadBufferSize
+ - m_nReadBufferFilled));
+ sal_uInt32 nPosition = m_pWritePage->m_nOffset
+ + (m_pWritePage->m_pEnd
+ - m_pWritePage->m_aBuffer);
+ if (!m_aMarks.empty())
+ nBlock = *m_aMarks.begin() > nPosition ?
+ std::min(nBlock, sal_uInt32(*m_aMarks.begin()
+ - nPosition)) :
+ 0;
+
+ if (nBlock > 0)
+ {
+ memcpy(m_pReadBuffer + m_nReadBufferFilled, pBuffer,
+ nBlock);
+ m_nReadBufferFilled += nBlock;
+ nRemain -= nBlock;
+
+ nPosition += nBlock;
+ m_pWritePage->m_nOffset = (nPosition / m_nPageSize) * m_nPageSize;
+ m_pWritePage->m_pStart = m_pWritePage->m_aBuffer
+ + nPosition % m_nPageSize;
+ m_pWritePage->m_pRead = m_pWritePage->m_pStart;
+ m_pWritePage->m_pEnd = m_pWritePage->m_pStart;
+ }
+ }
+
+ if (nRemain <= 0)
+ return;
+
+ for (;;)
+ {
+ sal_uInt32 nBlock
+ = std::min(sal_uInt32(m_pWritePage->m_aBuffer + m_nPageSize
+ - m_pWritePage->m_pEnd),
+ nRemain);
+ memcpy(m_pWritePage->m_pEnd, pBuffer, nBlock);
+ m_pWritePage->m_pEnd += nBlock;
+ pBuffer += nBlock;
+ nRemain -= nBlock;
+
+ if (nRemain == 0)
+ break;
+
+ if (m_pWritePage->m_pNext == m_pFirstPage)
+ {
+ if (m_nPages == std::numeric_limits< sal_uInt32 >::max())
+ break;
+
+ Page * pNew
+ = static_cast< Page * >(std::malloc(
+ sizeof (Page) + m_nPageSize
+ - 1));
+ pNew->m_pPrev = m_pWritePage;
+ pNew->m_pNext = m_pWritePage->m_pNext;
+
+ m_pWritePage->m_pNext->m_pPrev = pNew;
+ m_pWritePage->m_pNext = pNew;
+ ++m_nPages;
+ }
+
+ m_pWritePage->m_pNext->m_nOffset = m_pWritePage->m_nOffset
+ + m_nPageSize;
+ m_pWritePage = m_pWritePage->m_pNext;
+ m_pWritePage->m_pStart = m_pWritePage->m_aBuffer;
+ m_pWritePage->m_pRead = m_pWritePage->m_aBuffer;
+ m_pWritePage->m_pEnd = m_pWritePage->m_aBuffer;
+ }
+}
+
+SvDataPipe_Impl::SeekResult SvDataPipe_Impl::setReadPosition(sal_uInt32
+ nPosition)
+{
+ if (m_pFirstPage == nullptr)
+ return nPosition == 0 ? SEEK_OK : SEEK_PAST_END;
+
+ if (nPosition
+ <= m_pReadPage->m_nOffset
+ + (m_pReadPage->m_pRead - m_pReadPage->m_aBuffer))
+ {
+ if (nPosition
+ < m_pFirstPage->m_nOffset
+ + (m_pFirstPage->m_pStart - m_pFirstPage->m_aBuffer))
+ return SEEK_BEFORE_MARKED;
+
+ while (nPosition < m_pReadPage->m_nOffset)
+ {
+ m_pReadPage->m_pRead = m_pReadPage->m_pStart;
+ m_pReadPage = m_pReadPage->m_pPrev;
+ }
+ }
+ else
+ {
+ if (nPosition
+ > m_pWritePage->m_nOffset
+ + (m_pWritePage->m_pEnd - m_pWritePage->m_aBuffer))
+ return SEEK_PAST_END;
+
+ while (m_pReadPage != m_pWritePage
+ && nPosition >= m_pReadPage->m_nOffset + m_nPageSize)
+ {
+ Page * pRemove = m_pReadPage;
+ m_pReadPage = pRemove->m_pNext;
+ remove(pRemove);
+ }
+ }
+
+ m_pReadPage->m_pRead = m_pReadPage->m_aBuffer
+ + (nPosition - m_pReadPage->m_nOffset);
+ return SEEK_OK;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/misc/urihelper.cxx b/svl/source/misc/urihelper.cxx
new file mode 100644
index 0000000000..6488edb5bb
--- /dev/null
+++ b/svl/source/misc/urihelper.cxx
@@ -0,0 +1,884 @@
+/* -*- 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 <memory>
+#include <string_view>
+
+#include <sal/config.h>
+
+#include <unicode/idna.h>
+
+#include <svl/urihelper.hxx>
+#include <com/sun/star/ucb/Command.hpp>
+#include <com/sun/star/ucb/IllegalIdentifierException.hpp>
+#include <com/sun/star/ucb/UniversalContentBroker.hpp>
+#include <com/sun/star/ucb/UnsupportedCommandException.hpp>
+#include <com/sun/star/ucb/XCommandEnvironment.hpp>
+#include <com/sun/star/ucb/XCommandProcessor.hpp>
+#include <com/sun/star/ucb/XContent.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/XComponentContext.hpp>
+#include <com/sun/star/uri/UriReferenceFactory.hpp>
+#include <com/sun/star/uri/XUriReference.hpp>
+#include <com/sun/star/uri/XUriReferenceFactory.hpp>
+#include <comphelper/processfactory.hxx>
+#include <osl/diagnose.h>
+#include <rtl/character.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <rtl/ustring.hxx>
+#include <sal/types.h>
+#include <sal/log.hxx>
+#include <tools/inetmime.hxx>
+#include <unotools/charclass.hxx>
+
+using namespace com::sun::star;
+
+OUString URIHelper::SmartRel2Abs(INetURLObject const & rTheBaseURIRef,
+ OUString const & rTheRelURIRef,
+ Link<OUString *, bool> const & rMaybeFileHdl,
+ bool bCheckFileExists,
+ bool bIgnoreFragment,
+ INetURLObject::EncodeMechanism eEncodeMechanism,
+ INetURLObject::DecodeMechanism eDecodeMechanism,
+ rtl_TextEncoding eCharset,
+ FSysStyle eStyle)
+{
+ // Backwards compatibility:
+ if( rTheRelURIRef.startsWith("#") )
+ return rTheRelURIRef;
+
+ INetURLObject aAbsURIRef;
+ if (rTheBaseURIRef.HasError())
+ aAbsURIRef. SetSmartURL(rTheRelURIRef, eEncodeMechanism, eCharset, eStyle);
+ else
+ {
+ bool bWasAbsolute;
+ aAbsURIRef = rTheBaseURIRef.smartRel2Abs(rTheRelURIRef,
+ bWasAbsolute,
+ bIgnoreFragment,
+ eEncodeMechanism,
+ eCharset,
+ false/*bRelativeNonURIs*/,
+ eStyle);
+ if (bCheckFileExists
+ && !bWasAbsolute
+ && (aAbsURIRef.GetProtocol() == INetProtocol::File))
+ {
+ INetURLObject aNonFileURIRef;
+ aNonFileURIRef.SetSmartURL(rTheRelURIRef,
+ eEncodeMechanism,
+ eCharset,
+ eStyle);
+ if (!aNonFileURIRef.HasError()
+ && aNonFileURIRef.GetProtocol() != INetProtocol::File)
+ {
+ bool bMaybeFile = false;
+ if (rMaybeFileHdl.IsSet())
+ {
+ OUString aFilePath(rTheRelURIRef);
+ bMaybeFile = rMaybeFileHdl.Call(&aFilePath);
+ }
+ if (!bMaybeFile)
+ aAbsURIRef = aNonFileURIRef;
+ }
+ }
+ }
+ return aAbsURIRef.GetMainURL(eDecodeMechanism, eCharset);
+}
+
+namespace { Link<OUString *, bool> gMaybeFileHdl; }
+
+void URIHelper::SetMaybeFileHdl(Link<OUString *, bool> const & rTheMaybeFileHdl)
+{
+ gMaybeFileHdl = rTheMaybeFileHdl;
+}
+
+Link<OUString *, bool> const & URIHelper::GetMaybeFileHdl()
+{
+ return gMaybeFileHdl;
+}
+
+namespace {
+
+bool isAbsoluteHierarchicalUriReference(
+ css::uno::Reference< css::uri::XUriReference > const & uriReference)
+{
+ return uriReference.is() && uriReference->isAbsolute()
+ && !uriReference->hasRelativePath();
+}
+
+// To improve performance, assume that if for any prefix URL of a given
+// hierarchical URL either a UCB content cannot be created, or the UCB content
+// does not support the getCasePreservingURL command, then this will hold for
+// any other prefix URL of the given URL, too:
+enum Result { Success, GeneralFailure, SpecificFailure };
+
+Result normalizePrefix( css::uno::Reference< css::ucb::XUniversalContentBroker > const & broker,
+ OUString const & uri, OUString * normalized)
+{
+ OSL_ASSERT(broker.is() && normalized != nullptr);
+ css::uno::Reference< css::ucb::XContent > content;
+ try {
+ content = broker->queryContent(broker->createContentIdentifier(uri));
+ } catch (css::ucb::IllegalIdentifierException &) {}
+ if (!content.is()) {
+ return GeneralFailure;
+ }
+ try {
+ bool ok =
+ (css::uno::Reference< css::ucb::XCommandProcessor >(
+ content, css::uno::UNO_QUERY_THROW)->execute(
+ css::ucb::Command("getCasePreservingURL",
+ -1, css::uno::Any()),
+ 0,
+ css::uno::Reference< css::ucb::XCommandEnvironment >())
+ >>= *normalized);
+ OSL_ASSERT(ok);
+ } catch (css::uno::RuntimeException &) {
+ throw;
+ } catch (css::ucb::UnsupportedCommandException &) {
+ return GeneralFailure;
+ } catch (css::uno::Exception &) {
+ return SpecificFailure;
+ }
+ return Success;
+}
+
+OUString normalize(
+ css::uno::Reference< css::ucb::XUniversalContentBroker > const & broker,
+ css::uno::Reference< css::uri::XUriReferenceFactory > const & uriFactory,
+ OUString const & uriReference)
+{
+ // normalizePrefix can potentially fail (a typically example being a file
+ // URL that denotes a non-existing resource); in such a case, try to
+ // normalize as long a prefix of the given URL as possible (i.e., normalize
+ // all the existing directories within the path):
+ OUString normalized;
+ sal_Int32 n = uriReference.indexOf('#');
+ normalized = n == -1 ? uriReference : uriReference.copy(0, n);
+ switch (normalizePrefix(broker, normalized, &normalized)) {
+ case Success:
+ return n == -1 ? normalized : normalized + uriReference.subView(n);
+ case GeneralFailure:
+ return uriReference;
+ case SpecificFailure:
+ default:
+ break;
+ }
+ css::uno::Reference< css::uri::XUriReference > ref(
+ uriFactory->parse(uriReference));
+ if (!isAbsoluteHierarchicalUriReference(ref)) {
+ return uriReference;
+ }
+ sal_Int32 count = ref->getPathSegmentCount();
+ if (count < 2) {
+ return uriReference;
+ }
+ OUStringBuffer head(ref->getScheme());
+ head.append(':');
+ if (ref->hasAuthority()) {
+ head.append("//" + ref->getAuthority());
+ }
+ for (sal_Int32 i = count - 1; i > 0; --i) {
+ OUStringBuffer buf(head);
+ for (sal_Int32 j = 0; j < i; ++j) {
+ buf.append('/');
+ buf.append(ref->getPathSegment(j));
+ }
+ normalized = buf.makeStringAndClear();
+ if (normalizePrefix(broker, normalized, &normalized) != SpecificFailure)
+ {
+ buf.append(normalized);
+ css::uno::Reference< css::uri::XUriReference > preRef(
+ uriFactory->parse(normalized));
+ if (!isAbsoluteHierarchicalUriReference(preRef)) {
+ // This could only happen if something is inconsistent:
+ break;
+ }
+ sal_Int32 preCount = preRef->getPathSegmentCount();
+ // normalizePrefix may have added or removed a final slash:
+ if (preCount != i) {
+ if (preCount == i - 1) {
+ buf.append('/');
+ } else if (preCount - 1 == i && !buf.isEmpty()
+ && buf[buf.getLength() - 1] == '/')
+ {
+ buf.setLength(buf.getLength() - 1);
+ } else {
+ // This could only happen if something is inconsistent:
+ break;
+ }
+ }
+ for (sal_Int32 j = i; j < count; ++j) {
+ buf.append('/');
+ buf.append(ref->getPathSegment(j));
+ }
+ if (ref->hasQuery()) {
+ buf.append('?');
+ buf.append(ref->getQuery());
+ }
+ if (ref->hasFragment()) {
+ buf.append('#');
+ buf.append(ref->getFragment());
+ }
+ return buf.makeStringAndClear();
+ }
+ }
+ return uriReference;
+}
+
+}
+
+css::uno::Reference< css::uri::XUriReference >
+URIHelper::normalizedMakeRelative(
+ css::uno::Reference< css::uno::XComponentContext > const & context,
+ OUString const & baseUriReference, OUString const & uriReference)
+{
+ OSL_ASSERT(context.is());
+ css::uno::Reference< css::ucb::XUniversalContentBroker > broker(
+ css::ucb::UniversalContentBroker::create(context));
+ css::uno::Reference< css::uri::XUriReferenceFactory > uriFactory(
+ css::uri::UriReferenceFactory::create(context));
+ return uriFactory->makeRelative(
+ uriFactory->parse(normalize(broker, uriFactory, baseUriReference)),
+ uriFactory->parse(normalize(broker, uriFactory, uriReference)), true,
+ true, false);
+}
+
+OUString URIHelper::simpleNormalizedMakeRelative(
+ OUString const & baseUriReference, OUString const & uriReference)
+{
+ css::uno::Reference< css::uri::XUriReference > rel(
+ URIHelper::normalizedMakeRelative(
+ comphelper::getProcessComponentContext(), baseUriReference,
+ uriReference));
+ return rel.is() ? rel->getUriReference() : uriReference;
+}
+
+
+// FindFirstURLInText
+
+
+namespace {
+
+sal_Int32 nextChar(std::u16string_view rStr, sal_Int32 nPos)
+{
+ return rtl::isHighSurrogate(rStr[nPos])
+ && rStr.size() - nPos >= 2
+ && rtl::isLowSurrogate(rStr[nPos + 1]) ?
+ nPos + 2 : nPos + 1;
+}
+
+bool isBoundary1(CharClass const & rCharClass, OUString const & rStr,
+ sal_Int32 nPos, sal_Int32 nEnd)
+{
+ if (nPos == nEnd)
+ return true;
+ if (rCharClass.isLetterNumeric(rStr, nPos))
+ return false;
+ switch (rStr[nPos])
+ {
+ case '$':
+ case '%':
+ case '&':
+ case '-':
+ case '/':
+ case '@':
+ case '\\':
+ return false;
+ default:
+ return true;
+ }
+}
+
+bool isBoundary2(CharClass const & rCharClass, OUString const & rStr,
+ sal_Int32 nPos, sal_Int32 nEnd)
+{
+ if (nPos == nEnd)
+ return true;
+ if (rCharClass.isLetterNumeric(rStr, nPos))
+ return false;
+ switch (rStr[nPos])
+ {
+ case '!':
+ case '#':
+ case '$':
+ case '%':
+ case '&':
+ case '\'':
+ case '*':
+ case '+':
+ case '-':
+ case '/':
+ case '=':
+ case '?':
+ case '@':
+ case '^':
+ case '_':
+ case '`':
+ case '{':
+ case '|':
+ case '}':
+ case '~':
+ return false;
+ default:
+ return true;
+ }
+}
+
+// tdf#145381 Added MatchingBracketDepth counter to detect matching closing
+// brackets that are part of the uri
+bool checkWChar(CharClass const & rCharClass, OUString const & rStr,
+ sal_Int32 * pPos, sal_Int32 * pEnd,
+ sal_Int32 * pMatchingBracketDepth = nullptr,
+ bool bBackslash = false, bool bPipe = false)
+{
+ sal_Unicode c = rStr[*pPos];
+ if (rtl::isAscii(c))
+ {
+ static sal_uInt8 const aMap[128]
+ = { 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 1, 0, 0, 4, 4, 4, 1, // !"#$%&'
+ 5, 6, 1, 1, 1, 4, 1, 4, // ()*+,-./
+ 4, 4, 4, 4, 4, 4, 4, 4, // 01234567
+ 4, 4, 1, 1, 0, 1, 0, 1, // 89:;<=>?
+ 4, 4, 4, 4, 4, 4, 4, 4, // @ABCDEFG
+ 4, 4, 4, 4, 4, 4, 4, 4, // HIJKLMNO
+ 4, 4, 4, 4, 4, 4, 4, 4, // PQRSTUVW
+ 4, 4, 4, 1, 2, 1, 0, 1, // XYZ[\]^_
+ 0, 4, 4, 4, 4, 4, 4, 4, // `abcdefg
+ 4, 4, 4, 4, 4, 4, 4, 4, // hijklmno
+ 4, 4, 4, 4, 4, 4, 4, 4, // pqrstuvw
+ 4, 4, 4, 0, 3, 0, 1, 0 }; // xyz{|}~
+ switch (aMap[c])
+ {
+ default: // not uric
+ return false;
+
+ case 1: // uric
+ ++(*pPos);
+ return true;
+
+ case 2: // "\"
+ if (bBackslash)
+ {
+ *pEnd = ++(*pPos);
+ return true;
+ }
+ else
+ return false;
+
+ case 3: // "|"
+ if (bPipe)
+ {
+ *pEnd = ++(*pPos);
+ return true;
+ }
+ else
+ return false;
+
+ case 4: // alpha, digit, "$", "%", "&", "-", "/", "@" (see
+ // isBoundary1)
+ *pEnd = ++(*pPos);
+ return true;
+
+ case 5: // opening bracket
+ ++(*pPos);
+ if(nullptr != pMatchingBracketDepth)
+ ++(*pMatchingBracketDepth);
+ return true;
+
+ case 6: // closing bracket
+ ++(*pPos);
+ if(nullptr != pMatchingBracketDepth && *pMatchingBracketDepth > 0)
+ {
+ --(*pMatchingBracketDepth);
+ // tdf#145381 When there was an opening bracket, detect this closing bracket
+ // as part of the uri
+ *pEnd = *pPos;
+ }
+ return true;
+
+ }
+ }
+ else if (rCharClass.isLetterNumeric(rStr, *pPos))
+ {
+ *pEnd = *pPos = nextChar(rStr, *pPos);
+ return true;
+ }
+ else
+ return false;
+}
+
+sal_uInt32 scanDomain(OUString const & rStr, sal_Int32 * pPos,
+ sal_Int32 nEnd)
+{
+ sal_Unicode const * pBuffer = rStr.getStr();
+ sal_Unicode const * p = pBuffer + *pPos;
+ sal_uInt32 nLabels = INetURLObject::scanDomain(p, pBuffer + nEnd, false);
+ *pPos = sal::static_int_cast< sal_Int32 >(p - pBuffer);
+ return nLabels;
+}
+
+}
+
+OUString URIHelper::FindFirstURLInText(OUString const & rText,
+ sal_Int32 & rBegin,
+ sal_Int32 & rEnd,
+ CharClass const & rCharClass,
+ INetURLObject::EncodeMechanism eMechanism,
+ rtl_TextEncoding eCharset)
+{
+ if (rBegin > rEnd || rEnd > rText.getLength())
+ return OUString();
+
+ // Search for the first substring of [rBegin..rEnd[ that matches any of the
+ // following productions (for which the appropriate style bit is set in
+ // eStyle, if applicable).
+
+ // 1st Production (known scheme):
+ // \B1 <one of the known schemes, except file> ":" 1*wchar ["#" 1*wchar]
+ // \B1
+
+ // 2nd Production (file):
+ // \B1 "FILE:" 1*(wchar / "\" / "|") ["#" 1*wchar] \B1
+
+ // 3rd Production (ftp):
+ // \B1 "FTP" 2*("." label) ["/" *wchar] ["#" 1*wchar] \B1
+
+ // 4th Production (http):
+ // \B1 "WWW" 2*("." label) ["/" *wchar] ["#" 1*wchar] \B1
+
+ // 5th Production (mailto):
+ // \B2 local-part "@" domain \B1
+
+ // 6th Production (UNC file):
+ // \B1 "\\" domain "\" *(wchar / "\") \B1
+
+ // 7th Production (DOS file):
+ // \B1 ALPHA ":\" *(wchar / "\") \B1
+
+ // 8th Production (Unix-like DOS file):
+ // \B1 ALPHA ":/" *(wchar / "\") \B1
+
+ // The productions use the following auxiliary rules.
+
+ // local-part = atom *("." atom)
+ // atom = 1*(alphanum / "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+"
+ // / "-" / "/" / "=" / "?" / "^" / "_" / "`" / "{" / "|" / "}"
+ // / "~")
+ // domain = label *("." label)
+ // label = alphanum [*(alphanum / "-") alphanum]
+ // alphanum = ALPHA / DIGIT
+ // wchar = <any uric character (ignoring the escaped rule), or "%", or
+ // a letter or digit (according to rCharClass)>
+
+ // "\B1" (boundary 1) stands for the beginning or end of the block of text,
+ // or a character that is neither (a) a letter or digit (according to
+ // rCharClass), nor (b) any of "$", "%", "&", "-", "/", "@", or "\".
+ // (FIXME: What was the rationale for this set of punctuation characters?)
+
+ // "\B2" (boundary 2) stands for the beginning or end of the block of text,
+ // or a character that is neither (a) a letter or digit (according to
+ // rCharClass), nor (b) any of "!", "#", "$", "%", "&", "'", "*", "+", "-",
+ // "/", "=", "?", "@", "^", "_", "`", "{", "|", "}", or "~" (i.e., an RFC
+ // 822 <atom> character, or "@" from \B1's set above).
+
+ // Productions 1--4, and 6--8 try to find a maximum-length match, but they
+ // stop at the first <wchar> character that is a "\B1" character which is
+ // only followed by "\B1" characters (taking "\" and "|" characters into
+ // account appropriately). Production 5 simply tries to find a maximum-
+ // length match.
+
+ // Productions 1--4 use the given eMechanism and eCharset. Productions 5--9
+ // use EncodeMechanism::All.
+
+ // Productions 6--9 are only applicable if the FSysStyle::Dos bit is set in
+ // eStyle.
+
+ // tdf#145381: In addition to the productions I added a mechanism to detect
+ // matching brackets. The task presents the case of an url that ends on a
+ // closing bracket. This needs to be detected as part of the uri in the case
+ // that a matching opening bracket exists.
+
+ bool bBoundary1 = true;
+ bool bBoundary2 = true;
+ for (sal_Int32 nPos = rBegin; nPos != rEnd; nPos = nextChar(rText, nPos))
+ {
+ sal_Unicode c = rText[nPos];
+ if (bBoundary1)
+ {
+ if (rtl::isAsciiAlpha(c))
+ {
+ sal_Int32 i = nPos;
+ INetProtocol eScheme = INetURLObject::CompareProtocolScheme(rText.subView(i, rEnd - i));
+ if (eScheme == INetProtocol::File) // 2nd
+ {
+ while (rText[i++] != ':') ;
+ sal_Int32 nPrefixEnd = i;
+ sal_Int32 nUriEnd = i;
+ while (i != rEnd
+ && checkWChar(rCharClass, rText, &i, &nUriEnd, nullptr, true,
+ true)) ;
+ if (i != nPrefixEnd && i != rEnd && rText[i] == '#')
+ {
+ ++i;
+ while (i != rEnd
+ && checkWChar(rCharClass, rText, &i, &nUriEnd)) ;
+ }
+ if (nUriEnd != nPrefixEnd
+ && isBoundary1(rCharClass, rText, nUriEnd, rEnd))
+ {
+ INetURLObject aUri(rText.subView(nPos, nUriEnd - nPos),
+ INetProtocol::File, eMechanism, eCharset,
+ FSysStyle::Detect);
+ if (!aUri.HasError())
+ {
+ rBegin = nPos;
+ rEnd = nUriEnd;
+ return
+ aUri.GetMainURL(INetURLObject::DecodeMechanism::ToIUri);
+ }
+ }
+ }
+ else if (eScheme != INetProtocol::NotValid) // 1st
+ {
+ while (rText[i++] != ':') ;
+ sal_Int32 nPrefixEnd = i;
+ sal_Int32 nUriEnd = i;
+ sal_Int32 nMatchingBracketDepth = 0;
+ while (i != rEnd
+ && checkWChar(rCharClass, rText, &i, &nUriEnd,
+ &nMatchingBracketDepth)) ;
+ if (i != nPrefixEnd && i != rEnd && rText[i] == '#')
+ {
+ ++i;
+ while (i != rEnd
+ && checkWChar(rCharClass, rText, &i, &nUriEnd)) ;
+ }
+ if (nUriEnd != nPrefixEnd
+ && (isBoundary1(rCharClass, rText, nUriEnd, rEnd)
+ || rText[nUriEnd] == '\\'))
+ {
+ INetURLObject aUri(rText.subView(nPos, nUriEnd - nPos),
+ INetProtocol::Http, eMechanism,
+ eCharset);
+ if (!aUri.HasError())
+ {
+ rBegin = nPos;
+ rEnd = nUriEnd;
+ return
+ aUri.GetMainURL(INetURLObject::DecodeMechanism::ToIUri);
+ }
+ }
+ }
+
+ // 3rd, 4th:
+ i = nPos;
+ sal_uInt32 nLabels = scanDomain(rText, &i, rEnd);
+ if (nLabels >= 3
+ && rText[nPos + 3] == '.'
+ && (((rText[nPos] == 'w'
+ || rText[nPos] == 'W')
+ && (rText[nPos + 1] == 'w'
+ || rText[nPos + 1] == 'W')
+ && (rText[nPos + 2] == 'w'
+ || rText[nPos + 2] == 'W'))
+ || ((rText[nPos] == 'f'
+ || rText[nPos] == 'F')
+ && (rText[nPos + 1] == 't'
+ || rText[nPos + 1] == 'T')
+ && (rText[nPos + 2] == 'p'
+ || rText[nPos + 2] == 'P'))))
+ // (note that rText.GetChar(nPos + 3) is guaranteed to be
+ // valid)
+ {
+ sal_Int32 nUriEnd = i;
+ if (i != rEnd && rText[i] == '/')
+ {
+ nUriEnd = ++i;
+ while (i != rEnd
+ && checkWChar(rCharClass, rText, &i, &nUriEnd)) ;
+ }
+ if (i != rEnd && rText[i] == '#')
+ {
+ ++i;
+ while (i != rEnd
+ && checkWChar(rCharClass, rText, &i, &nUriEnd)) ;
+ }
+ if (isBoundary1(rCharClass, rText, nUriEnd, rEnd)
+ || rText[nUriEnd] == '\\')
+ {
+ INetURLObject aUri(rText.subView(nPos, nUriEnd - nPos),
+ INetProtocol::Http, eMechanism,
+ eCharset);
+ if (!aUri.HasError())
+ {
+ rBegin = nPos;
+ rEnd = nUriEnd;
+ return
+ aUri.GetMainURL(INetURLObject::DecodeMechanism::ToIUri);
+ }
+ }
+ }
+
+ if (rEnd - nPos >= 3
+ && rText[nPos + 1] == ':'
+ && (rText[nPos + 2] == '/'
+ || rText[nPos + 2] == '\\')) // 7th, 8th
+ {
+ i = nPos + 3;
+ sal_Int32 nUriEnd = i;
+ while (i != rEnd
+ && checkWChar(rCharClass, rText, &i, &nUriEnd)) ;
+ if (isBoundary1(rCharClass, rText, nUriEnd, rEnd))
+ {
+ INetURLObject aUri(rText.subView(nPos, nUriEnd - nPos),
+ INetProtocol::File,
+ INetURLObject::EncodeMechanism::All,
+ RTL_TEXTENCODING_UTF8,
+ FSysStyle::Dos);
+ if (!aUri.HasError())
+ {
+ rBegin = nPos;
+ rEnd = nUriEnd;
+ return
+ aUri.GetMainURL(INetURLObject::DecodeMechanism::ToIUri);
+ }
+ }
+ }
+ }
+ else if (rEnd - nPos >= 2
+ && rText[nPos] == '\\'
+ && rText[nPos + 1] == '\\') // 6th
+ {
+ sal_Int32 i = nPos + 2;
+ sal_uInt32 nLabels = scanDomain(rText, &i, rEnd);
+ if (nLabels >= 1 && i != rEnd && rText[i] == '\\')
+ {
+ sal_Int32 nUriEnd = ++i;
+ while (i != rEnd
+ && checkWChar(rCharClass, rText, &i, &nUriEnd,
+ nullptr, true)) ;
+ if (isBoundary1(rCharClass, rText, nUriEnd, rEnd))
+ {
+ INetURLObject aUri(rText.subView(nPos, nUriEnd - nPos),
+ INetProtocol::File,
+ INetURLObject::EncodeMechanism::All,
+ RTL_TEXTENCODING_UTF8,
+ FSysStyle::Dos);
+ if (!aUri.HasError())
+ {
+ rBegin = nPos;
+ rEnd = nUriEnd;
+ return
+ aUri.GetMainURL(INetURLObject::DecodeMechanism::ToIUri);
+ }
+ }
+ }
+ }
+ }
+ if (bBoundary2 && INetMIME::isAtomChar(c)) // 5th
+ {
+ bool bDot = false;
+ for (sal_Int32 i = nPos + 1; i != rEnd; ++i)
+ {
+ sal_Unicode c2 = rText[i];
+ if (INetMIME::isAtomChar(c2))
+ bDot = false;
+ else if (bDot)
+ break;
+ else if (c2 == '.')
+ bDot = true;
+ else
+ {
+ if (c2 == '@')
+ {
+ ++i;
+ sal_uInt32 nLabels = scanDomain(rText, &i, rEnd);
+ if (nLabels >= 1
+ && isBoundary1(rCharClass, rText, i, rEnd))
+ {
+ INetURLObject aUri(rText.subView(nPos, i - nPos),
+ INetProtocol::Mailto,
+ INetURLObject::EncodeMechanism::All);
+ if (!aUri.HasError())
+ {
+ rBegin = nPos;
+ rEnd = i;
+ return aUri.GetMainURL(
+ INetURLObject::DecodeMechanism::ToIUri);
+ }
+ }
+ }
+ break;
+ }
+ }
+ }
+ bBoundary1 = isBoundary1(rCharClass, rText, nPos, rEnd);
+ bBoundary2 = isBoundary2(rCharClass, rText, nPos, rEnd);
+ }
+ rBegin = rEnd;
+ return OUString();
+}
+
+OUString URIHelper::FindFirstDOIInText(OUString const & rText,
+ sal_Int32 & rBegin,
+ sal_Int32 & rEnd,
+ CharClass const & rCharClass)
+{
+ if (rBegin > rEnd || rEnd > rText.getLength())
+ return OUString();
+
+ sal_Int32 start = 7;
+ sal_Int32 count = rEnd-rBegin;
+ OUString candidate(rText.subView(rBegin, count));
+ // Match with regex "doi:10\.\d{4,9}\/[-._;()\/:a-zA-Z0-9]+"
+ if (candidate.startsWithIgnoreAsciiCase("doi:10."))
+ {
+ bool flag = true;
+ sal_Int32 digit = 0;
+ for (sal_Int32 i=start; i<count; i++)
+ {
+ sal_Unicode c = candidate[i];
+ // Match 4 to 9 digits before slash
+ if (digit >= 0)
+ {
+ if (digit>9)
+ {
+ flag = false;
+ break;
+ }
+
+ if ( rCharClass.isDigit(candidate,i) )
+ {
+ digit++;
+ }
+ else if (c=='/' && digit>=4 && i<count-1)
+ {
+ digit=-1;
+ }
+ else
+ {
+ flag = false;
+ break;
+ }
+ }
+ // Match [-._;()\/:a-zA-Z0-9] after slash
+ else if (!( rCharClass.isAlphaNumeric(candidate, i) || c == '.' || c == '-' || c=='_' ||
+ c==';' || c=='(' || c==')' || c=='\\' || (c=='/' && i<count-1) || c==':'))
+ {
+ flag = false;
+ break;
+ }
+ }
+ if (flag && digit==-1)
+ {
+ return OUString::Concat("https://doi.org/")+candidate.subView(4);
+ }
+ }
+ rBegin = rEnd;
+ return OUString();
+}
+
+OUString URIHelper::removePassword(OUString const & rURI,
+ INetURLObject::EncodeMechanism eEncodeMechanism,
+ INetURLObject::DecodeMechanism eDecodeMechanism,
+ rtl_TextEncoding eCharset)
+{
+ INetURLObject aObj(rURI, eEncodeMechanism, eCharset);
+ return aObj.HasError() ?
+ rURI :
+ aObj.GetURLNoPass(eDecodeMechanism, eCharset);
+}
+
+OUString URIHelper::resolveIdnaHost(OUString const & url) {
+ css::uno::Reference<css::uri::XUriReference> uri(
+ css::uri::UriReferenceFactory::create(
+ comphelper::getProcessComponentContext())
+ ->parse(url));
+ if (!(uri.is() && uri->hasAuthority())) {
+ return url;
+ }
+ auto auth(uri->getAuthority());
+ if (auth.isEmpty())
+ return url;
+ sal_Int32 hostStart = auth.indexOf('@') + 1;
+ sal_Int32 hostEnd = auth.getLength();
+ while (hostEnd > hostStart && rtl::isAsciiDigit(auth[hostEnd - 1])) {
+ --hostEnd;
+ }
+ if (hostEnd > hostStart && auth[hostEnd - 1] == ':') {
+ --hostEnd;
+ } else {
+ hostEnd = auth.getLength();
+ }
+ auto asciiOnly = true;
+ for (auto i = hostStart; i != hostEnd; ++i) {
+ if (!rtl::isAscii(auth[i])) {
+ asciiOnly = false;
+ break;
+ }
+ }
+ if (asciiOnly) {
+ // Avoid icu::IDNA case normalization in purely non-IDNA domain names:
+ return url;
+ }
+ UErrorCode e = U_ZERO_ERROR;
+ std::unique_ptr<icu::IDNA> idna(
+ icu::IDNA::createUTS46Instance(
+ (UIDNA_USE_STD3_RULES | UIDNA_CHECK_BIDI | UIDNA_CHECK_CONTEXTJ | UIDNA_CHECK_CONTEXTO),
+ e));
+ if (U_FAILURE(e)) {
+ SAL_WARN("vcl.gdi", "icu::IDNA::createUTS46Instance " << e);
+ return url;
+ }
+ icu::UnicodeString ascii;
+ icu::IDNAInfo info;
+ idna->nameToASCII(
+ icu::UnicodeString(
+ reinterpret_cast<UChar const *>(auth.getStr() + hostStart),
+ hostEnd - hostStart),
+ ascii, info, e);
+ if (U_FAILURE(e) || info.hasErrors()) {
+ return url;
+ }
+ OUStringBuffer buf(uri->getScheme());
+ buf.append(OUString::Concat("://") + auth.subView(0, hostStart));
+ buf.append(
+ reinterpret_cast<sal_Unicode const *>(ascii.getBuffer()),
+ ascii.length());
+ buf.append(auth.subView(hostEnd) + uri->getPath());
+ if (uri->hasQuery()) {
+ buf.append("?" + uri->getQuery());
+ }
+ if (uri->hasFragment()) {
+ buf.append("#" + uri->getFragment());
+ }
+ return buf.makeStringAndClear();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/notify/SfxBroadcaster.cxx b/svl/source/notify/SfxBroadcaster.cxx
new file mode 100644
index 0000000000..419c535f56
--- /dev/null
+++ b/svl/source/notify/SfxBroadcaster.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 <svl/SfxBroadcaster.hxx>
+
+#include <svl/hint.hxx>
+#include <svl/lstner.hxx>
+#include <tools/debug.hxx>
+
+#include <algorithm>
+#include <cassert>
+#include <vector>
+
+// broadcast immediately
+
+void SfxBroadcaster::Broadcast(const SfxHint& rHint)
+{
+ // notify all registered listeners exactly once
+ size_t nSize = m_Listeners.size();
+ for (size_t i = 0; i < nSize; ++i)
+ {
+ SfxListener* const pListener = m_Listeners[i];
+ if (pListener)
+ pListener->Notify(*this, rHint);
+ }
+}
+
+// unregister all listeners
+
+SfxBroadcaster::~SfxBroadcaster() COVERITY_NOEXCEPT_FALSE
+{
+ Broadcast(SfxHint(SfxHintId::Dying));
+
+ // remove all still registered listeners
+ for (size_t i = 0; i < m_Listeners.size(); ++i)
+ {
+ SfxListener* const pListener = m_Listeners[i];
+ if (pListener)
+ pListener->RemoveBroadcaster_Impl(*this);
+ }
+}
+
+// copy ctor of class SfxBroadcaster
+
+SfxBroadcaster::SfxBroadcaster(const SfxBroadcaster& rOther)
+{
+ for (size_t i = 0; i < rOther.m_Listeners.size(); ++i)
+ {
+ SfxListener* const pListener = rOther.m_Listeners[i];
+ if (pListener)
+ pListener->StartListening(*this);
+ }
+}
+
+// add a new SfxListener to the list
+
+void SfxBroadcaster::AddListener(SfxListener& rListener)
+{
+ DBG_TESTSOLARMUTEX();
+ if (m_RemovedPositions.empty())
+ {
+ m_Listeners.push_back(&rListener);
+ }
+ else
+ {
+ size_t targetPosition = m_RemovedPositions.back();
+ m_RemovedPositions.pop_back();
+ assert(m_Listeners[targetPosition] == nullptr);
+ m_Listeners[targetPosition] = &rListener;
+ }
+}
+
+// forward a notification to all registered listeners
+
+void SfxBroadcaster::Forward(SfxBroadcaster& rBC, const SfxHint& rHint)
+{
+ for (size_t i = 0; i < m_Listeners.size(); ++i)
+ {
+ SfxListener* const pListener = m_Listeners[i];
+ if (pListener)
+ pListener->Notify(rBC, rHint);
+ }
+}
+
+// remove one SfxListener from the list
+
+void SfxBroadcaster::RemoveListener(SfxListener& rListener)
+{
+ DBG_TESTSOLARMUTEX();
+
+ // First, check the slots either side of the last removed slot, makes a significant
+ // difference when the list is large.
+ int positionOfRemovedElement = -1;
+ if (!m_RemovedPositions.empty())
+ {
+ auto i = m_RemovedPositions.back();
+ if (i < m_Listeners.size() - 2 && m_Listeners[i + 1] == &rListener)
+ {
+ positionOfRemovedElement = i + 1;
+ }
+ else if (i > 0 && m_Listeners[i - 1] == &rListener)
+ {
+ positionOfRemovedElement = i - 1;
+ }
+ }
+ // then scan the whole list if we didn't find it
+ if (positionOfRemovedElement == -1)
+ {
+ auto aIter = std::find(m_Listeners.begin(), m_Listeners.end(), &rListener);
+ positionOfRemovedElement = std::distance(m_Listeners.begin(), aIter);
+ }
+ // DO NOT erase the listener, set the pointer to 0
+ // because the current continuation may contain this->Broadcast
+ m_Listeners[positionOfRemovedElement] = nullptr;
+ m_RemovedPositions.push_back(positionOfRemovedElement);
+}
+
+void SfxBroadcaster::ForAllListeners(std::function<bool(SfxListener*)> f) const
+{
+ for (size_t i = 0; i < m_Listeners.size(); ++i)
+ {
+ SfxListener* const pListener = m_Listeners[i];
+ if (pListener)
+ if (f(pListener))
+ break;
+ }
+}
+
+bool SfxBroadcaster::HasListeners() const { return GetListenerCount() != 0; }
+
+size_t SfxBroadcaster::GetListenerCount() const
+{
+ return m_Listeners.size() - m_RemovedPositions.size();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/notify/broadcast.cxx b/svl/source/notify/broadcast.cxx
new file mode 100644
index 0000000000..6b323a6693
--- /dev/null
+++ b/svl/source/notify/broadcast.cxx
@@ -0,0 +1,244 @@
+/* -*- 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 <svl/broadcast.hxx>
+#include <svl/listener.hxx>
+#include <svl/hint.hxx>
+#include <o3tl/safeint.hxx>
+#include <cassert>
+#include <algorithm>
+
+/**
+ Design Notes
+ -------------------------------
+
+ This class is extremely heavily used - we can have millions of broadcasters and listeners and we can also
+ have a broadcaster that has a million listeners.
+
+ So we do a number of things
+ (*) use a cache-dense listener list (std::vector) because caching effects dominate a lot of operations
+ (*) use a sorted list to speed up find operations
+ (*) only sort the list when we absolutely have to, to speed up sequential add/remove operations
+ (*) defer removing items from the list by (ab)using the last bit of the pointer
+
+ Also we have some complications around destruction because
+ (*) we broadcast a message to indicate that we are destructing
+ (*) which can trigger arbitrality complicated behaviour, including
+ (*) adding a removing things from the in-destruction object!
+
+*/
+
+static bool isDeletedPtr(SvtListener* p)
+{
+ /** mark deleted entries by toggling the last bit,which is effectively unused, since the struct we point
+ * to is at least 16-bit aligned. This allows the binary search to continue working even when we have
+ * deleted entries */
+ return (reinterpret_cast<uintptr_t>(p) & 0x01) == 0x01;
+}
+
+static void markDeletedPtr(SvtListener*& rp)
+{
+ reinterpret_cast<uintptr_t&>(rp) |= 0x01;
+}
+
+static void sortListeners(std::vector<SvtListener*>& listeners, size_t firstUnsorted)
+{
+ // Add() only appends new values, so often the container will be sorted expect for one
+ // or few last items. For larger containers it is much more efficient to just handle
+ // the unsorted part.
+ auto sortedEnd = firstUnsorted == 0
+ ? std::is_sorted_until(listeners.begin(),listeners.end())
+ : listeners.begin() + firstUnsorted;
+ if( listeners.end() - sortedEnd == 1 )
+ { // Just one item, insert it in the right place.
+ SvtListener* item = listeners.back();
+ listeners.pop_back();
+ listeners.insert( std::upper_bound( listeners.begin(), listeners.end(), item ), item );
+ }
+ else if( o3tl::make_unsigned( sortedEnd - listeners.begin()) > listeners.size() * 3 / 4 )
+ { // Sort the unsorted part and then merge.
+ std::sort( sortedEnd, listeners.end());
+ std::inplace_merge( listeners.begin(), sortedEnd, listeners.end());
+ }
+ else
+ {
+ std::sort(listeners.begin(), listeners.end());
+ }
+}
+
+void SvtBroadcaster::Normalize() const
+{
+ // clear empty slots first, because then we often have to do very little sorting
+ if (mnEmptySlots)
+ {
+ std::erase_if(maListeners, [] (SvtListener* p) { return isDeletedPtr(p); });
+ mnEmptySlots = 0;
+ }
+
+ if (mnListenersFirstUnsorted != static_cast<sal_Int32>(maListeners.size()))
+ {
+ sortListeners(maListeners, mnListenersFirstUnsorted);
+ mnListenersFirstUnsorted = maListeners.size();
+ }
+
+ if (!mbDestNormalized)
+ {
+ sortListeners(maDestructedListeners, 0);
+ mbDestNormalized = true;
+ }
+}
+
+void SvtBroadcaster::Add( SvtListener* p )
+{
+ assert(!mbDisposing && "called inside my own destructor?");
+ assert(!mbAboutToDie && "called after PrepareForDestruction()?");
+ if (mbDisposing || mbAboutToDie)
+ return;
+ // Avoid normalizing if the item appended keeps the container sorted.
+ auto nRealSize = static_cast<sal_Int32>(maListeners.size() - mnEmptySlots);
+ auto bSorted = mnListenersFirstUnsorted == nRealSize;
+ if (maListeners.empty() || (bSorted && maListeners.back() <= p))
+ {
+ ++mnListenersFirstUnsorted;
+ maListeners.push_back(p);
+ return;
+ }
+ // see if we can stuff it into an empty slot, which succeeds surprisingly often in
+ // some calc workloads where it removes and then re-adds the same listener
+ if (mnEmptySlots && bSorted)
+ {
+ auto it = std::lower_bound(maListeners.begin(), maListeners.end(), p);
+ if (it != maListeners.end() && isDeletedPtr(*it))
+ {
+ *it = p;
+ ++mnListenersFirstUnsorted;
+ --mnEmptySlots;
+ return;
+ }
+ }
+ maListeners.push_back(p);
+}
+
+void SvtBroadcaster::Remove( SvtListener* p )
+{
+ if (mbDisposing)
+ return;
+
+ if (mbAboutToDie)
+ {
+ // only reset mbDestNormalized if we are going to become unsorted
+ if (!maDestructedListeners.empty() && maDestructedListeners.back() > p)
+ mbDestNormalized = false;
+ maDestructedListeners.push_back(p);
+ return;
+ }
+
+ // We only need to fully normalize if one or more Add()s have been performed that make the array unsorted.
+ auto nRealSize = static_cast<sal_Int32>(maListeners.size() - mnEmptySlots);
+ if (mnListenersFirstUnsorted != nRealSize || (maListeners.size() > 1024 && maListeners.size() / nRealSize > 16))
+ Normalize();
+
+ auto it = std::lower_bound(maListeners.begin(), maListeners.end(), p);
+ assert (it != maListeners.end() && *it == p);
+ if (it != maListeners.end() && *it == p)
+ {
+ markDeletedPtr(*it);
+ ++mnEmptySlots;
+ --mnListenersFirstUnsorted;
+ }
+
+ if (!HasListeners())
+ ListenersGone();
+}
+
+SvtBroadcaster::SvtBroadcaster( const SvtBroadcaster &rBC ) :
+ mnEmptySlots(0), mnListenersFirstUnsorted(0),
+ mbAboutToDie(false), mbDisposing(false),
+ mbDestNormalized(true)
+{
+ assert(!rBC.mbAboutToDie && "copying an object marked with PrepareForDestruction()?");
+ assert(!rBC.mbDisposing && "copying an object that is in its destructor?");
+
+ rBC.Normalize(); // so that insert into ourself is in-order, and therefore we do not need to Normalize()
+ maListeners.reserve(rBC.maListeners.size());
+ for (SvtListener* p : rBC.maListeners)
+ p->StartListening(*this); // this will call back into this->Add()
+}
+
+SvtBroadcaster::~SvtBroadcaster()
+{
+ mbDisposing = true;
+ Broadcast( SfxHint(SfxHintId::Dying) );
+
+ Normalize();
+
+ // now when both lists are sorted, we can linearly unregister all
+ // listeners, with the exception of those that already asked to be removed
+ // during their own destruction
+ ListenersType::const_iterator dest(maDestructedListeners.begin());
+ for (auto& rpListener : maListeners)
+ {
+ // skip the destructed ones
+ while (dest != maDestructedListeners.end() && (*dest < rpListener))
+ ++dest;
+
+ if (dest == maDestructedListeners.end() || *dest != rpListener)
+ rpListener->BroadcasterDying(*this);
+ }
+}
+
+void SvtBroadcaster::Broadcast( const SfxHint &rHint )
+{
+ Normalize();
+
+ ListenersType::const_iterator dest(maDestructedListeners.begin());
+ ListenersType aListeners(maListeners); // this copy is important to avoid erasing entries while iterating
+ for (auto& rpListener : aListeners)
+ {
+ // skip the destructed ones
+ while (dest != maDestructedListeners.end() && (*dest < rpListener))
+ ++dest;
+
+ if (dest == maDestructedListeners.end() || *dest != rpListener)
+ rpListener->Notify(rHint);
+ }
+}
+
+void SvtBroadcaster::ListenersGone() {}
+
+SvtBroadcaster::ListenersType& SvtBroadcaster::GetAllListeners()
+{
+ Normalize();
+ return maListeners;
+}
+
+const SvtBroadcaster::ListenersType& SvtBroadcaster::GetAllListeners() const
+{
+ Normalize();
+ return maListeners;
+}
+
+void SvtBroadcaster::PrepareForDestruction()
+{
+ mbAboutToDie = true;
+ // the reserve() serves two purpose (1) performance (2) makes sure our iterators do not become invalid
+ maDestructedListeners.reserve(maListeners.size());
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/notify/listener.cxx b/svl/source/notify/listener.cxx
new file mode 100644
index 0000000000..6b1be0ca20
--- /dev/null
+++ b/svl/source/notify/listener.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/.
+ *
+ * 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 <svl/listener.hxx>
+#include <svl/broadcast.hxx>
+
+SvtListener::~SvtListener() COVERITY_NOEXCEPT_FALSE
+{
+ // Unregister itself from all broadcasters it's listening to.
+ EndListeningAll();
+}
+
+// registers at a specific SvtBroadcaster
+
+bool SvtListener::StartListening( SvtBroadcaster& rBroadcaster )
+{
+ std::pair<BroadcastersType::const_iterator, bool> r =
+ maBroadcasters.insert(&rBroadcaster);
+ if (r.second)
+ {
+ // This is a new broadcaster.
+ rBroadcaster.Add(this);
+ }
+ return r.second;
+}
+
+void SvtListener::EndListening( SvtBroadcaster& rBroadcaster )
+{
+ BroadcastersType::const_iterator it = maBroadcasters.find(&rBroadcaster);
+ if (it == maBroadcasters.end())
+ // Not listening to this broadcaster.
+ return;
+ maBroadcasters.erase(it);
+
+ rBroadcaster.Remove(this);
+}
+
+// called from the SvtBroadcaster destructor, used to avoid calling
+// back into the broadcaster again
+void SvtListener::BroadcasterDying( SvtBroadcaster& rBroadcaster )
+{
+ BroadcastersType::const_iterator it = maBroadcasters.find(&rBroadcaster);
+ if (it != maBroadcasters.end())
+ maBroadcasters.erase(it);
+}
+
+void SvtListener::EndListeningAll()
+{
+ for (SvtBroadcaster* p : maBroadcasters)
+ {
+ SvtBroadcaster& rBC = *p;
+ rBC.Remove(this);
+ }
+ maBroadcasters.clear();
+}
+
+
+void SvtListener::CopyAllBroadcasters( const SvtListener& r )
+{
+ EndListeningAll();
+ BroadcastersType aCopy(r.maBroadcasters);
+ maBroadcasters.swap(aCopy);
+ for (SvtBroadcaster* p : maBroadcasters)
+ {
+ p->Add(this);
+ }
+}
+
+void SvtListener::Notify( const SfxHint& /*rHint*/ ) {}
+
+void SvtListener::Query( QueryBase& /*rQuery*/ ) const {}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/notify/lstner.cxx b/svl/source/notify/lstner.cxx
new file mode 100644
index 0000000000..40a59960a0
--- /dev/null
+++ b/svl/source/notify/lstner.cxx
@@ -0,0 +1,163 @@
+/* -*- 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 <svl/lstner.hxx>
+
+#include <svl/SfxBroadcaster.hxx>
+#include <sal/backtrace.hxx>
+#include <sal/log.hxx>
+
+#include <algorithm>
+#include <cassert>
+#include <vector>
+#include <memory>
+
+// copy ctor of class SfxListener
+
+SfxListener::SfxListener( const SfxListener &rOther )
+ : maBCs( rOther.maBCs )
+{
+ for ( size_t n = 0; n < maBCs.size(); ++n )
+ {
+ maBCs[n]->AddListener(*this);
+#ifdef DBG_UTIL
+ maCallStacks.emplace( maBCs[n], sal::backtrace_get(10) );
+#endif
+ }
+}
+
+// unregisters the SfxListener from its SfxBroadcasters
+
+SfxListener::~SfxListener() COVERITY_NOEXCEPT_FALSE
+{
+ // unregister at all remaining broadcasters
+ for ( size_t nPos = 0; nPos < maBCs.size(); ++nPos )
+ {
+ SfxBroadcaster *pBC = maBCs[nPos];
+ pBC->RemoveListener(*this);
+ }
+}
+
+
+// unregisters a specific SfxBroadcaster
+
+void SfxListener::RemoveBroadcaster_Impl( SfxBroadcaster& rBroadcaster )
+{
+ auto it = std::find( maBCs.begin(), maBCs.end(), &rBroadcaster );
+ if (it != maBCs.end()) {
+ maBCs.erase( it );
+#ifdef DBG_UTIL
+ maCallStacks.erase( &rBroadcaster );
+#endif
+ }
+}
+
+
+
+/**
+ Registers a specific SfxBroadcaster.
+
+ Some code uses duplicates as a kind of ref-counting thing i.e. they add and remove listeners
+ on different code paths, and they only really stop listening when the last EndListening() is called.
+*/
+void SfxListener::StartListening(SfxBroadcaster& rBroadcaster, DuplicateHandling eDuplicateHanding)
+{
+ bool bListeningAlready = IsListening( rBroadcaster );
+
+#ifdef DBG_UTIL
+ if (bListeningAlready && eDuplicateHanding == DuplicateHandling::Unexpected)
+ {
+ auto f = maCallStacks.find( &rBroadcaster );
+ SAL_WARN("svl", "previous StartListening call came from: " << sal::backtrace_to_string(f->second.get()));
+ }
+#endif
+ assert(!(bListeningAlready && eDuplicateHanding == DuplicateHandling::Unexpected) && "duplicate listener, try building with DBG_UTIL to find the other insert site.");
+
+ if (!bListeningAlready || eDuplicateHanding != DuplicateHandling::Prevent)
+ {
+ rBroadcaster.AddListener(*this);
+ maBCs.push_back( &rBroadcaster );
+#ifdef DBG_UTIL
+ maCallStacks.emplace( &rBroadcaster, sal::backtrace_get(10) );
+#endif
+ assert(IsListening(rBroadcaster) && "StartListening failed");
+ }
+}
+
+// unregisters a specific SfxBroadcaster
+
+void SfxListener::EndListening( SfxBroadcaster& rBroadcaster, bool bRemoveAllDuplicates )
+{
+ auto beginIt = maBCs.begin();
+ do
+ {
+ auto it = std::find( beginIt, maBCs.end(), &rBroadcaster );
+ if ( it == maBCs.end() )
+ {
+ break;
+ }
+ rBroadcaster.RemoveListener(*this);
+ beginIt = maBCs.erase( it );
+#ifdef DBG_UTIL
+ maCallStacks.erase( &rBroadcaster );
+#endif
+ }
+ while ( bRemoveAllDuplicates );
+}
+
+
+// unregisters all Broadcasters
+
+void SfxListener::EndListeningAll()
+{
+ std::vector<SfxBroadcaster*> aBroadcasters;
+ std::swap(maBCs, aBroadcasters);
+ for (SfxBroadcaster *pBC : aBroadcasters)
+ pBC->RemoveListener(*this);
+#ifdef DBG_UTIL
+ maCallStacks.clear();
+#endif
+}
+
+
+bool SfxListener::IsListening( SfxBroadcaster& rBroadcaster ) const
+{
+ return maBCs.end() != std::find( maBCs.begin(), maBCs.end(), &rBroadcaster );
+}
+
+sal_uInt16 SfxListener::GetBroadcasterCount() const
+{
+ return maBCs.size();
+}
+
+SfxBroadcaster* SfxListener::GetBroadcasterJOE( sal_uInt16 nNo ) const
+{
+ return maBCs[nNo];
+}
+
+
+// base implementation of notification handler
+
+void SfxListener::Notify( SfxBroadcaster& rBroadcaster, const SfxHint& )
+{
+ (void) rBroadcaster;
+ assert(IsListening(rBroadcaster));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/numbers/currencytable.cxx b/svl/source/numbers/currencytable.cxx
new file mode 100644
index 0000000000..84604ecc9f
--- /dev/null
+++ b/svl/source/numbers/currencytable.cxx
@@ -0,0 +1,37 @@
+/* -*- 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 <svl/currencytable.hxx>
+
+NfCurrencyTable::iterator NfCurrencyTable::begin()
+{
+ return maData.begin();
+}
+
+NfCurrencyEntry& NfCurrencyTable::operator[] ( size_t i )
+{
+ return maData[i];
+}
+
+const NfCurrencyEntry& NfCurrencyTable::operator[] ( size_t i ) const
+{
+ return maData[i];
+}
+
+size_t NfCurrencyTable::size() const
+{
+ return maData.size();
+}
+
+void NfCurrencyTable::insert(const iterator& it, NfCurrencyEntry p)
+{
+ maData.insert(it, std::move(p));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/numbers/numfmuno.cxx b/svl/source/numbers/numfmuno.cxx
new file mode 100644
index 0000000000..58094faa54
--- /dev/null
+++ b/svl/source/numbers/numfmuno.cxx
@@ -0,0 +1,987 @@
+/* -*- 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 <tools/color.hxx>
+#include <o3tl/any.hxx>
+#include <osl/mutex.hxx>
+#include <osl/diagnose.h>
+#include <rtl/character.hxx>
+#include <rtl/ustring.hxx>
+
+#include <com/sun/star/util/Date.hpp>
+#include <com/sun/star/util/MalformedNumberFormatException.hpp>
+#include <com/sun/star/util/NotNumericException.hpp>
+#include <com/sun/star/beans/PropertyAttribute.hpp>
+#include <comphelper/propertysequence.hxx>
+#include <cppuhelper/supportsservice.hxx>
+
+#include "numfmuno.hxx"
+#include <svl/numformat.hxx>
+#include <svl/numuno.hxx>
+#include <svl/zforlist.hxx>
+#include <svl/zformat.hxx>
+#include <svl/itemprop.hxx>
+#include <utility>
+
+using namespace com::sun::star;
+
+constexpr OUString PROPERTYNAME_FMTSTR = u"FormatString"_ustr;
+constexpr OUString PROPERTYNAME_LOCALE = u"Locale"_ustr;
+constexpr OUString PROPERTYNAME_TYPE = u"Type"_ustr;
+constexpr OUString PROPERTYNAME_COMMENT = u"Comment"_ustr;
+constexpr OUString PROPERTYNAME_CURREXT = u"CurrencyExtension"_ustr;
+constexpr OUString PROPERTYNAME_CURRSYM = u"CurrencySymbol"_ustr;
+constexpr OUString PROPERTYNAME_CURRABB = u"CurrencyAbbreviation"_ustr;
+constexpr OUString PROPERTYNAME_DECIMALS = u"Decimals"_ustr;
+constexpr OUString PROPERTYNAME_LEADING = u"LeadingZeros"_ustr;
+constexpr OUString PROPERTYNAME_NEGRED = u"NegativeRed"_ustr;
+constexpr OUString PROPERTYNAME_STDFORM = u"StandardFormat"_ustr;
+constexpr OUString PROPERTYNAME_THOUS = u"ThousandsSeparator"_ustr;
+constexpr OUString PROPERTYNAME_USERDEF = u"UserDefined"_ustr;
+
+constexpr OUString PROPERTYNAME_NOZERO = u"NoZero"_ustr;
+constexpr OUString PROPERTYNAME_NULLDATE = u"NullDate"_ustr;
+constexpr OUString PROPERTYNAME_STDDEC = u"StandardDecimals"_ustr;
+constexpr OUString PROPERTYNAME_TWODIGIT = u"TwoDigitDateStart"_ustr;
+
+// All without a Which-ID, Map only for PropertySetInfo
+
+static std::span<const SfxItemPropertyMapEntry> lcl_GetNumberFormatPropertyMap()
+{
+ static const SfxItemPropertyMapEntry aNumberFormatPropertyMap_Impl[] =
+ {
+ {PROPERTYNAME_FMTSTR, 0, cppu::UnoType<OUString>::get(), beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY, 0},
+ {PROPERTYNAME_LOCALE, 0, cppu::UnoType<lang::Locale>::get(),beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY, 0},
+ {PROPERTYNAME_TYPE, 0, cppu::UnoType<sal_Int16>::get(), beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY, 0},
+ {PROPERTYNAME_COMMENT, 0, cppu::UnoType<OUString>::get(), beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY, 0},
+ {PROPERTYNAME_CURREXT, 0, cppu::UnoType<OUString>::get(), beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY, 0},
+ {PROPERTYNAME_CURRSYM, 0, cppu::UnoType<OUString>::get(), beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY, 0},
+ {PROPERTYNAME_DECIMALS, 0, cppu::UnoType<sal_Int16>::get(), beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY, 0},
+ {PROPERTYNAME_LEADING, 0, cppu::UnoType<sal_Int16>::get(), beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY, 0},
+ {PROPERTYNAME_NEGRED, 0, cppu::UnoType<bool>::get(), beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY, 0},
+ {PROPERTYNAME_STDFORM, 0, cppu::UnoType<bool>::get(), beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY, 0},
+ {PROPERTYNAME_THOUS, 0, cppu::UnoType<bool>::get(), beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY, 0},
+ {PROPERTYNAME_USERDEF, 0, cppu::UnoType<bool>::get(), beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY, 0},
+ {PROPERTYNAME_CURRABB, 0, cppu::UnoType<OUString>::get(), beans::PropertyAttribute::BOUND | beans::PropertyAttribute::READONLY, 0},
+ };
+ return aNumberFormatPropertyMap_Impl;
+}
+
+static std::span<const SfxItemPropertyMapEntry> lcl_GetNumberSettingsPropertyMap()
+{
+ static const SfxItemPropertyMapEntry aNumberSettingsPropertyMap_Impl[] =
+ {
+ {PROPERTYNAME_NOZERO, 0, cppu::UnoType<bool>::get(), beans::PropertyAttribute::BOUND, 0},
+ {PROPERTYNAME_NULLDATE, 0, cppu::UnoType<util::Date>::get(), beans::PropertyAttribute::BOUND, 0},
+ {PROPERTYNAME_STDDEC, 0, cppu::UnoType<sal_Int16>::get(), beans::PropertyAttribute::BOUND, 0},
+ {PROPERTYNAME_TWODIGIT, 0, cppu::UnoType<sal_Int16>::get(), beans::PropertyAttribute::BOUND, 0},
+ };
+ return aNumberSettingsPropertyMap_Impl;
+}
+
+static LanguageType lcl_GetLanguage( const lang::Locale& rLocale )
+{
+ LanguageType eRet = LanguageTag::convertToLanguageTypeWithFallback( rLocale );
+ if ( eRet == LANGUAGE_NONE )
+ eRet = LANGUAGE_SYSTEM; //! or throw an exception?
+
+ return eRet;
+}
+
+SvNumberFormatterServiceObj::SvNumberFormatterServiceObj()
+{
+}
+
+SvNumberFormatterServiceObj::~SvNumberFormatterServiceObj()
+{
+}
+
+// XNumberFormatter
+
+void SAL_CALL SvNumberFormatterServiceObj::attachNumberFormatsSupplier( const uno::Reference<util::XNumberFormatsSupplier>& _xSupplier )
+{
+ ::rtl::Reference< SvNumberFormatsSupplierObj > xAutoReleaseOld;
+
+ // SYNCHRONIZED ->
+ {
+ ::osl::MutexGuard aGuard( ::osl::Mutex::getGlobalMutex() );
+
+ SvNumberFormatsSupplierObj* pNew = comphelper::getFromUnoTunnel<SvNumberFormatsSupplierObj>( _xSupplier );
+ if (!pNew)
+ throw uno::RuntimeException(); // wrong object
+
+ xAutoReleaseOld = xSupplier;
+
+ xSupplier = pNew;
+ m_aMutex = xSupplier->getSharedMutex();
+ }
+ // <- SYNCHRONIZED
+}
+
+uno::Reference<util::XNumberFormatsSupplier> SAL_CALL SvNumberFormatterServiceObj::getNumberFormatsSupplier()
+{
+ ::osl::MutexGuard aGuard( m_aMutex );
+ return xSupplier;
+}
+
+sal_Int32 SAL_CALL SvNumberFormatterServiceObj::detectNumberFormat( sal_Int32 nKey, const OUString& aString )
+{
+ ::osl::MutexGuard aGuard( m_aMutex );
+
+ SvNumberFormatter* pFormatter = xSupplier.is() ? xSupplier->GetNumberFormatter() : nullptr;
+ if (!pFormatter)
+ throw uno::RuntimeException();
+
+ sal_uInt32 nUKey = nKey;
+ double fValue = 0.0;
+ if ( !pFormatter->IsNumberFormat(aString, nUKey, fValue) )
+ throw util::NotNumericException();
+
+ return nUKey;
+}
+
+double SAL_CALL SvNumberFormatterServiceObj::convertStringToNumber( sal_Int32 nKey, const OUString& aString )
+{
+ ::osl::MutexGuard aGuard( m_aMutex );
+
+ SvNumberFormatter* pFormatter = xSupplier.is() ? xSupplier->GetNumberFormatter() : nullptr;
+ if (!pFormatter)
+ throw uno::RuntimeException();
+
+ sal_uInt32 nUKey = nKey;
+ double fValue = 0.0;
+ if ( !pFormatter->IsNumberFormat(aString, nUKey, fValue) )
+ throw util::NotNumericException();
+
+ return fValue;
+}
+
+OUString SAL_CALL SvNumberFormatterServiceObj::convertNumberToString( sal_Int32 nKey, double fValue )
+{
+ ::osl::MutexGuard aGuard( m_aMutex );
+
+ OUString aRet;
+ SvNumberFormatter* pFormatter = xSupplier.is() ? xSupplier->GetNumberFormatter() : nullptr;
+ if (!pFormatter)
+ throw uno::RuntimeException();
+
+ const Color* pColor = nullptr;
+ pFormatter->GetOutputString(fValue, nKey, aRet, &pColor);
+
+ return aRet;
+}
+
+sal_Int32 SAL_CALL SvNumberFormatterServiceObj::queryColorForNumber( sal_Int32 nKey,
+ double fValue,
+ sal_Int32 aDefaultColor )
+{
+ ::osl::MutexGuard aGuard( m_aMutex );
+
+ util::Color nRet = aDefaultColor; // color = sal_Int32
+ SvNumberFormatter* pFormatter = xSupplier.is() ? xSupplier->GetNumberFormatter() : nullptr;
+ if (!pFormatter)
+ throw uno::RuntimeException();
+
+ OUString aStr;
+ const Color* pColor = nullptr;
+ pFormatter->GetOutputString(fValue, nKey, aStr, &pColor);
+ if (pColor)
+ nRet = sal_uInt32(*pColor);
+ // Else keep Default
+
+ return nRet;
+}
+
+OUString SAL_CALL SvNumberFormatterServiceObj::formatString( sal_Int32 nKey,
+ const OUString& aString )
+{
+ ::osl::MutexGuard aGuard( m_aMutex );
+
+ OUString aRet;
+ SvNumberFormatter* pFormatter = xSupplier.is() ? xSupplier->GetNumberFormatter() : nullptr;
+ if (!pFormatter)
+ {
+ throw uno::RuntimeException();
+ }
+
+ const Color* pColor = nullptr;
+ pFormatter->GetOutputString(aString, nKey, aRet, &pColor);
+
+ return aRet;
+}
+
+sal_Int32 SAL_CALL SvNumberFormatterServiceObj::queryColorForString( sal_Int32 nKey,
+ const OUString& aString,
+ sal_Int32 aDefaultColor )
+{
+ ::osl::MutexGuard aGuard( m_aMutex );
+
+ util::Color nRet = aDefaultColor; // color = sal_Int32
+ SvNumberFormatter* pFormatter = xSupplier.is() ? xSupplier->GetNumberFormatter() : nullptr;
+ if (!pFormatter)
+ {
+ throw uno::RuntimeException();
+ }
+
+ OUString aStr;
+ const Color* pColor = nullptr;
+ pFormatter->GetOutputString(aString, nKey, aStr, &pColor);
+ if (pColor)
+ {
+ nRet = sal_uInt32(*pColor);
+ }
+ // Else keep Default
+
+ return nRet;
+}
+
+OUString SAL_CALL SvNumberFormatterServiceObj::getInputString( sal_Int32 nKey, double fValue )
+{
+ ::osl::MutexGuard aGuard( m_aMutex );
+
+ OUString aRet;
+ SvNumberFormatter* pFormatter = xSupplier.is() ? xSupplier->GetNumberFormatter() : nullptr;
+ if (!pFormatter)
+ throw uno::RuntimeException();
+
+ pFormatter->GetInputLineString(fValue, nKey, aRet);
+
+ return aRet;
+}
+
+// XNumberFormatPreviewer
+
+OUString SAL_CALL SvNumberFormatterServiceObj::convertNumberToPreviewString( const OUString& aFormat,
+ double fValue,
+ const lang::Locale& nLocale,
+ sal_Bool bAllowEnglish )
+{
+ ::osl::MutexGuard aGuard( m_aMutex );
+
+ OUString aRet;
+ SvNumberFormatter* pFormatter = xSupplier.is() ? xSupplier->GetNumberFormatter() : nullptr;
+ if (!pFormatter)
+ throw uno::RuntimeException();
+
+ LanguageType eLang = lcl_GetLanguage( nLocale );
+ const Color* pColor = nullptr;
+
+ bool bOk;
+ if ( bAllowEnglish )
+ bOk = pFormatter->GetPreviewStringGuess( aFormat, fValue, aRet, &pColor, eLang );
+ else
+ bOk = pFormatter->GetPreviewString( aFormat, fValue, aRet, &pColor, eLang );
+
+ if (!bOk)
+ throw util::MalformedNumberFormatException();
+
+ return aRet;
+}
+
+sal_Int32 SAL_CALL SvNumberFormatterServiceObj::queryPreviewColorForNumber( const OUString& aFormat,
+ double fValue,
+ const lang::Locale& nLocale,
+ sal_Bool bAllowEnglish,
+ sal_Int32 aDefaultColor )
+{
+ ::osl::MutexGuard aGuard( m_aMutex );
+
+ util::Color nRet = aDefaultColor; // color = sal_Int32
+ SvNumberFormatter* pFormatter = xSupplier.is() ? xSupplier->GetNumberFormatter() : nullptr;
+ if (!pFormatter)
+ throw uno::RuntimeException();
+
+ OUString aOutString;
+ LanguageType eLang = lcl_GetLanguage( nLocale );
+ const Color* pColor = nullptr;
+
+ bool bOk;
+ if ( bAllowEnglish )
+ bOk = pFormatter->GetPreviewStringGuess( aFormat, fValue, aOutString, &pColor, eLang );
+ else
+ bOk = pFormatter->GetPreviewString( aFormat, fValue, aOutString, &pColor, eLang );
+
+ if (!bOk)
+ throw util::MalformedNumberFormatException();
+
+ if (pColor)
+ nRet = sal_uInt32(*pColor);
+ // Else keep Default
+
+ return nRet;
+}
+
+// XServiceInfo
+
+OUString SAL_CALL SvNumberFormatterServiceObj::getImplementationName()
+{
+ return "com.sun.star.uno.util.numbers.SvNumberFormatterServiceObject";
+}
+
+sal_Bool SAL_CALL SvNumberFormatterServiceObj::supportsService( const OUString& ServiceName )
+{
+ return cppu::supportsService( this, ServiceName );
+}
+
+uno::Sequence<OUString> SAL_CALL SvNumberFormatterServiceObj::getSupportedServiceNames()
+{
+ return { "com.sun.star.util.NumberFormatter" };
+}
+
+SvNumberFormatsObj::SvNumberFormatsObj( SvNumberFormatsSupplierObj& _rParent, ::comphelper::SharedMutex _aMutex )
+ :m_xSupplier( &_rParent )
+ ,m_aMutex(std::move( _aMutex ))
+{
+}
+
+SvNumberFormatsObj::~SvNumberFormatsObj()
+{
+}
+
+// XNumberFormats
+
+uno::Reference<beans::XPropertySet> SAL_CALL SvNumberFormatsObj::getByKey( sal_Int32 nKey )
+{
+ ::osl::MutexGuard aGuard( m_aMutex );
+
+ SvNumberFormatter* pFormatter = m_xSupplier->GetNumberFormatter();
+ const SvNumberformat* pFormat = pFormatter ? pFormatter->GetEntry(nKey) : nullptr;
+ if (!pFormat)
+ throw uno::RuntimeException();
+
+ return new SvNumberFormatObj( *m_xSupplier, nKey, m_aMutex );
+}
+
+uno::Sequence<sal_Int32> SAL_CALL SvNumberFormatsObj::queryKeys( sal_Int16 nType,
+ const lang::Locale& nLocale,
+ sal_Bool bCreate )
+{
+ ::osl::MutexGuard aGuard( m_aMutex );
+
+ SvNumberFormatter* pFormatter = m_xSupplier->GetNumberFormatter();
+ if ( !pFormatter )
+ throw uno::RuntimeException();
+
+ sal_uInt32 nIndex = 0;
+ LanguageType eLang = lcl_GetLanguage( nLocale );
+ SvNumberFormatTable& rTable = bCreate ?
+ pFormatter->ChangeCL( static_cast<SvNumFormatType>(nType), nIndex, eLang ) :
+ pFormatter->GetEntryTable( static_cast<SvNumFormatType>(nType), nIndex, eLang );
+ sal_uInt32 nCount = rTable.size();
+ uno::Sequence<sal_Int32> aSeq(nCount);
+ sal_Int32* pAry = aSeq.getArray();
+ sal_uInt32 i=0;
+ for (const auto& rEntry : rTable)
+ {
+ pAry[i] = rEntry.first;
+ ++i;
+ }
+ return aSeq;
+}
+
+sal_Int32 SAL_CALL SvNumberFormatsObj::queryKey( const OUString& aFormat,
+ const lang::Locale& nLocale,
+ sal_Bool bScan )
+{
+ ::osl::MutexGuard aGuard( m_aMutex );
+
+ SvNumberFormatter* pFormatter = m_xSupplier->GetNumberFormatter();
+ if (!pFormatter)
+ throw uno::RuntimeException();
+
+ LanguageType eLang = lcl_GetLanguage( nLocale );
+ if (bScan)
+ {
+ //! FIXME: Something still needs to happen here ...
+ }
+ sal_uInt32 nRet = pFormatter->GetEntryKey( aFormat, eLang );
+ if (nRet == NUMBERFORMAT_ENTRY_NOT_FOUND)
+ {
+ // This seems to be a workaround for what maybe the bScan option was
+ // intended for? Tokenize the format code?
+
+ // The format string based search is vague and fuzzy, as it is case
+ // sensitive, but the format string is only half way cased, the
+ // keywords (except the "General" keyword) are uppercased and literals
+ // of course are not. Clients using this queryKey() and if not
+ // successful addNew() may still fail if the result of PutEntry() is
+ // false because the format actually existed, just with a different
+ // casing. The only clean way is to just use PutEntry() and ignore the
+ // duplicate case, which clients can't because the API doesn't provide
+ // the information.
+ // Try just another possibilty here, without any guarantee.
+
+ // Use only ASCII upper, because keywords are only those.
+ // Do not transliterate any quoted literals.
+ const sal_Int32 nLen = aFormat.getLength();
+ OUStringBuffer aBuf(0);
+ sal_Unicode* p = aBuf.appendUninitialized( nLen + 1);
+ memcpy( p, aFormat.getStr(), (nLen + 1) * sizeof(sal_Unicode)); // including 0-char
+ aBuf.setLength( nLen);
+ assert(p == aBuf.getStr());
+ sal_Unicode const * const pStop = p + aBuf.getLength();
+ bool bQuoted = false;
+ for ( ; p < pStop; ++p)
+ {
+ if (bQuoted)
+ {
+ // Format codes don't have embedded doubled quotes, i.e. "a""b"
+ // is two literals displayed as `ab`.
+ if (*p == '"')
+ bQuoted = false;
+ }
+ else if (*p == '"')
+ bQuoted = true;
+ else if (rtl::isAsciiLowerCase(*p))
+ *p = rtl::toAsciiUpperCase(*p);
+ else if (*p == '\\')
+ ++p; // skip escaped next char
+ // Theoretically that should cater for UTF-32 with
+ // iterateCodePoints(), but such character would not match any
+ // of [a-z\"] in its UTF-16 units.
+ }
+ nRet = pFormatter->GetEntryKey( aBuf, eLang );
+ }
+ return static_cast<sal_Int32>(nRet);
+}
+
+sal_Int32 SAL_CALL SvNumberFormatsObj::addNew( const OUString& aFormat,
+ const lang::Locale& nLocale )
+{
+ ::osl::MutexGuard aGuard( m_aMutex );
+
+ sal_Int32 nRet = 0;
+ SvNumberFormatter* pFormatter = m_xSupplier->GetNumberFormatter();
+ if (!pFormatter)
+ throw uno::RuntimeException();
+
+ OUString aFormStr = aFormat;
+ LanguageType eLang = lcl_GetLanguage( nLocale );
+ sal_uInt32 nKey = 0;
+ sal_Int32 nCheckPos = 0;
+ SvNumFormatType nType = SvNumFormatType::ALL;
+ bool bOk = pFormatter->PutEntry( aFormStr, nCheckPos, nType, nKey, eLang );
+ if (bOk)
+ nRet = nKey;
+ else if (nCheckPos)
+ {
+ throw util::MalformedNumberFormatException(); // Invalid Format
+ }
+ else if (aFormStr != aFormat)
+ {
+ // The format exists but with a different format code string, and if it
+ // was only uppercase vs lowercase keywords; but also syntax extensions
+ // are possible like resulting embedded LCIDs and what not the client
+ // doesn't know about. Silently accept instead of throwing an error.
+ nRet = nKey;
+ }
+ else
+ throw uno::RuntimeException(); // Other error (e.g. already added)
+
+ return nRet;
+}
+
+sal_Int32 SAL_CALL SvNumberFormatsObj::addNewConverted( const OUString& aFormat,
+ const lang::Locale& nLocale,
+ const lang::Locale& nNewLocale )
+{
+ ::osl::MutexGuard aGuard( m_aMutex );
+
+ sal_Int32 nRet = 0;
+ SvNumberFormatter* pFormatter = m_xSupplier->GetNumberFormatter();
+ if (!pFormatter)
+ throw uno::RuntimeException();
+
+ OUString aFormStr = aFormat;
+ LanguageType eLang = lcl_GetLanguage( nLocale );
+ LanguageType eNewLang = lcl_GetLanguage( nNewLocale );
+ sal_uInt32 nKey = 0;
+ sal_Int32 nCheckPos = 0;
+ SvNumFormatType nType = SvNumFormatType::ALL;
+ // This is used also when reading OOXML documents, there's no indicator
+ // whether to convert date particle order as well, so don't. See tdf#119013
+ bool bOk = pFormatter->PutandConvertEntry( aFormStr, nCheckPos, nType, nKey, eLang, eNewLang, false);
+ if (bOk || nKey > 0)
+ nRet = nKey;
+ else if (nCheckPos)
+ {
+ throw util::MalformedNumberFormatException(); // Invalid format
+ }
+ else
+ throw uno::RuntimeException(); // Other error (e.g. already added)
+
+ return nRet;
+}
+
+void SAL_CALL SvNumberFormatsObj::removeByKey( sal_Int32 nKey )
+{
+ ::osl::MutexGuard aGuard( m_aMutex );
+ SvNumberFormatter* pFormatter = m_xSupplier->GetNumberFormatter();
+
+ if (pFormatter)
+ {
+ pFormatter->DeleteEntry(nKey);
+ }
+}
+
+OUString SAL_CALL SvNumberFormatsObj::generateFormat( sal_Int32 nBaseKey,
+ const lang::Locale& nLocale,
+ sal_Bool bThousands,
+ sal_Bool bRed, sal_Int16 nDecimals,
+ sal_Int16 nLeading )
+{
+ ::osl::MutexGuard aGuard( m_aMutex );
+
+ SvNumberFormatter* pFormatter = m_xSupplier->GetNumberFormatter();
+ if (!pFormatter)
+ throw uno::RuntimeException();
+
+ LanguageType eLang = lcl_GetLanguage( nLocale );
+ OUString aRet = pFormatter->GenerateFormat(nBaseKey, eLang, bThousands, bRed, nDecimals, nLeading);
+ return aRet;
+}
+
+// XNumberFormatTypes
+
+sal_Int32 SAL_CALL SvNumberFormatsObj::getStandardIndex( const lang::Locale& nLocale )
+{
+ ::osl::MutexGuard aGuard( m_aMutex );
+
+ SvNumberFormatter* pFormatter = m_xSupplier->GetNumberFormatter();
+ if (!pFormatter)
+ throw uno::RuntimeException();
+
+ LanguageType eLang = lcl_GetLanguage( nLocale );
+ sal_Int32 nRet = pFormatter->GetStandardIndex(eLang);
+ return nRet;
+}
+
+sal_Int32 SAL_CALL SvNumberFormatsObj::getStandardFormat( sal_Int16 nType, const lang::Locale& nLocale )
+{
+ ::osl::MutexGuard aGuard( m_aMutex );
+
+ SvNumberFormatter* pFormatter = m_xSupplier->GetNumberFormatter();
+ if (!pFormatter)
+ throw uno::RuntimeException();
+
+ LanguageType eLang = lcl_GetLanguage( nLocale );
+ // Mask out "defined" bit, so type from an existing number format
+ // can directly be used for getStandardFormat
+ SvNumFormatType nType2 = static_cast<SvNumFormatType>(nType);
+ nType2 &= ~SvNumFormatType::DEFINED;
+ sal_Int32 nRet = pFormatter->GetStandardFormat(nType2, eLang);
+ return nRet;
+}
+
+sal_Int32 SAL_CALL SvNumberFormatsObj::getFormatIndex( sal_Int16 nIndex, const lang::Locale& nLocale )
+{
+ ::osl::MutexGuard aGuard( m_aMutex );
+
+ SvNumberFormatter* pFormatter = m_xSupplier->GetNumberFormatter();
+ if (!pFormatter)
+ throw uno::RuntimeException();
+
+ LanguageType eLang = lcl_GetLanguage( nLocale );
+ sal_Int32 nRet = pFormatter->GetFormatIndex( static_cast<NfIndexTableOffset>(nIndex), eLang );
+ return nRet;
+}
+
+sal_Bool SAL_CALL SvNumberFormatsObj::isTypeCompatible( sal_Int16 nOldType, sal_Int16 nNewType )
+{
+ ::osl::MutexGuard aGuard( m_aMutex );
+
+ return SvNumberFormatter::IsCompatible( static_cast<SvNumFormatType>(nOldType), static_cast<SvNumFormatType>(nNewType) );
+}
+
+sal_Int32 SAL_CALL SvNumberFormatsObj::getFormatForLocale( sal_Int32 nKey, const lang::Locale& nLocale )
+{
+ ::osl::MutexGuard aGuard( m_aMutex );
+
+ SvNumberFormatter* pFormatter = m_xSupplier->GetNumberFormatter();
+ if (!pFormatter)
+ throw uno::RuntimeException();
+
+ LanguageType eLang = lcl_GetLanguage( nLocale );
+ sal_Int32 nRet = pFormatter->GetFormatForLanguageIfBuiltIn(nKey, eLang);
+ return nRet;
+}
+
+// XServiceInfo
+
+OUString SAL_CALL SvNumberFormatsObj::getImplementationName()
+{
+ return "SvNumberFormatsObj";
+}
+
+sal_Bool SAL_CALL SvNumberFormatsObj::supportsService( const OUString& ServiceName )
+{
+ return cppu::supportsService( this, ServiceName );
+}
+
+uno::Sequence<OUString> SAL_CALL SvNumberFormatsObj::getSupportedServiceNames()
+{
+ return { "com.sun.star.util.NumberFormats" };
+}
+
+SvNumberFormatObj::SvNumberFormatObj( SvNumberFormatsSupplierObj& rParent, sal_uLong nK, ::comphelper::SharedMutex _aMutex )
+ :m_xSupplier( &rParent )
+ ,nKey( nK )
+ ,m_aMutex(std::move( _aMutex ))
+{
+}
+
+SvNumberFormatObj::~SvNumberFormatObj()
+{
+}
+
+// XPropertySet
+
+uno::Reference<beans::XPropertySetInfo> SAL_CALL SvNumberFormatObj::getPropertySetInfo()
+{
+ static uno::Reference<beans::XPropertySetInfo> aRef =
+ new SfxItemPropertySetInfo( lcl_GetNumberFormatPropertyMap() );
+ return aRef;
+}
+
+void SAL_CALL SvNumberFormatObj::setPropertyValue( const OUString&,
+ const uno::Any& )
+{
+ throw beans::UnknownPropertyException(); // Everything is read-only
+}
+
+uno::Any SAL_CALL SvNumberFormatObj::getPropertyValue( const OUString& aPropertyName )
+{
+ ::osl::MutexGuard aGuard( m_aMutex );
+
+ uno::Any aRet;
+ SvNumberFormatter* pFormatter = m_xSupplier->GetNumberFormatter();
+ const SvNumberformat* pFormat = pFormatter ? pFormatter->GetEntry(nKey) : nullptr;
+ if (!pFormat)
+ throw uno::RuntimeException();
+
+ bool bThousand, bRed;
+ sal_uInt16 nDecimals, nLeading;
+
+ if (aPropertyName == PROPERTYNAME_FMTSTR)
+ {
+ aRet <<= pFormat->GetFormatstring();
+ }
+ else if (aPropertyName == PROPERTYNAME_LOCALE)
+ {
+ lang::Locale aLocale( LanguageTag::convertToLocale( pFormat->GetLanguage(), false));
+ aRet <<= aLocale;
+ }
+ else if (aPropertyName == PROPERTYNAME_TYPE)
+ {
+ aRet <<= static_cast<sal_Int16>( pFormat->GetType() );
+ }
+ else if (aPropertyName == PROPERTYNAME_COMMENT)
+ {
+ aRet <<= pFormat->GetComment();
+ }
+ else if (aPropertyName == PROPERTYNAME_STDFORM)
+ {
+ //! Pass through SvNumberformat Member bStandard?
+ aRet <<= ( ( nKey % SV_COUNTRY_LANGUAGE_OFFSET ) == 0 );
+ }
+ else if (aPropertyName == PROPERTYNAME_USERDEF)
+ {
+ aRet <<= bool( pFormat->GetType() & SvNumFormatType::DEFINED );
+ }
+ else if (aPropertyName == PROPERTYNAME_DECIMALS)
+ {
+ pFormat->GetFormatSpecialInfo( bThousand, bRed, nDecimals, nLeading );
+ aRet <<= static_cast<sal_Int16>(nDecimals);
+ }
+ else if (aPropertyName == PROPERTYNAME_LEADING)
+ {
+ pFormat->GetFormatSpecialInfo( bThousand, bRed, nDecimals, nLeading );
+ aRet <<= static_cast<sal_Int16>(nLeading);
+ }
+ else if (aPropertyName == PROPERTYNAME_NEGRED)
+ {
+ pFormat->GetFormatSpecialInfo( bThousand, bRed, nDecimals, nLeading );
+ aRet <<= bRed;
+ }
+ else if (aPropertyName == PROPERTYNAME_THOUS)
+ {
+ pFormat->GetFormatSpecialInfo( bThousand, bRed, nDecimals, nLeading );
+ aRet <<= bThousand;
+ }
+ else if (aPropertyName == PROPERTYNAME_CURRSYM)
+ {
+ OUString aSymbol, aExt;
+ pFormat->GetNewCurrencySymbol( aSymbol, aExt );
+ aRet <<= aSymbol;
+ }
+ else if (aPropertyName == PROPERTYNAME_CURREXT)
+ {
+ OUString aSymbol, aExt;
+ pFormat->GetNewCurrencySymbol( aSymbol, aExt );
+ aRet <<= aExt;
+ }
+ else if (aPropertyName == PROPERTYNAME_CURRABB)
+ {
+ OUString aSymbol, aExt;
+ bool bBank = false;
+ pFormat->GetNewCurrencySymbol( aSymbol, aExt );
+ const NfCurrencyEntry* pCurr = SvNumberFormatter::GetCurrencyEntry( bBank,
+ aSymbol, aExt, pFormat->GetLanguage() );
+ if ( pCurr )
+ aRet <<= pCurr->GetBankSymbol();
+ else
+ aRet <<= OUString();
+ }
+ else
+ throw beans::UnknownPropertyException(aPropertyName);
+
+ return aRet;
+}
+
+void SAL_CALL SvNumberFormatObj::addPropertyChangeListener( const OUString&,
+ const uno::Reference<beans::XPropertyChangeListener>&)
+{
+ OSL_FAIL("not implemented");
+}
+
+void SAL_CALL SvNumberFormatObj::removePropertyChangeListener( const OUString&,
+ const uno::Reference<beans::XPropertyChangeListener>&)
+{
+ OSL_FAIL("not implemented");
+}
+
+void SAL_CALL SvNumberFormatObj::addVetoableChangeListener( const OUString&,
+ const uno::Reference<beans::XVetoableChangeListener>&)
+{
+ OSL_FAIL("not implemented");
+}
+
+void SAL_CALL SvNumberFormatObj::removeVetoableChangeListener( const OUString&,
+ const uno::Reference<beans::XVetoableChangeListener>&)
+{
+ OSL_FAIL("not implemented");
+}
+
+// XPropertyAccess
+
+uno::Sequence<beans::PropertyValue> SAL_CALL SvNumberFormatObj::getPropertyValues()
+{
+ ::osl::MutexGuard aGuard( m_aMutex );
+
+ SvNumberFormatter* pFormatter = m_xSupplier->GetNumberFormatter();
+ const SvNumberformat* pFormat = pFormatter ? pFormatter->GetEntry(nKey) : nullptr;
+ if (!pFormat)
+ throw uno::RuntimeException();
+
+ OUString aSymbol, aExt;
+ OUString aAbb;
+ bool bBank = false;
+ pFormat->GetNewCurrencySymbol( aSymbol, aExt );
+ const NfCurrencyEntry* pCurr = SvNumberFormatter::GetCurrencyEntry( bBank,
+ aSymbol, aExt, pFormat->GetLanguage() );
+ if ( pCurr )
+ aAbb = pCurr->GetBankSymbol();
+
+ OUString aFmtStr = pFormat->GetFormatstring();
+ OUString aComment = pFormat->GetComment();
+ bool bStandard = ( ( nKey % SV_COUNTRY_LANGUAGE_OFFSET ) == 0 );
+ //! Pass through SvNumberformat Member bStandard?
+ bool bUserDef( pFormat->GetType() & SvNumFormatType::DEFINED );
+ bool bThousand, bRed;
+ sal_uInt16 nDecimals, nLeading;
+ pFormat->GetFormatSpecialInfo( bThousand, bRed, nDecimals, nLeading );
+ lang::Locale aLocale( LanguageTag( pFormat->GetLanguage()).getLocale());
+
+ uno::Sequence<beans::PropertyValue> aSeq( comphelper::InitPropertySequence({
+ { PROPERTYNAME_FMTSTR, uno::Any(aFmtStr) },
+ { PROPERTYNAME_LOCALE, uno::Any(aLocale) },
+ { PROPERTYNAME_TYPE, uno::Any(sal_Int16( pFormat->GetType() )) },
+ { PROPERTYNAME_COMMENT, uno::Any(aComment) },
+ { PROPERTYNAME_STDFORM, uno::Any(bStandard) },
+ { PROPERTYNAME_USERDEF, uno::Any(bUserDef) },
+ { PROPERTYNAME_DECIMALS, uno::Any(sal_Int16( nDecimals )) },
+ { PROPERTYNAME_LEADING, uno::Any(sal_Int16( nLeading )) },
+ { PROPERTYNAME_NEGRED, uno::Any(bRed) },
+ { PROPERTYNAME_THOUS, uno::Any(bThousand) },
+ { PROPERTYNAME_CURRSYM, uno::Any(aSymbol) },
+ { PROPERTYNAME_CURREXT, uno::Any(aExt) },
+ { PROPERTYNAME_CURRABB, uno::Any(aAbb) }
+ }));
+
+ return aSeq;
+}
+
+void SAL_CALL SvNumberFormatObj::setPropertyValues( const uno::Sequence<beans::PropertyValue>& )
+{
+ throw beans::UnknownPropertyException(); // Everything is read-only
+}
+
+// XServiceInfo
+
+OUString SAL_CALL SvNumberFormatObj::getImplementationName()
+{
+ return "SvNumberFormatObj";
+}
+
+sal_Bool SAL_CALL SvNumberFormatObj::supportsService( const OUString& ServiceName )
+{
+ return cppu::supportsService( this, ServiceName );
+}
+
+uno::Sequence<OUString> SAL_CALL SvNumberFormatObj::getSupportedServiceNames()
+{
+ return { "com.sun.star.util.NumberFormatProperties" };
+}
+
+SvNumberFormatSettingsObj::SvNumberFormatSettingsObj( SvNumberFormatsSupplierObj& rParent, ::comphelper::SharedMutex _aMutex )
+ :m_xSupplier( &rParent )
+ ,m_aMutex(std::move( _aMutex ))
+{
+}
+
+SvNumberFormatSettingsObj::~SvNumberFormatSettingsObj()
+{
+}
+
+// XPropertySet
+
+uno::Reference<beans::XPropertySetInfo> SAL_CALL SvNumberFormatSettingsObj::getPropertySetInfo()
+{
+ static uno::Reference<beans::XPropertySetInfo> aRef =
+ new SfxItemPropertySetInfo( lcl_GetNumberSettingsPropertyMap() );
+ return aRef;
+}
+
+void SAL_CALL SvNumberFormatSettingsObj::setPropertyValue( const OUString& aPropertyName,
+ const uno::Any& aValue )
+{
+ ::osl::MutexGuard aGuard( m_aMutex );
+
+ SvNumberFormatter* pFormatter = m_xSupplier->GetNumberFormatter();
+ if (!pFormatter)
+ throw uno::RuntimeException();
+
+ if (aPropertyName == PROPERTYNAME_NOZERO)
+ {
+ // operator >>= shouldn't be used for bool (?)
+ if ( auto b = o3tl::tryAccess<bool>(aValue) )
+ pFormatter->SetNoZero( *b );
+ }
+ else if (aPropertyName == PROPERTYNAME_NULLDATE)
+ {
+ util::Date aDate;
+ if ( aValue >>= aDate )
+ pFormatter->ChangeNullDate( aDate.Day, aDate.Month, aDate.Year );
+ }
+ else if (aPropertyName == PROPERTYNAME_STDDEC)
+ {
+ sal_Int16 nInt16 = sal_Int16();
+ if ( aValue >>= nInt16 )
+ pFormatter->ChangeStandardPrec( nInt16 );
+ }
+ else if (aPropertyName == PROPERTYNAME_TWODIGIT)
+ {
+ sal_Int16 nInt16 = sal_Int16();
+ if ( aValue >>= nInt16 )
+ pFormatter->SetYear2000( nInt16 );
+ }
+ else
+ throw beans::UnknownPropertyException(aPropertyName);
+
+}
+
+uno::Any SAL_CALL SvNumberFormatSettingsObj::getPropertyValue( const OUString& aPropertyName )
+{
+ ::osl::MutexGuard aGuard( m_aMutex );
+
+ uno::Any aRet;
+ SvNumberFormatter* pFormatter = m_xSupplier->GetNumberFormatter();
+ if (!pFormatter)
+ throw uno::RuntimeException();
+
+ if (aPropertyName == PROPERTYNAME_NOZERO)
+ {
+ aRet <<= pFormatter->GetNoZero();
+ }
+ else if (aPropertyName == PROPERTYNAME_NULLDATE)
+ {
+ const Date& rDate = pFormatter->GetNullDate();
+ aRet <<= rDate.GetUNODate();
+ }
+ else if (aPropertyName == PROPERTYNAME_STDDEC)
+ aRet <<= static_cast<sal_Int16>( pFormatter->GetStandardPrec() );
+ else if (aPropertyName == PROPERTYNAME_TWODIGIT)
+ aRet <<= static_cast<sal_Int16>( pFormatter->GetYear2000() );
+ else
+ throw beans::UnknownPropertyException(aPropertyName);
+
+ return aRet;
+}
+
+void SAL_CALL SvNumberFormatSettingsObj::addPropertyChangeListener( const OUString&,
+ const uno::Reference<beans::XPropertyChangeListener>&)
+{
+ OSL_FAIL("not implemented");
+}
+
+void SAL_CALL SvNumberFormatSettingsObj::removePropertyChangeListener( const OUString&,
+ const uno::Reference<beans::XPropertyChangeListener>&)
+{
+ OSL_FAIL("not implemented");
+}
+
+void SAL_CALL SvNumberFormatSettingsObj::addVetoableChangeListener( const OUString&,
+ const uno::Reference<beans::XVetoableChangeListener>&)
+{
+ OSL_FAIL("not implemented");
+}
+
+void SAL_CALL SvNumberFormatSettingsObj::removeVetoableChangeListener( const OUString&,
+ const uno::Reference<beans::XVetoableChangeListener>&)
+{
+ OSL_FAIL("not implemented");
+}
+
+// XServiceInfo
+
+OUString SAL_CALL SvNumberFormatSettingsObj::getImplementationName()
+{
+ return "SvNumberFormatSettingsObj";
+}
+
+sal_Bool SAL_CALL SvNumberFormatSettingsObj::supportsService( const OUString& ServiceName )
+{
+ return cppu::supportsService( this, ServiceName );
+}
+
+uno::Sequence<OUString> SAL_CALL SvNumberFormatSettingsObj::getSupportedServiceNames()
+{
+ return { "com.sun.star.util.NumberFormatSettings" };
+}
+
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
+com_sun_star_uno_util_numbers_SvNumberFormatterServiceObject_get_implementation(css::uno::XComponentContext*,
+ css::uno::Sequence<css::uno::Any> const &)
+{
+ return cppu::acquire(new SvNumberFormatterServiceObj());
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/numbers/numfmuno.hxx b/svl/source/numbers/numfmuno.hxx
new file mode 100644
index 0000000000..f88c2e6429
--- /dev/null
+++ b/svl/source/numbers/numfmuno.hxx
@@ -0,0 +1,222 @@
+/* -*- 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 .
+ */
+
+#ifndef INCLUDED_SVL_SOURCE_NUMBERS_NUMFMUNO_HXX
+#define INCLUDED_SVL_SOURCE_NUMBERS_NUMFMUNO_HXX
+
+#include <com/sun/star/util/XNumberFormatter2.hpp>
+#include <com/sun/star/util/XNumberFormats.hpp>
+#include <com/sun/star/util/XNumberFormatTypes.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/beans/XPropertyAccess.hpp>
+#include <cppuhelper/implbase.hxx>
+#include <comphelper/sharedmutex.hxx>
+#include <rtl/ref.hxx>
+#include <tools/solar.h>
+
+class SvNumberFormatsSupplierObj;
+
+
+/**
+ * SvNumberFormatterServiceObj is registered globally as a Service
+ */
+class SvNumberFormatterServiceObj : public cppu::WeakImplHelper<
+ css::util::XNumberFormatter2,
+ css::lang::XServiceInfo>
+{
+private:
+ ::rtl::Reference< SvNumberFormatsSupplierObj > xSupplier;
+ mutable ::comphelper::SharedMutex m_aMutex;
+
+public:
+ SvNumberFormatterServiceObj();
+ virtual ~SvNumberFormatterServiceObj() override;
+
+ // XNumberFormatter
+ virtual void SAL_CALL attachNumberFormatsSupplier(
+ const css::uno::Reference< css::util::XNumberFormatsSupplier >& xSupplier ) override;
+ virtual css::uno::Reference< css::util::XNumberFormatsSupplier >
+ SAL_CALL getNumberFormatsSupplier() override;
+ virtual sal_Int32 SAL_CALL detectNumberFormat( sal_Int32 nKey, const OUString& aString ) override;
+ virtual double SAL_CALL convertStringToNumber( sal_Int32 nKey, const OUString& aString ) override;
+ virtual OUString SAL_CALL convertNumberToString( sal_Int32 nKey, double fValue ) override;
+ virtual sal_Int32 SAL_CALL queryColorForNumber( sal_Int32 nKey,
+ double fValue, sal_Int32 aDefaultColor ) override;
+ virtual OUString SAL_CALL formatString( sal_Int32 nKey, const OUString& aString ) override;
+ virtual sal_Int32 SAL_CALL queryColorForString( sal_Int32 nKey,
+ const OUString& aString,
+ sal_Int32 aDefaultColor ) override;
+ virtual OUString SAL_CALL getInputString( sal_Int32 nKey, double fValue ) override;
+
+ // XNumberFormatPreviewer
+ virtual OUString SAL_CALL convertNumberToPreviewString(
+ const OUString& aFormat, double fValue,
+ const css::lang::Locale& nLocale, sal_Bool bAllowEnglish ) override;
+ virtual sal_Int32 SAL_CALL queryPreviewColorForNumber(
+ const OUString& aFormat, double fValue,
+ const css::lang::Locale& nLocale, sal_Bool bAllowEnglish,
+ sal_Int32 aDefaultColor ) override;
+
+ // XServiceInfo
+ virtual OUString SAL_CALL getImplementationName( ) override;
+ virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override;
+ virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override;
+};
+
+
+class SvNumberFormatsObj : public cppu::WeakImplHelper<
+ css::util::XNumberFormats,
+ css::util::XNumberFormatTypes,
+ css::lang::XServiceInfo>
+{
+private:
+ rtl::Reference<SvNumberFormatsSupplierObj> m_xSupplier;
+ mutable ::comphelper::SharedMutex m_aMutex;
+
+public:
+ SvNumberFormatsObj(SvNumberFormatsSupplierObj& pParent, ::comphelper::SharedMutex _aMutex);
+ virtual ~SvNumberFormatsObj() override;
+
+
+ // XNumberFormats
+ virtual css::uno::Reference< css::beans::XPropertySet > SAL_CALL
+ getByKey( sal_Int32 nKey ) override;
+ virtual css::uno::Sequence< sal_Int32 > SAL_CALL queryKeys( sal_Int16 nType,
+ const css::lang::Locale& nLocale, sal_Bool bCreate ) override;
+ virtual sal_Int32 SAL_CALL queryKey( const OUString& aFormat,
+ const css::lang::Locale& nLocale, sal_Bool bScan ) override;
+ virtual sal_Int32 SAL_CALL addNew( const OUString& aFormat,
+ const css::lang::Locale& nLocale ) override;
+ virtual sal_Int32 SAL_CALL addNewConverted( const OUString& aFormat,
+ const css::lang::Locale& nLocale,
+ const css::lang::Locale& nNewLocale ) override;
+ virtual void SAL_CALL removeByKey( sal_Int32 nKey ) override;
+ virtual OUString SAL_CALL generateFormat( sal_Int32 nBaseKey,
+ const css::lang::Locale& nLocale, sal_Bool bThousands,
+ sal_Bool bRed, sal_Int16 nDecimals, sal_Int16 nLeading ) override;
+
+ // XNumberFormatTypes
+ virtual sal_Int32 SAL_CALL getStandardIndex( const css::lang::Locale& nLocale ) override;
+ virtual sal_Int32 SAL_CALL getStandardFormat( sal_Int16 nType,
+ const css::lang::Locale& nLocale ) override;
+ virtual sal_Int32 SAL_CALL getFormatIndex( sal_Int16 nIndex,
+ const css::lang::Locale& nLocale ) override;
+ virtual sal_Bool SAL_CALL isTypeCompatible( sal_Int16 nOldType, sal_Int16 nNewType ) override;
+ virtual sal_Int32 SAL_CALL getFormatForLocale( sal_Int32 nKey,
+ const css::lang::Locale& nLocale ) override;
+
+ // XServiceInfo
+ virtual OUString SAL_CALL getImplementationName( ) override;
+ virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override;
+ virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override;
+};
+
+
+class SvNumberFormatObj : public cppu::WeakImplHelper<
+ css::beans::XPropertySet,
+ css::beans::XPropertyAccess,
+ css::lang::XServiceInfo>
+{
+private:
+ rtl::Reference<SvNumberFormatsSupplierObj>
+ m_xSupplier;
+ sal_uLong nKey;
+ mutable ::comphelper::SharedMutex m_aMutex;
+
+public:
+ SvNumberFormatObj( SvNumberFormatsSupplierObj& rParent, sal_uLong nK, ::comphelper::SharedMutex _aMutex );
+ virtual ~SvNumberFormatObj() override;
+
+ // XPropertySet
+ virtual css::uno::Reference< css::beans::XPropertySetInfo >
+ SAL_CALL getPropertySetInfo( ) override;
+ virtual void SAL_CALL setPropertyValue( const OUString& aPropertyName,
+ const css::uno::Any& aValue ) override;
+ virtual css::uno::Any SAL_CALL getPropertyValue(
+ const OUString& PropertyName ) override;
+ virtual void SAL_CALL addPropertyChangeListener( const OUString& aPropertyName,
+ const css::uno::Reference<
+ css::beans::XPropertyChangeListener >& xListener ) override;
+ virtual void SAL_CALL removePropertyChangeListener( const OUString& aPropertyName,
+ const css::uno::Reference<
+ css::beans::XPropertyChangeListener >& aListener ) override;
+ virtual void SAL_CALL addVetoableChangeListener( const OUString& PropertyName,
+ const css::uno::Reference<
+ css::beans::XVetoableChangeListener >& aListener ) override;
+ virtual void SAL_CALL removeVetoableChangeListener( const OUString& PropertyName,
+ const css::uno::Reference<
+ css::beans::XVetoableChangeListener >& aListener ) 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;
+
+ // XServiceInfo
+ virtual OUString SAL_CALL getImplementationName( ) override;
+ virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override;
+ virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override;
+};
+
+
+class SvNumberFormatSettingsObj : public cppu::WeakImplHelper<
+ css::beans::XPropertySet,
+ css::lang::XServiceInfo>
+{
+private:
+ rtl::Reference<SvNumberFormatsSupplierObj>
+ m_xSupplier;
+ mutable ::comphelper::SharedMutex m_aMutex;
+
+public:
+ SvNumberFormatSettingsObj( SvNumberFormatsSupplierObj& rParent, ::comphelper::SharedMutex _aMutex);
+ virtual ~SvNumberFormatSettingsObj() override;
+
+
+ // XPropertySet
+ virtual css::uno::Reference< css::beans::XPropertySetInfo >
+ SAL_CALL getPropertySetInfo( ) override;
+ virtual void SAL_CALL setPropertyValue( const OUString& aPropertyName,
+ const css::uno::Any& aValue ) override;
+ virtual css::uno::Any SAL_CALL getPropertyValue(
+ const OUString& PropertyName ) override;
+ virtual void SAL_CALL addPropertyChangeListener( const OUString& aPropertyName,
+ const css::uno::Reference<
+ css::beans::XPropertyChangeListener >& xListener ) override;
+ virtual void SAL_CALL removePropertyChangeListener( const OUString& aPropertyName,
+ const css::uno::Reference<
+ css::beans::XPropertyChangeListener >& aListener ) override;
+ virtual void SAL_CALL addVetoableChangeListener( const OUString& PropertyName,
+ const css::uno::Reference<
+ css::beans::XVetoableChangeListener >& aListener ) override;
+ virtual void SAL_CALL removeVetoableChangeListener( const OUString& PropertyName,
+ const css::uno::Reference<
+ css::beans::XVetoableChangeListener >& aListener ) override;
+
+ // XServiceInfo
+ virtual OUString SAL_CALL getImplementationName( ) override;
+ virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override;
+ virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override;
+};
+
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/numbers/numuno.cxx b/svl/source/numbers/numuno.cxx
new file mode 100644
index 0000000000..db633f4055
--- /dev/null
+++ b/svl/source/numbers/numuno.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/.
+ *
+ * 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 <svl/numuno.hxx>
+#include "numfmuno.hxx"
+
+using namespace com::sun::star;
+
+
+class SvNumFmtSuppl_Impl
+{
+public:
+ SvNumberFormatter* pFormatter;
+ mutable ::comphelper::SharedMutex aMutex;
+
+ explicit SvNumFmtSuppl_Impl(SvNumberFormatter* p) :
+ pFormatter(p) {}
+};
+
+
+// Default ctor for getReflection
+SvNumberFormatsSupplierObj::SvNumberFormatsSupplierObj()
+ : pImpl( new SvNumFmtSuppl_Impl(nullptr) )
+{
+}
+
+SvNumberFormatsSupplierObj::SvNumberFormatsSupplierObj(SvNumberFormatter* pForm)
+ : pImpl( new SvNumFmtSuppl_Impl(pForm) )
+{
+}
+
+SvNumberFormatsSupplierObj::~SvNumberFormatsSupplierObj()
+{
+}
+
+::comphelper::SharedMutex& SvNumberFormatsSupplierObj::getSharedMutex() const
+{
+ return pImpl->aMutex;
+}
+
+SvNumberFormatter* SvNumberFormatsSupplierObj::GetNumberFormatter() const
+{
+ return pImpl->pFormatter;
+}
+
+void SvNumberFormatsSupplierObj::SetNumberFormatter(SvNumberFormatter* pNew)
+{
+ // The old Numberformatter has been retired, do not access it anymore!
+ pImpl->pFormatter = pNew;
+}
+
+// XNumberFormatsSupplier
+
+uno::Reference<beans::XPropertySet> SAL_CALL SvNumberFormatsSupplierObj::getNumberFormatSettings()
+{
+ ::osl::MutexGuard aGuard( pImpl->aMutex );
+
+ return new SvNumberFormatSettingsObj( *this, pImpl->aMutex );
+}
+
+uno::Reference<util::XNumberFormats> SAL_CALL SvNumberFormatsSupplierObj::getNumberFormats()
+{
+ ::osl::MutexGuard aGuard( pImpl->aMutex );
+
+ return new SvNumberFormatsObj( *this, pImpl->aMutex );
+}
+
+// XUnoTunnel
+
+UNO3_GETIMPLEMENTATION_IMPL(SvNumberFormatsSupplierObj);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/numbers/supservs.cxx b/svl/source/numbers/supservs.cxx
new file mode 100644
index 0000000000..5a51158a2b
--- /dev/null
+++ b/svl/source/numbers/supservs.cxx
@@ -0,0 +1,161 @@
+/* -*- 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 "supservs.hxx"
+#include <com/sun/star/lang/Locale.hpp>
+#include <comphelper/sharedmutex.hxx>
+#include <cppuhelper/supportsservice.hxx>
+#include <cppuhelper/queryinterface.hxx>
+#include <cppuhelper/weak.hxx>
+#include <i18nlangtag/languagetag.hxx>
+#include <svl/numformat.hxx>
+#include <tools/debug.hxx>
+#include <osl/mutex.hxx>
+#include <utility>
+#include <osl/diagnose.h>
+
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::beans;
+using namespace ::com::sun::star::util;
+using namespace ::utl;
+
+
+SvNumberFormatsSupplierServiceObject::SvNumberFormatsSupplierServiceObject(css::uno::Reference< css::uno::XComponentContext > _xORB)
+ :m_xORB(std::move(_xORB))
+{
+}
+
+SvNumberFormatsSupplierServiceObject::~SvNumberFormatsSupplierServiceObject()
+{
+}
+
+Any SAL_CALL SvNumberFormatsSupplierServiceObject::queryAggregation( const Type& _rType )
+{
+ Any aReturn = ::cppu::queryInterface(_rType,
+ static_cast< XInitialization* >(this),
+ static_cast< XServiceInfo* >(this)
+ );
+
+ if (!aReturn.hasValue())
+ aReturn = SvNumberFormatsSupplierObj::queryAggregation(_rType);
+
+ return aReturn;
+}
+
+void SAL_CALL SvNumberFormatsSupplierServiceObject::initialize( const Sequence< Any >& _rArguments )
+{
+ ::osl::MutexGuard aGuard( getSharedMutex() );
+
+ DBG_ASSERT(m_pOwnFormatter == nullptr,
+ "SvNumberFormatsSupplierServiceObject::initialize : already initialized !");
+ // maybe you already called a method which needed the formatter
+ // you should use XMultiServiceFactory::createInstanceWithArguments to avoid that
+ if (m_pOwnFormatter)
+ { // !!! this is only an emergency handling, normally this should not occur !!!
+ m_pOwnFormatter.reset();
+ SetNumberFormatter(m_pOwnFormatter.get());
+ }
+
+ Type aExpectedArgType = ::cppu::UnoType<css::lang::Locale>::get();
+ LanguageType eNewFormatterLanguage = LANGUAGE_SYSTEM;
+ // the default
+
+ for (const Any& rArg : _rArguments)
+ {
+ if (rArg.getValueType().equals(aExpectedArgType))
+ {
+ css::lang::Locale aLocale;
+ rArg >>= aLocale;
+ eNewFormatterLanguage = LanguageTag::convertToLanguageType( aLocale, false);
+ }
+#ifdef DBG_UTIL
+ else
+ {
+ OSL_FAIL("SvNumberFormatsSupplierServiceObject::initialize : unknown argument !");
+ }
+#endif
+ }
+
+ m_pOwnFormatter.reset( new SvNumberFormatter( m_xORB, eNewFormatterLanguage) );
+ m_pOwnFormatter->SetEvalDateFormat( NF_EVALDATEFORMAT_FORMAT_INTL );
+ SetNumberFormatter(m_pOwnFormatter.get());
+}
+
+OUString SAL_CALL SvNumberFormatsSupplierServiceObject::getImplementationName( )
+{
+ return "com.sun.star.uno.util.numbers.SvNumberFormatsSupplierServiceObject";
+}
+
+sal_Bool SAL_CALL SvNumberFormatsSupplierServiceObject::supportsService( const OUString& _rServiceName )
+{
+ return cppu::supportsService(this, _rServiceName);
+}
+
+Sequence< OUString > SAL_CALL SvNumberFormatsSupplierServiceObject::getSupportedServiceNames( )
+{
+ return { "com.sun.star.util.NumberFormatsSupplier" };
+}
+
+Reference< XPropertySet > SAL_CALL SvNumberFormatsSupplierServiceObject::getNumberFormatSettings()
+{
+ ::osl::MutexGuard aGuard( getSharedMutex() );
+ implEnsureFormatter();
+ return SvNumberFormatsSupplierObj::getNumberFormatSettings();
+}
+
+Reference< XNumberFormats > SAL_CALL SvNumberFormatsSupplierServiceObject::getNumberFormats()
+{
+ ::osl::MutexGuard aGuard( getSharedMutex() );
+ implEnsureFormatter();
+ return SvNumberFormatsSupplierObj::getNumberFormats();
+}
+
+sal_Int64 SAL_CALL SvNumberFormatsSupplierServiceObject::getSomething( const Sequence< sal_Int8 >& aIdentifier )
+{
+ sal_Int64 nReturn = SvNumberFormatsSupplierObj::getSomething( aIdentifier );
+ if ( nReturn )
+ // if somebody accesses internals then we should have the formatter
+ implEnsureFormatter();
+ return nReturn;
+}
+
+void SvNumberFormatsSupplierServiceObject::implEnsureFormatter()
+{
+ if (!m_pOwnFormatter)
+ {
+ // get the office's UI locale
+ SvtSysLocale aSysLocale;
+ css::lang::Locale aOfficeLocale = aSysLocale.GetLocaleData().getLanguageTag().getLocale();
+
+ // initialize with this locale
+ initialize({ Any(aOfficeLocale) });
+ }
+}
+
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
+com_sun_star_uno_util_numbers_SvNumberFormatsSupplierServiceObject_get_implementation(css::uno::XComponentContext* context,
+ css::uno::Sequence<css::uno::Any> const &)
+{
+ return cppu::acquire(new SvNumberFormatsSupplierServiceObject(context));
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/numbers/supservs.hxx b/svl/source/numbers/supservs.hxx
new file mode 100644
index 0000000000..4ddf5751e8
--- /dev/null
+++ b/svl/source/numbers/supservs.hxx
@@ -0,0 +1,80 @@
+/* -*- 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 .
+ */
+
+#ifndef INCLUDED_SVL_SOURCE_NUMBERS_SUPSERVS_HXX
+#define INCLUDED_SVL_SOURCE_NUMBERS_SUPSERVS_HXX
+
+#include <svl/numuno.hxx>
+#include <com/sun/star/lang/XInitialization.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+#include <memory>
+
+/**
+ * SvNumberFormatsSupplierServiceObject - a number formats supplier which
+ * - can be instantiated as a service
+ * - works with its own SvNumberFormatter instance
+ * - can be initialized (css::lang::XInitialization)
+ * with a specific language (i.e. css::lang::Locale)
+ */
+class SvNumberFormatsSupplierServiceObject final
+ :public SvNumberFormatsSupplierObj
+ ,public css::lang::XInitialization
+ ,public css::lang::XServiceInfo
+{
+ std::unique_ptr<SvNumberFormatter> m_pOwnFormatter;
+ css::uno::Reference< css::uno::XComponentContext > m_xORB;
+
+ void implEnsureFormatter();
+
+public:
+ explicit SvNumberFormatsSupplierServiceObject(css::uno::Reference< css::uno::XComponentContext > _xORB);
+ virtual ~SvNumberFormatsSupplierServiceObject() override;
+
+ // XInterface
+ virtual void SAL_CALL acquire() noexcept override { SvNumberFormatsSupplierObj::acquire(); }
+ virtual void SAL_CALL release() noexcept override { SvNumberFormatsSupplierObj::release(); }
+ virtual css::uno::Any SAL_CALL queryInterface( const css::uno::Type& _rType ) override
+ { return SvNumberFormatsSupplierObj::queryInterface(_rType); }
+
+ // XAggregation
+ virtual css::uno::Any SAL_CALL queryAggregation( const css::uno::Type& _rType ) override;
+
+ // XInitialization
+ virtual void SAL_CALL initialize( const css::uno::Sequence< css::uno::Any >& aArguments ) override;
+
+ // XServiceInfo
+ virtual OUString SAL_CALL getImplementationName( ) override;
+ virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override;
+ virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames( ) override;
+
+ // XNumberFormatsSupplier
+ virtual css::uno::Reference< css::beans::XPropertySet > SAL_CALL
+ getNumberFormatSettings() override;
+ virtual css::uno::Reference< css::util::XNumberFormats > SAL_CALL
+ getNumberFormats() override;
+
+ // XUnoTunneler
+ virtual sal_Int64 SAL_CALL getSomething( const css::uno::Sequence< sal_Int8 >& aIdentifier ) override;
+};
+
+
+#endif // INCLUDED_SVL_SOURCE_NUMBERS_SUPSERVS_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/numbers/zforfind.cxx b/svl/source/numbers/zforfind.cxx
new file mode 100644
index 0000000000..c1898104a9
--- /dev/null
+++ b/svl/source/numbers/zforfind.cxx
@@ -0,0 +1,4324 @@
+/* -*- 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 <cstdlib>
+#include <dtoa.h>
+#include <float.h>
+#include <comphelper/string.hxx>
+#include <o3tl/string_view.hxx>
+#include <sal/log.hxx>
+#include <tools/date.hxx>
+#include <rtl/math.hxx>
+#include <rtl/character.hxx>
+#include <unotools/charclass.hxx>
+#include <unotools/calendarwrapper.hxx>
+#include <unotools/localedatawrapper.hxx>
+#include <com/sun/star/i18n/CalendarFieldIndex.hpp>
+#include <com/sun/star/i18n/LocaleCalendar2.hpp>
+#include <unotools/digitgroupingiterator.hxx>
+#include <comphelper/sequence.hxx>
+
+#include <svl/zforlist.hxx>
+#include "zforscan.hxx"
+#include <svl/zformat.hxx>
+
+#include <memory>
+
+#include "zforfind.hxx"
+
+#ifndef DBG_UTIL
+#define NF_TEST_CALENDAR 0
+#else
+#define NF_TEST_CALENDAR 0
+#endif
+#if NF_TEST_CALENDAR
+#include <comphelper/processfactory.hxx>
+#include <com/sun/star/i18n/XCalendar4.hpp>
+#endif
+
+
+const sal_uInt8 ImpSvNumberInputScan::nMatchedEndString = 0x01;
+const sal_uInt8 ImpSvNumberInputScan::nMatchedMidString = 0x02;
+const sal_uInt8 ImpSvNumberInputScan::nMatchedStartString = 0x04;
+const sal_uInt8 ImpSvNumberInputScan::nMatchedVirgin = 0x08;
+const sal_uInt8 ImpSvNumberInputScan::nMatchedUsedAsReturn = 0x10;
+
+/* It is not clear how we want timezones to be handled. Convert them to local
+ * time isn't wanted, as it isn't done in any other place and timezone
+ * information isn't stored anywhere. Ignoring them and pretending local time
+ * may be wrong too and might not be what the user expects. Keep the input as
+ * string so that no information is lost.
+ * Anyway, defining NF_RECOGNIZE_ISO8601_TIMEZONES to 1 would be the way how it
+ * would work, together with the nTimezonePos handling in GetTimeRef(). */
+#define NF_RECOGNIZE_ISO8601_TIMEZONES 0
+
+const sal_Unicode cNoBreakSpace = 0xA0;
+const sal_Unicode cNarrowNoBreakSpace = 0x202F;
+const bool kDefaultEra = true; // Gregorian CE, positive year
+
+ImpSvNumberInputScan::ImpSvNumberInputScan( SvNumberFormatter* pFormatterP )
+ :
+ bTextInitialized( false ),
+ bScanGenitiveMonths( false ),
+ bScanPartitiveMonths( false ),
+ eScannedType( SvNumFormatType::UNDEFINED ),
+ eSetType( SvNumFormatType::UNDEFINED )
+{
+ pFormatter = pFormatterP;
+ moNullDate.emplace( 30,12,1899 );
+ nYear2000 = SvNumberFormatter::GetYear2000Default();
+ Reset();
+ ChangeIntl();
+}
+
+
+ImpSvNumberInputScan::~ImpSvNumberInputScan()
+{
+}
+
+
+void ImpSvNumberInputScan::Reset()
+{
+ mpFormat = nullptr;
+ nMonth = 0;
+ nMonthPos = 0;
+ nDayOfWeek = 0;
+ nTimePos = 0;
+ nSign = 0;
+ nESign = 0;
+ nDecPos = 0;
+ bNegCheck = false;
+ nStringsCnt = 0;
+ nNumericsCnt = 0;
+ nThousand = 0;
+ eScannedType = SvNumFormatType::UNDEFINED;
+ nAmPm = 0;
+ nPosThousandString = 0;
+ nLogical = 0;
+ mbEraCE = kDefaultEra;
+ nStringScanNumFor = 0;
+ nStringScanSign = 0;
+ nMatchedAllStrings = nMatchedVirgin;
+ nMayBeIso8601 = 0;
+ bIso8601Tsep = false;
+ nMayBeMonthDate = 0;
+ nAcceptedDatePattern = -2;
+ nDatePatternStart = 0;
+ nDatePatternNumbers = 0;
+
+ for (sal_uInt32 i = 0; i < SV_MAX_COUNT_INPUT_STRINGS; i++)
+ {
+ IsNum[i] = false;
+ nNums[i] = 0;
+ }
+}
+
+// native number transliteration if necessary
+static void TransformInput( SvNumberFormatter const * pFormatter, OUString& rStr )
+{
+ sal_Int32 nPos, nLen;
+ for ( nPos = 0, nLen = rStr.getLength(); nPos < nLen; ++nPos )
+ {
+ if ( 256 <= rStr[ nPos ] &&
+ pFormatter->GetCharClass()->isDigit( rStr, nPos ) )
+ {
+ break;
+ }
+ }
+ if ( nPos < nLen )
+ {
+ rStr = pFormatter->GetNatNum()->getNativeNumberString( rStr,
+ pFormatter->GetLanguageTag().getLocale(), 0 );
+ }
+}
+
+
+/**
+ * Only simple unsigned floating point values without any error detection,
+ * decimal separator has to be '.'
+ */
+double ImpSvNumberInputScan::StringToDouble( std::u16string_view aStr, bool bForceFraction )
+{
+ std::unique_ptr<char[]> bufInHeap;
+ constexpr int bufOnStackSize = 256;
+ char bufOnStack[bufOnStackSize];
+ char* buf = bufOnStack;
+ const sal_Int32 bufsize = aStr.size() + (bForceFraction ? 2 : 1);
+ if (bufsize > bufOnStackSize)
+ {
+ bufInHeap = std::make_unique<char[]>(bufsize);
+ buf = bufInHeap.get();
+ }
+ char* p = buf;
+ if (bForceFraction)
+ *p++ = '.';
+ for (size_t nPos = 0; nPos < aStr.size(); ++nPos)
+ {
+ sal_Unicode c = aStr[nPos];
+ if (c == '.' || (c >= '0' && c <= '9'))
+ *p++ = static_cast<char>(c);
+ else
+ break;
+ }
+ *p = '\0';
+
+ return strtod_nolocale(buf, nullptr);
+}
+
+namespace {
+
+/**
+ * Splits up the input into numbers and strings for further processing
+ * (by the Turing machine).
+ *
+ * Starting state = GetChar
+ * ---------------+-------------------+-----------------------------+---------------
+ * Old State | Character read | Event | New state
+ * ---------------+-------------------+-----------------------------+---------------
+ * GetChar | Number | Symbol = Character | GetValue
+ * | Else | Symbol = Character | GetString
+ * ---------------|-------------------+-----------------------------+---------------
+ * GetValue | Number | Symbol = Symbol + Character | GetValue
+ * | Else | Dec(CharPos) | Stop
+ * ---------------+-------------------+-----------------------------+---------------
+ * GetString | Number | Dec(CharPos) | Stop
+ * | Else | Symbol = Symbol + Character | GetString
+ * ---------------+-------------------+-----------------------------+---------------
+ */
+enum ScanState // States of the Turing machine
+{
+ SsStop = 0,
+ SsStart = 1,
+ SsGetValue = 2,
+ SsGetString = 3
+};
+
+}
+
+bool ImpSvNumberInputScan::NextNumberStringSymbol( const sal_Unicode*& pStr,
+ OUString& rSymbol )
+{
+ bool isNumber = false;
+ sal_Unicode cToken;
+ ScanState eState = SsStart;
+ const sal_Unicode* pHere = pStr;
+ sal_Int32 nChars = 0;
+
+ for (;;)
+ {
+ cToken = *pHere;
+ if (cToken == 0 || eState == SsStop)
+ break;
+ pHere++;
+ switch (eState)
+ {
+ case SsStart:
+ if ( rtl::isAsciiDigit( cToken ) )
+ {
+ eState = SsGetValue;
+ isNumber = true;
+ }
+ else
+ {
+ eState = SsGetString;
+ }
+ nChars++;
+ break;
+ case SsGetValue:
+ if ( rtl::isAsciiDigit( cToken ) )
+ {
+ nChars++;
+ }
+ else
+ {
+ eState = SsStop;
+ pHere--;
+ }
+ break;
+ case SsGetString:
+ if ( !rtl::isAsciiDigit( cToken ) )
+ {
+ nChars++;
+ }
+ else
+ {
+ eState = SsStop;
+ pHere--;
+ }
+ break;
+ default:
+ break;
+ } // switch
+ } // while
+
+ if ( nChars )
+ {
+ rSymbol = OUString( pStr, nChars );
+ }
+ else
+ {
+ rSymbol.clear();
+ }
+
+ pStr = pHere;
+
+ return isNumber;
+}
+
+
+// FIXME: should be grouping; it is only used though in case nStringsCnt is
+// near SV_MAX_COUNT_INPUT_STRINGS, in NumberStringDivision().
+
+bool ImpSvNumberInputScan::SkipThousands( const sal_Unicode*& pStr,
+ OUString& rSymbol ) const
+{
+ bool res = false;
+ OUStringBuffer sBuff(rSymbol);
+ sal_Unicode cToken;
+ const OUString& rThSep = pFormatter->GetNumThousandSep();
+ const sal_Unicode* pHere = pStr;
+ ScanState eState = SsStart;
+ sal_Int32 nCounter = 0; // counts 3 digits
+
+ for (;;)
+ {
+ cToken = *pHere;
+ if (cToken == 0 || eState == SsStop)
+ break;
+ pHere++;
+ switch (eState)
+ {
+ case SsStart:
+ if ( StringPtrContains( rThSep, pHere-1, 0 ) )
+ {
+ nCounter = 0;
+ eState = SsGetValue;
+ pHere += rThSep.getLength() - 1;
+ }
+ else
+ {
+ eState = SsStop;
+ pHere--;
+ }
+ break;
+ case SsGetValue:
+ if ( rtl::isAsciiDigit( cToken ) )
+ {
+ sBuff.append(cToken);
+ nCounter++;
+ if (nCounter == 3)
+ {
+ eState = SsStart;
+ res = true; // .000 combination found
+ }
+ }
+ else
+ {
+ eState = SsStop;
+ pHere--;
+ }
+ break;
+ default:
+ break;
+ } // switch
+ } // while
+
+ if (eState == SsGetValue) // break with less than 3 digits
+ {
+ if ( nCounter )
+ {
+ sBuff.remove( sBuff.getLength() - nCounter, nCounter );
+ }
+ pHere -= nCounter + rThSep.getLength(); // put back ThSep also
+ }
+ rSymbol = sBuff.makeStringAndClear();
+ pStr = pHere;
+
+ return res;
+}
+
+
+void ImpSvNumberInputScan::NumberStringDivision( const OUString& rString )
+{
+ const sal_Unicode* pStr = rString.getStr();
+ const sal_Unicode* const pEnd = pStr + rString.getLength();
+ while ( pStr < pEnd && nStringsCnt < SV_MAX_COUNT_INPUT_STRINGS )
+ {
+ if ( NextNumberStringSymbol( pStr, sStrArray[nStringsCnt] ) )
+ { // Number
+ IsNum[nStringsCnt] = true;
+ nNums[nNumericsCnt] = nStringsCnt;
+ nNumericsCnt++;
+ if (nStringsCnt >= SV_MAX_COUNT_INPUT_STRINGS - 7 &&
+ nPosThousandString == 0) // Only once
+ {
+ if ( SkipThousands( pStr, sStrArray[nStringsCnt] ) )
+ {
+ nPosThousandString = nStringsCnt;
+ }
+ }
+ }
+ else
+ {
+ IsNum[nStringsCnt] = false;
+ }
+ nStringsCnt++;
+ }
+}
+
+
+/**
+ * Whether rString contains rWhat at nPos
+ */
+bool ImpSvNumberInputScan::StringContainsImpl( const OUString& rWhat,
+ const OUString& rString, sal_Int32 nPos )
+{
+ if ( nPos + rWhat.getLength() <= rString.getLength() )
+ {
+ return StringPtrContainsImpl( rWhat, rString.getStr(), nPos );
+ }
+ return false;
+}
+
+
+/**
+ * Whether pString contains rWhat at nPos
+ */
+bool ImpSvNumberInputScan::StringPtrContainsImpl( const OUString& rWhat,
+ const sal_Unicode* pString, sal_Int32 nPos )
+{
+ if ( rWhat.isEmpty() )
+ {
+ return false;
+ }
+ const sal_Unicode* pWhat = rWhat.getStr();
+ const sal_Unicode* const pEnd = pWhat + rWhat.getLength();
+ const sal_Unicode* pStr = pString + nPos;
+ while ( pWhat < pEnd )
+ {
+ if ( *pWhat != *pStr )
+ {
+ return false;
+ }
+ pWhat++;
+ pStr++;
+ }
+ return true;
+}
+
+
+/**
+ * Whether rString contains word rWhat at nPos
+ */
+bool ImpSvNumberInputScan::StringContainsWord( const OUString& rWhat,
+ const OUString& rString, sal_Int32 nPos ) const
+{
+ if (rWhat.isEmpty() || rString.getLength() < nPos + rWhat.getLength())
+ return false;
+
+ if (StringPtrContainsImpl( rWhat, rString.getStr(), nPos))
+ {
+ nPos += rWhat.getLength();
+ if (nPos == rString.getLength())
+ return true; // word at end of string
+
+ /* TODO: we COULD invoke bells and whistles word break iterator to find
+ * the next boundary, but really ... this is called for date input, so
+ * how many languages do not separate the day and month names in some
+ * form? */
+
+ // Check simple ASCII first before invoking i18n or anything else.
+ const sal_Unicode c = rString[nPos];
+
+ // Common separating ASCII characters in date context.
+ switch (c)
+ {
+ case ' ':
+ case '-':
+ case '.':
+ case '/':
+ return true;
+ default:
+ ; // nothing
+ }
+
+ if (rtl::isAsciiAlphanumeric( c ))
+ return false; // Alpha or numeric is not word gap.
+
+ sal_Int32 nIndex = nPos;
+ rString.iterateCodePoints( &nIndex);
+ if (nPos+1 < nIndex)
+ return true; // Surrogate, assume these to be new words.
+
+ const sal_Int32 nType = pFormatter->GetCharClass()->getCharacterType( rString, nPos);
+ using namespace ::com::sun::star::i18n;
+
+ if ((nType & (KCharacterType::UPPER | KCharacterType::LOWER | KCharacterType::DIGIT)) != 0)
+ return false; // Alpha or numeric is not word gap.
+
+ if (nType & KCharacterType::LETTER)
+ return true; // Letter other than alpha is new word. (Is it?)
+
+ return true; // Catch all remaining as gap until we know better.
+ }
+
+ return false;
+}
+
+
+/**
+ * Skips the supplied char
+ */
+inline bool ImpSvNumberInputScan::SkipChar( sal_Unicode c, std::u16string_view rString,
+ sal_Int32& nPos )
+{
+ if ((nPos < static_cast<sal_Int32>(rString.size())) && (rString[nPos] == c))
+ {
+ nPos++;
+ return true;
+ }
+ return false;
+}
+
+
+/**
+ * Skips blanks
+ */
+inline bool ImpSvNumberInputScan::SkipBlanks( const OUString& rString,
+ sal_Int32& nPos )
+{
+ sal_Int32 nHere = nPos;
+ if ( nPos < rString.getLength() )
+ {
+ const sal_Unicode* p = rString.getStr() + nPos;
+ while ( *p == ' ' || *p == cNoBreakSpace || *p == cNarrowNoBreakSpace )
+ {
+ nPos++;
+ p++;
+ }
+ }
+ return nHere < nPos;
+}
+
+
+/**
+ * jump over rWhat in rString at nPos
+ */
+inline bool ImpSvNumberInputScan::SkipString( const OUString& rWhat,
+ const OUString& rString, sal_Int32& nPos )
+{
+ if ( StringContains( rWhat, rString, nPos ) )
+ {
+ nPos = nPos + rWhat.getLength();
+ return true;
+ }
+ return false;
+}
+
+
+/**
+ * Recognizes exactly ,111 in {3} and {3,2} or ,11 in {3,2} grouping
+ */
+inline bool ImpSvNumberInputScan::GetThousandSep( std::u16string_view rString,
+ sal_Int32& nPos,
+ sal_uInt16 nStringPos ) const
+{
+ const OUString& rSep = pFormatter->GetNumThousandSep();
+ // Is it an ordinary space instead of a no-break space?
+ bool bSpaceBreak = (rSep[0] == cNoBreakSpace || rSep[0] == cNarrowNoBreakSpace) &&
+ rString[0] == u' ' &&
+ rSep.getLength() == 1 && rString.size() == 1;
+ if (!((rString == rSep || bSpaceBreak) && // nothing else
+ nStringPos < nStringsCnt - 1 && // safety first!
+ IsNum[ nStringPos + 1 ] )) // number follows
+ {
+ return false; // no? => out
+ }
+
+ utl::DigitGroupingIterator aGrouping( pFormatter->GetLocaleData()->getDigitGrouping());
+ // Match ,### in {3} or ,## in {3,2}
+ /* FIXME: this could be refined to match ,## in {3,2} only if ,##,## or
+ * ,##,### and to match ,### in {3,2} only if it's the last. However,
+ * currently there is no track kept where group separators occur. In {3,2}
+ * #,###,### and #,##,## would be valid input, which maybe isn't even bad
+ * for #,###,###. Other combinations such as #,###,## maybe not. */
+ sal_Int32 nLen = sStrArray[ nStringPos + 1 ].getLength();
+ if (nLen == aGrouping.get() || // with 3 (or so) digits
+ nLen == aGrouping.advance().get() || // or with 2 (or 3 or so) digits
+ nPosThousandString == nStringPos + 1 ) // or concatenated
+ {
+ nPos = nPos + rSep.getLength();
+ return true;
+ }
+ return false;
+}
+
+
+/**
+ * Conversion of text to logical value
+ * "true" => 1:
+ * "false"=> -1:
+ * else => 0:
+ */
+short ImpSvNumberInputScan::GetLogical( std::u16string_view rString ) const
+{
+ short res;
+
+ const ImpSvNumberformatScan* pFS = pFormatter->GetFormatScanner();
+ if ( rString == pFS->GetTrueString() )
+ {
+ res = 1;
+ }
+ else if ( rString == pFS->GetFalseString() )
+ {
+ res = -1;
+ }
+ else
+ {
+ res = 0;
+ }
+ return res;
+}
+
+
+/**
+ * Converts a string containing a month name (JAN, January) at nPos into the
+ * month number (negative if abbreviated), returns 0 if nothing found
+ */
+short ImpSvNumberInputScan::GetMonth( const OUString& rString, sal_Int32& nPos )
+{
+ short res = 0; // no month found
+
+ if (rString.getLength() > nPos) // only if needed
+ {
+ if ( !bTextInitialized )
+ {
+ InitText();
+ }
+ sal_Int16 nMonths = pFormatter->GetCalendar()->getNumberOfMonthsInYear();
+ for ( sal_Int16 i = 0; i < nMonths; i++ )
+ {
+ if ( bScanGenitiveMonths && StringContainsWord( pUpperGenitiveMonthText[i], rString, nPos ) )
+ { // genitive full names first
+ nPos = nPos + pUpperGenitiveMonthText[i].getLength();
+ res = i + 1;
+ break; // for
+ }
+ else if ( bScanGenitiveMonths && StringContainsWord( pUpperGenitiveAbbrevMonthText[i], rString, nPos ) )
+ { // genitive abbreviated
+ nPos = nPos + pUpperGenitiveAbbrevMonthText[i].getLength();
+ res = sal::static_int_cast< short >(-(i+1)); // negative
+ break; // for
+ }
+ else if ( bScanPartitiveMonths && StringContainsWord( pUpperPartitiveMonthText[i], rString, nPos ) )
+ { // partitive full names
+ nPos = nPos + pUpperPartitiveMonthText[i].getLength();
+ res = i+1;
+ break; // for
+ }
+ else if ( bScanPartitiveMonths && StringContainsWord( pUpperPartitiveAbbrevMonthText[i], rString, nPos ) )
+ { // partitive abbreviated
+ nPos = nPos + pUpperPartitiveAbbrevMonthText[i].getLength();
+ res = sal::static_int_cast< short >(-(i+1)); // negative
+ break; // for
+ }
+ else if ( StringContainsWord( pUpperMonthText[i], rString, nPos ) )
+ { // noun full names
+ nPos = nPos + pUpperMonthText[i].getLength();
+ res = i+1;
+ break; // for
+ }
+ else if ( StringContainsWord( pUpperAbbrevMonthText[i], rString, nPos ) )
+ { // noun abbreviated
+ nPos = nPos + pUpperAbbrevMonthText[i].getLength();
+ res = sal::static_int_cast< short >(-(i+1)); // negative
+ break; // for
+ }
+ else if (i == 2 && pFormatter->GetLanguageTag().getLanguage() == "de")
+ {
+ if (pUpperAbbrevMonthText[i] == u"M\u00C4R" && StringContainsWord( "MRZ", rString, nPos))
+ { // Accept MRZ for MÄR
+ nPos = nPos + 3;
+ res = sal::static_int_cast< short >(-(i+1)); // negative
+ break; // for
+ }
+ else if (pUpperAbbrevMonthText[i] == "MRZ" && StringContainsWord( u"M\u00C4R"_ustr, rString, nPos))
+ { // And vice versa, accept MÄR for MRZ
+ nPos = nPos + 3;
+ res = sal::static_int_cast< short >(-(i+1)); // negative
+ break; // for
+ }
+ }
+ else if (i == 8)
+ {
+ // This assumes the weirdness is applicable to all locales.
+ // It is the case for at least en-* and de-* locales.
+ if (pUpperAbbrevMonthText[i] == "SEPT" && StringContainsWord( "SEP", rString, nPos))
+ { // #102136# The correct English form of month September abbreviated is
+ // SEPT, but almost every data contains SEP instead.
+ nPos = nPos + 3;
+ res = sal::static_int_cast< short >(-(i+1)); // negative
+ break; // for
+ }
+ else if (pUpperAbbrevMonthText[i] == "SEP" && StringContainsWord( "SEPT", rString, nPos))
+ { // And vice versa, accept SEPT for SEP
+ nPos = nPos + 4;
+ res = sal::static_int_cast< short >(-(i+1)); // negative
+ break; // for
+ }
+ }
+ }
+ if (!res)
+ {
+ // Brutal hack for German locales that know "Januar" or "Jänner".
+ /* TODO: add alternative month names to locale data? if there are
+ * more languages... */
+ const LanguageTag& rLanguageTag = pFormatter->GetLanguageTag();
+ if (rLanguageTag.getLanguage() == "de")
+ {
+ if (rLanguageTag.getCountry() == "AT")
+ {
+ // Locale data has Jänner/Jän
+ assert(pUpperMonthText[0] == u"J\u00C4NNER");
+ if (StringContainsWord( "JANUAR", rString, nPos))
+ {
+ nPos += 6;
+ res = 1;
+ }
+ else if (StringContainsWord( "JAN", rString, nPos))
+ {
+ nPos += 3;
+ res = -1;
+ }
+ }
+ else
+ {
+ // Locale data has Januar/Jan
+ assert(pUpperMonthText[0] == "JANUAR");
+ if (StringContainsWord( u"J\u00C4NNER"_ustr, rString, nPos))
+ {
+ nPos += 6;
+ res = 1;
+ }
+ else if (StringContainsWord( u"J\u00C4N"_ustr, rString, nPos))
+ {
+ nPos += 3;
+ res = -1;
+ }
+ }
+ }
+ }
+ }
+
+ return res;
+}
+
+
+/**
+ * Converts a string containing a DayOfWeek name (Mon, Monday) at nPos into the
+ * DayOfWeek number + 1 (negative if abbreviated), returns 0 if nothing found
+ */
+int ImpSvNumberInputScan::GetDayOfWeek( const OUString& rString, sal_Int32& nPos )
+{
+ int res = 0; // no day found
+
+ if (rString.getLength() > nPos) // only if needed
+ {
+ if ( !bTextInitialized )
+ {
+ InitText();
+ }
+ sal_Int16 nDays = pFormatter->GetCalendar()->getNumberOfDaysInWeek();
+ for ( sal_Int16 i = 0; i < nDays; i++ )
+ {
+ if ( StringContainsWord( pUpperDayText[i], rString, nPos ) )
+ { // full names first
+ nPos = nPos + pUpperDayText[i].getLength();
+ res = i + 1;
+ break; // for
+ }
+ if ( StringContainsWord( pUpperAbbrevDayText[i], rString, nPos ) )
+ { // abbreviated
+ nPos = nPos + pUpperAbbrevDayText[i].getLength();
+ res = -(i + 1); // negative
+ break; // for
+ }
+ }
+ }
+
+ return res;
+}
+
+
+/**
+ * Reading a currency symbol
+ * '$' => true
+ * else => false
+ */
+bool ImpSvNumberInputScan::GetCurrency( const OUString& rString, sal_Int32& nPos )
+{
+ if ( rString.getLength() > nPos )
+ {
+ if ( !aUpperCurrSymbol.getLength() )
+ { // If no format specified the currency of the currently active locale.
+ LanguageType eLang = (mpFormat ? mpFormat->GetLanguage() :
+ pFormatter->GetLocaleData()->getLanguageTag().getLanguageType());
+ aUpperCurrSymbol = pFormatter->GetCharClass()->uppercase(
+ SvNumberFormatter::GetCurrencyEntry( eLang ).GetSymbol() );
+ }
+ if ( StringContains( aUpperCurrSymbol, rString, nPos ) )
+ {
+ nPos = nPos + aUpperCurrSymbol.getLength();
+ return true;
+ }
+ if ( mpFormat )
+ {
+ OUString aSymbol, aExtension;
+ if ( mpFormat->GetNewCurrencySymbol( aSymbol, aExtension ) )
+ {
+ if ( aSymbol.getLength() <= rString.getLength() - nPos )
+ {
+ aSymbol = pFormatter->GetCharClass()->uppercase(aSymbol);
+ if ( StringContains( aSymbol, rString, nPos ) )
+ {
+ nPos = nPos + aSymbol.getLength();
+ return true;
+ }
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+
+/**
+ * Reading the time period specifier (AM/PM) for the 12 hour clock
+ *
+ * Returns:
+ * "AM" or "PM" => true
+ * else => false
+ *
+ * nAmPos:
+ * "AM" => 1
+ * "PM" => -1
+ * else => 0
+*/
+bool ImpSvNumberInputScan::GetTimeAmPm( const OUString& rString, sal_Int32& nPos )
+{
+
+ if ( rString.getLength() > nPos )
+ {
+ const CharClass* pChr = pFormatter->GetCharClass();
+ const LocaleDataWrapper* pLoc = pFormatter->GetLocaleData();
+ if ( StringContains( pChr->uppercase( pLoc->getTimeAM() ), rString, nPos ) )
+ {
+ nAmPm = 1;
+ nPos = nPos + pLoc->getTimeAM().getLength();
+ return true;
+ }
+ else if ( StringContains( pChr->uppercase( pLoc->getTimePM() ), rString, nPos ) )
+ {
+ nAmPm = -1;
+ nPos = nPos + pLoc->getTimePM().getLength();
+ return true;
+ }
+ }
+
+ return false;
+}
+
+
+/**
+ * Read a decimal separator (',')
+ * ',' => true
+ * else => false
+ */
+inline bool ImpSvNumberInputScan::GetDecSep( std::u16string_view rString, sal_Int32& nPos ) const
+{
+ if ( static_cast<sal_Int32>(rString.size()) > nPos )
+ {
+ const OUString& rSep = pFormatter->GetNumDecimalSep();
+ if ( o3tl::starts_with(rString.substr(nPos), rSep) )
+ {
+ nPos = nPos + rSep.getLength();
+ return true;
+ }
+ const OUString& rSepAlt = pFormatter->GetNumDecimalSepAlt();
+ if ( !rSepAlt.isEmpty() && o3tl::starts_with(rString.substr(nPos), rSepAlt) )
+ {
+ nPos = nPos + rSepAlt.getLength();
+ return true;
+ }
+ }
+ return false;
+}
+
+
+/**
+ * Reading a hundredth seconds separator
+ */
+inline bool ImpSvNumberInputScan::GetTime100SecSep( std::u16string_view rString, sal_Int32& nPos ) const
+{
+ if ( static_cast<sal_Int32>(rString.size()) > nPos )
+ {
+ if (bIso8601Tsep)
+ {
+ // ISO 8601 specifies both '.' dot and ',' comma as fractional
+ // separator.
+ if (rString[nPos] == '.' || rString[nPos] == ',')
+ {
+ ++nPos;
+ return true;
+ }
+ }
+ // Even in an otherwise ISO 8601 string be lenient and accept the
+ // locale defined separator.
+ const OUString& rSep = pFormatter->GetLocaleData()->getTime100SecSep();
+ if ( o3tl::starts_with(rString.substr(nPos), rSep))
+ {
+ nPos = nPos + rSep.getLength();
+ return true;
+ }
+ }
+ return false;
+}
+
+
+/**
+ * Read a sign including brackets
+ * '+' => 1
+ * '-' => -1
+ * u'−' => -1
+ * '(' => -1, bNegCheck = 1
+ * else => 0
+ */
+int ImpSvNumberInputScan::GetSign( std::u16string_view rString, sal_Int32& nPos )
+{
+ if (static_cast<sal_Int32>(rString.size()) > nPos)
+ switch (rString[ nPos ])
+ {
+ case '+':
+ nPos++;
+ return 1;
+ case '(': // '(' similar to '-' ?!?
+ bNegCheck = true;
+ [[fallthrough]];
+ case '-':
+ // tdf#117037 - unicode minus (0x2212)
+ case u'−':
+ nPos++;
+ return -1;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+
+/**
+ * Read a sign with an exponent
+ * '+' => 1
+ * '-' => -1
+ * else => 0
+ */
+short ImpSvNumberInputScan::GetESign( std::u16string_view rString, sal_Int32& nPos )
+{
+ if (static_cast<sal_Int32>(rString.size()) > nPos)
+ {
+ switch (rString[nPos])
+ {
+ case '+':
+ nPos++;
+ return 1;
+ case '-':
+ nPos++;
+ return -1;
+ default:
+ break;
+ }
+ }
+ return 0;
+}
+
+
+/**
+ * i counts string portions, j counts numbers thereof.
+ * It should had been called SkipNumber instead.
+ */
+inline bool ImpSvNumberInputScan::GetNextNumber( sal_uInt16& i, sal_uInt16& j ) const
+{
+ if ( i < nStringsCnt && IsNum[i] )
+ {
+ j++;
+ i++;
+ return true;
+ }
+ return false;
+}
+
+
+bool ImpSvNumberInputScan::GetTimeRef( double& fOutNumber,
+ sal_uInt16 nIndex, // j-value of the first numeric time part of input, default 0
+ sal_uInt16 nCnt, // count of numeric time parts
+ SvNumInputOptions eInputOptions
+ ) const
+{
+ bool bRet = true;
+ sal_Int32 nHour;
+ sal_Int32 nMinute = 0;
+ sal_Int32 nSecond = 0;
+ double fSecond100 = 0.0;
+ sal_uInt16 nStartIndex = nIndex;
+
+ if (nDecPos == 2 && (nCnt == 3 || nCnt == 2)) // 20:45.5 or 45.5
+ {
+ nHour = 0;
+ }
+ else if (mpFormat && nDecPos == 0 && nCnt == 2 && mpFormat->IsMinuteSecondFormat())
+ {
+ // Input on MM:SS format, instead of doing HH:MM:00
+ nHour = 0;
+ }
+ else if (nIndex - nStartIndex < nCnt)
+ {
+ const OUString& rValStr = sStrArray[nNums[nIndex++]];
+ nHour = rValStr.toInt32();
+ if (nHour == 0 && rValStr != "0" && rValStr != "00")
+ bRet = false; // overflow -> Text
+ }
+ else
+ {
+ nHour = 0;
+ bRet = false;
+ SAL_WARN( "svl.numbers", "ImpSvNumberInputScan::GetTimeRef: bad number index");
+ }
+
+ // 0:123 or 0:0:123 or 0:123:59 is valid
+ bool bAllowDuration = (nHour == 0 && !nAmPm);
+
+ if (nAmPm && nHour > 12) // not a valid AM/PM clock time
+ {
+ bRet = false;
+ }
+ else if (nAmPm == -1 && nHour != 12) // PM
+ {
+ nHour += 12;
+ }
+ else if (nAmPm == 1 && nHour == 12) // 12 AM
+ {
+ nHour = 0;
+ }
+
+ if (nDecPos == 2 && nCnt == 2) // 45.5
+ {
+ nMinute = 0;
+ }
+ else if (nIndex - nStartIndex < nCnt)
+ {
+ const OUString& rValStr = sStrArray[nNums[nIndex++]];
+ nMinute = rValStr.toInt32();
+ if (nMinute == 0 && rValStr != "0" && rValStr != "00")
+ bRet = false; // overflow -> Text
+ if (!(eInputOptions & SvNumInputOptions::LAX_TIME) && !bAllowDuration
+ && nIndex > 1 && nMinute > 59)
+ bRet = false; // 1:60 or 1:123 is invalid, 123:1 or 0:123 is valid
+ if (bAllowDuration)
+ bAllowDuration = (nMinute == 0);
+ }
+ if (nIndex - nStartIndex < nCnt)
+ {
+ const OUString& rValStr = sStrArray[nNums[nIndex++]];
+ nSecond = rValStr.toInt32();
+ if (nSecond == 0 && rValStr != "0" && rValStr != "00")
+ bRet = false; // overflow -> Text
+ if (!(eInputOptions & SvNumInputOptions::LAX_TIME) && !bAllowDuration
+ && nIndex > 1 && nSecond > 59 && !(nHour == 23 && nMinute == 59 && nSecond == 60))
+ bRet = false; // 1:60 or 1:123 or 1:1:123 is invalid, 123:1 or 123:1:1 or 0:0:123 is valid, or leap second
+ }
+ if (nIndex - nStartIndex < nCnt)
+ {
+ fSecond100 = StringToDouble( sStrArray[nNums[nIndex]], true );
+ }
+ fOutNumber = (static_cast<double>(nHour)*3600 +
+ static_cast<double>(nMinute)*60 +
+ static_cast<double>(nSecond) +
+ fSecond100)/86400.0;
+ return bRet;
+}
+
+
+sal_uInt16 ImpSvNumberInputScan::ImplGetDay( sal_uInt16 nIndex ) const
+{
+ sal_uInt16 nRes = 0;
+
+ if (sStrArray[nNums[nIndex]].getLength() <= 2)
+ {
+ sal_uInt16 nNum = static_cast<sal_uInt16>(sStrArray[nNums[nIndex]].toInt32());
+ if (nNum <= 31)
+ {
+ nRes = nNum;
+ }
+ }
+
+ return nRes;
+}
+
+
+sal_uInt16 ImpSvNumberInputScan::ImplGetMonth( sal_uInt16 nIndex ) const
+{
+ // Preset invalid month number
+ sal_uInt16 nRes = pFormatter->GetCalendar()->getNumberOfMonthsInYear();
+
+ if (sStrArray[nNums[nIndex]].getLength() <= 2)
+ {
+ sal_uInt16 nNum = static_cast<sal_uInt16>(sStrArray[nNums[nIndex]].toInt32());
+ if ( 0 < nNum && nNum <= nRes )
+ {
+ nRes = nNum - 1; // zero based for CalendarFieldIndex::MONTH
+ }
+ }
+
+ return nRes;
+}
+
+
+/**
+ * 30 -> 1930, 29 -> 2029, or 56 -> 1756, 55 -> 1855, ...
+ */
+sal_uInt16 ImpSvNumberInputScan::ImplGetYear( sal_uInt16 nIndex )
+{
+ sal_uInt16 nYear = 0;
+
+ sal_Int32 nLen = sStrArray[nNums[nIndex]].getLength();
+ // 16-bit integer year width can have 5 digits, allow for one additional
+ // leading zero as convention.
+ if (nLen <= 6)
+ {
+ nYear = static_cast<sal_uInt16>(sStrArray[nNums[nIndex]].toInt32());
+ // A year in another, not Gregorian CE era is never expanded.
+ // A year < 100 entered with at least 3 digits with leading 0 is taken
+ // as is without expansion.
+ if (mbEraCE == kDefaultEra && nYear < 100 && nLen < 3)
+ {
+ nYear = SvNumberFormatter::ExpandTwoDigitYear( nYear, nYear2000 );
+ }
+ }
+
+ return nYear;
+}
+
+
+bool ImpSvNumberInputScan::MayBeIso8601()
+{
+ if (nMayBeIso8601 == 0)
+ {
+ nMayBeIso8601 = 1;
+ sal_Int32 nLen = ((nNumericsCnt >= 1 && nNums[0] < nStringsCnt) ? sStrArray[nNums[0]].getLength() : 0);
+ if (nLen)
+ {
+ sal_Int32 n;
+ if (nNumericsCnt >= 3 && nNums[2] < nStringsCnt &&
+ sStrArray[nNums[0]+1] == "-" && // separator year-month
+ (n = sStrArray[nNums[1]].toInt32()) >= 1 && n <= 12 && // month
+ sStrArray[nNums[1]+1] == "-" && // separator month-day
+ (n = sStrArray[nNums[2]].toInt32()) >= 1 && n <= 31) // day
+ {
+ // Year (nNums[0]) value not checked, may be anything, but
+ // length (number of digits) is checked.
+ nMayBeIso8601 = (nLen >= 4 ? 4 : (nLen == 3 ? 3 : (nLen > 0 ? 2 : 1)));
+ }
+ }
+ }
+ return nMayBeIso8601 > 1;
+}
+
+
+bool ImpSvNumberInputScan::CanForceToIso8601( DateOrder eDateOrder )
+{
+ int nCanForceToIso8601 = 0;
+ if (!MayBeIso8601())
+ {
+ return false;
+ }
+ else if (nMayBeIso8601 >= 3)
+ {
+ return true; // at least 3 digits in year
+ }
+ else
+ {
+ if (eDateOrder == DateOrder::Invalid)
+ {
+ // As if any of the cases below can be applied, but only if a
+ // locale dependent date pattern was not matched.
+ if ((GetDatePatternNumbers() == nNumericsCnt) && IsDatePatternNumberOfType(0,'Y'))
+ return false;
+ eDateOrder = GetDateOrder();
+ }
+
+ nCanForceToIso8601 = 1;
+ }
+
+ sal_Int32 n;
+ switch (eDateOrder)
+ {
+ case DateOrder::DMY: // "day" value out of range => ISO 8601 year
+ n = sStrArray[nNums[0]].toInt32();
+ if (n < 1 || n > 31)
+ {
+ nCanForceToIso8601 = 2;
+ }
+ break;
+ case DateOrder::MDY: // "month" value out of range => ISO 8601 year
+ n = sStrArray[nNums[0]].toInt32();
+ if (n < 1 || n > 12)
+ {
+ nCanForceToIso8601 = 2;
+ }
+ break;
+ case DateOrder::YMD: // always possible
+ nCanForceToIso8601 = 2;
+ break;
+ default: break;
+ }
+ return nCanForceToIso8601 > 1;
+}
+
+
+bool ImpSvNumberInputScan::IsAcceptableIso8601()
+{
+ if (mpFormat && (mpFormat->GetType() & SvNumFormatType::DATE))
+ {
+ switch (pFormatter->GetEvalDateFormat())
+ {
+ case NF_EVALDATEFORMAT_INTL:
+ return CanForceToIso8601( GetDateOrder());
+ case NF_EVALDATEFORMAT_FORMAT:
+ return CanForceToIso8601( mpFormat->GetDateOrder());
+ default:
+ return CanForceToIso8601( GetDateOrder()) || CanForceToIso8601( mpFormat->GetDateOrder());
+ }
+ }
+ return CanForceToIso8601( GetDateOrder());
+}
+
+
+bool ImpSvNumberInputScan::MayBeMonthDate()
+{
+ if (nMayBeMonthDate == 0)
+ {
+ nMayBeMonthDate = 1;
+ if (nNumericsCnt >= 2 && nNums[1] < nStringsCnt)
+ {
+ // "-Jan-"
+ const OUString& rM = sStrArray[ nNums[ 0 ] + 1 ];
+ if (rM.getLength() >= 3 && rM[0] == '-' && rM[ rM.getLength() - 1] == '-')
+ {
+ // Check year length assuming at least 3 digits (including
+ // leading zero). Two digit years 1..31 are out of luck here
+ // and may be taken as day of month.
+ bool bYear1 = (sStrArray[nNums[0]].getLength() >= 3);
+ bool bYear2 = (sStrArray[nNums[1]].getLength() >= 3);
+ sal_Int32 n;
+ bool bDay1 = !bYear1;
+ if (bDay1)
+ {
+ n = sStrArray[nNums[0]].toInt32();
+ bDay1 = n >= 1 && n <= 31;
+ }
+ bool bDay2 = !bYear2;
+ if (bDay2)
+ {
+ n = sStrArray[nNums[1]].toInt32();
+ bDay2 = n >= 1 && n <= 31;
+ }
+
+ if (bDay1 && !bDay2)
+ {
+ nMayBeMonthDate = 2; // dd-month-yy
+ }
+ else if (!bDay1 && bDay2)
+ {
+ nMayBeMonthDate = 3; // yy-month-dd
+ }
+ else if (bDay1 && bDay2)
+ {
+ // Ambiguous ##-MMM-## date, but some big vendor's database
+ // reports write this crap, assume this always to be
+ nMayBeMonthDate = 2; // dd-month-yy
+ }
+ }
+ }
+ }
+ return nMayBeMonthDate > 1;
+}
+
+
+/** If a string is a separator plus '-' minus sign preceding a 'Y' year in
+ a date pattern at position nPat.
+ */
+static bool lcl_IsSignedYearSep( std::u16string_view rStr, std::u16string_view rPat, sal_Int32 nPat )
+{
+ bool bOk = false;
+ sal_Int32 nLen = rStr.size();
+ if (nLen > 1 && rStr[nLen-1] == '-')
+ {
+ --nLen;
+ if (nPat + nLen < static_cast<sal_Int32>(rPat.size()) && rPat[nPat+nLen] == 'Y')
+ {
+ // Signed year is possible.
+ bOk = (rPat.find( rStr.substr( 0, nLen), nPat) == static_cast<size_t>(nPat));
+ }
+ }
+ return bOk;
+}
+
+
+/** Length of separator usually is 1 but theoretically could be anything. */
+static sal_Int32 lcl_getPatternSeparatorLength( std::u16string_view rPat, sal_Int32 nPat )
+{
+ sal_Int32 nSep = nPat;
+ sal_Unicode c;
+ while (nSep < static_cast<sal_Int32>(rPat.size()) && (c = rPat[nSep]) != 'D' && c != 'M' && c != 'Y')
+ ++nSep;
+ return nSep - nPat;
+}
+
+
+bool ImpSvNumberInputScan::IsAcceptedDatePattern( sal_uInt16 nStartPatternAt )
+{
+ if (nAcceptedDatePattern >= -1)
+ {
+ return (nAcceptedDatePattern >= 0);
+ }
+ if (!nNumericsCnt)
+ {
+ nAcceptedDatePattern = -1;
+ }
+ else if (!sDateAcceptancePatterns.hasElements())
+ {
+ // The current locale is the format's locale, if a format is present.
+ const NfEvalDateFormat eEDF = pFormatter->GetEvalDateFormat();
+ if (!mpFormat || eEDF == NF_EVALDATEFORMAT_FORMAT || mpFormat->GetLanguage() == pFormatter->GetLanguage())
+ {
+ sDateAcceptancePatterns = pFormatter->GetLocaleData()->getDateAcceptancePatterns();
+ }
+ else
+ {
+ OnDemandLocaleDataWrapper& xLocaleData = pFormatter->GetOnDemandLocaleDataWrapper(
+ SvNumberFormatter::InputScannerPrivateAccess());
+ const LanguageTag aSaveLocale( xLocaleData->getLanguageTag() );
+ assert(mpFormat->GetLanguage() == aSaveLocale.getLanguageType()); // prerequisite
+ // Obtain formatter's locale's (e.g. system) patterns.
+ xLocaleData.changeLocale( LanguageTag( pFormatter->GetLanguage()));
+ const css::uno::Sequence<OUString> aLocalePatterns( xLocaleData->getDateAcceptancePatterns());
+ // Reset to format's locale.
+ xLocaleData.changeLocale( aSaveLocale);
+ // When concatenating don't care about duplicates, combining
+ // weeding those out reallocs yet another time and probably doesn't
+ // take less time than looping over two additional patterns below...
+ switch (eEDF)
+ {
+ case NF_EVALDATEFORMAT_FORMAT:
+ assert(!"shouldn't reach here");
+ break;
+ case NF_EVALDATEFORMAT_INTL:
+ sDateAcceptancePatterns = aLocalePatterns;
+ break;
+ case NF_EVALDATEFORMAT_INTL_FORMAT:
+ sDateAcceptancePatterns = comphelper::concatSequences(
+ aLocalePatterns,
+ xLocaleData->getDateAcceptancePatterns());
+ break;
+ case NF_EVALDATEFORMAT_FORMAT_INTL:
+ sDateAcceptancePatterns = comphelper::concatSequences(
+ xLocaleData->getDateAcceptancePatterns(),
+ aLocalePatterns);
+ break;
+ }
+ }
+ SAL_WARN_IF( !sDateAcceptancePatterns.hasElements(), "svl.numbers", "ImpSvNumberInputScan::IsAcceptedDatePattern: no date acceptance patterns");
+ nAcceptedDatePattern = (sDateAcceptancePatterns.hasElements() ? -2 : -1);
+ }
+
+ if (nAcceptedDatePattern == -1)
+ {
+ return false;
+ }
+ nDatePatternStart = nStartPatternAt; // remember start particle
+
+ const sal_Int32 nMonthsInYear = pFormatter->GetCalendar()->getNumberOfMonthsInYear();
+
+ for (sal_Int32 nPattern=0; nPattern < sDateAcceptancePatterns.getLength(); ++nPattern)
+ {
+ const OUString& rPat = sDateAcceptancePatterns[nPattern];
+ if (rPat.getLength() == 3)
+ {
+ // Ignore a pattern that would match numeric input with decimal
+ // separator. It may had been read from configuration or resulted
+ // from the locales' patterns concatenation above.
+ if ( rPat[1] == pFormatter->GetLocaleData()->getNumDecimalSep().toChar()
+ || rPat[1] == pFormatter->GetLocaleData()->getNumDecimalSepAlt().toChar())
+ {
+ SAL_WARN("svl.numbers", "ignoring date acceptance pattern with decimal separator ambiguity: " << rPat);
+ continue; // for, next pattern
+ }
+ }
+ sal_uInt16 nNext = nDatePatternStart;
+ nDatePatternNumbers = 0;
+ bool bOk = true;
+ sal_Int32 nPat = 0;
+ for ( ; nPat < rPat.getLength() && bOk && nNext < nStringsCnt; ++nPat, ++nNext)
+ {
+ const sal_Unicode c = rPat[nPat];
+ switch (c)
+ {
+ case 'Y':
+ case 'M':
+ case 'D':
+ bOk = IsNum[nNext];
+ if (bOk && (c == 'M' || c == 'D'))
+ {
+ // Check the D and M cases for plausibility. This also
+ // prevents recognition of date instead of number with a
+ // numeric group input if date separator is identical to
+ // group separator, for example with D.M as a pattern and
+ // #.### as a group.
+ sal_Int32 nMaxLen, nMaxVal;
+ switch (c)
+ {
+ case 'M':
+ nMaxLen = 2;
+ nMaxVal = nMonthsInYear;
+ break;
+ case 'D':
+ nMaxLen = 2;
+ nMaxVal = 31;
+ break;
+ default:
+ // This merely exists against
+ // -Werror=maybe-uninitialized, which is nonsense
+ // after the (c == 'M' || c == 'D') check above,
+ // but ...
+ nMaxLen = 2;
+ nMaxVal = 31;
+ }
+ bOk = (sStrArray[nNext].getLength() <= nMaxLen);
+ if (bOk)
+ {
+ sal_Int32 nNum = sStrArray[nNext].toInt32();
+ bOk = (1 <= nNum && nNum <= nMaxVal);
+ }
+ }
+ if (bOk)
+ ++nDatePatternNumbers;
+ break;
+ default:
+ bOk = !IsNum[nNext];
+ if (bOk)
+ {
+ const sal_Int32 nSepLen = lcl_getPatternSeparatorLength( rPat, nPat);
+ // Non-numeric input must match separator exactly to be
+ // accepted as such.
+ const sal_Int32 nLen = sStrArray[nNext].getLength();
+ bOk = (nLen == nSepLen && rPat.indexOf( sStrArray[nNext], nPat) == nPat);
+ if (bOk)
+ {
+ nPat += nLen - 1;
+ }
+ else if ((bOk = lcl_IsSignedYearSep( sStrArray[nNext], rPat, nPat)))
+ {
+ nPat += nLen - 2;
+ }
+ else if (nPat + nLen > rPat.getLength() && sStrArray[nNext][ nLen - 1 ] == ' ')
+ {
+ using namespace comphelper::string;
+ // Trailing blanks in input.
+ OUStringBuffer aBuf(sStrArray[nNext]);
+ aBuf.stripEnd();
+ // Expand again in case of pattern "M. D. " and
+ // input "M. D. ", maybe fetched far, but...
+ padToLength(aBuf, rPat.getLength() - nPat, ' ');
+ bOk = (rPat.indexOf( aBuf, nPat) == nPat);
+ if (bOk)
+ {
+ nPat += aBuf.getLength() - 1;
+ }
+ }
+ }
+ break;
+ }
+ }
+ if (bOk)
+ {
+ // Check for trailing characters mismatch.
+ if (nNext < nStringsCnt)
+ {
+ // Pattern end but not input end.
+ // A trailing blank may be part of the current pattern input,
+ // if pattern is "D.M." and input is "D.M. hh:mm" last was
+ // ". ", or may be following the current pattern input, if
+ // pattern is "D.M" and input is "D.M hh:mm" last was "M".
+ sal_Int32 nPos = 0;
+ sal_uInt16 nCheck;
+ if (nPat > 0 && nNext > 0)
+ {
+ // nPat is one behind after the for loop.
+ sal_Int32 nPatCheck = nPat - 1;
+ switch (rPat[nPatCheck])
+ {
+ case 'Y':
+ case 'M':
+ case 'D':
+ nCheck = nNext;
+ break;
+ default:
+ {
+ nCheck = nNext - 1;
+ // Advance position in input to match length of
+ // non-YMD (separator) characters in pattern.
+ sal_Unicode c;
+ do
+ {
+ ++nPos;
+ c = rPat[--nPatCheck];
+ } while (c != 'Y' && c != 'M' && c != 'D' && nPatCheck > 0);
+ }
+ }
+ }
+ else
+ {
+ nCheck = nNext;
+ }
+ if (!IsNum[nCheck])
+ {
+ // Trailing (or separating if time follows) blanks are ok.
+ // No blank and a following number is not.
+ const bool bBlanks = SkipBlanks( sStrArray[nCheck], nPos);
+ if (nPos == sStrArray[nCheck].getLength() && (bBlanks || !IsNum[nNext]))
+ {
+ nAcceptedDatePattern = nPattern;
+ return true;
+ }
+ }
+ }
+ else if (nPat == rPat.getLength())
+ {
+ // Input end and pattern end => match.
+ nAcceptedDatePattern = nPattern;
+ return true;
+ }
+ // else Input end but not pattern end, no match.
+ }
+ }
+ nAcceptedDatePattern = -1;
+ return false;
+}
+
+
+bool ImpSvNumberInputScan::SkipDatePatternSeparator( sal_uInt16 nParticle, sal_Int32 & rPos, bool & rSignedYear )
+{
+ // If not initialized yet start with first number, if any.
+ if (!IsAcceptedDatePattern( nNumericsCnt ? nNums[0] : 0 ))
+ {
+ return false;
+ }
+ if (nParticle < nDatePatternStart || nParticle >= nStringsCnt || IsNum[nParticle])
+ {
+ return false;
+ }
+ sal_uInt16 nNext = nDatePatternStart;
+ const OUString& rPat = sDateAcceptancePatterns[nAcceptedDatePattern];
+ for (sal_Int32 nPat = 0; nPat < rPat.getLength() && nNext < nStringsCnt; ++nPat, ++nNext)
+ {
+ switch (rPat[nPat])
+ {
+ case 'Y':
+ case 'M':
+ case 'D':
+ break;
+ default:
+ if (nNext == nParticle)
+ {
+ const sal_Int32 nSepLen = lcl_getPatternSeparatorLength( rPat, nPat);
+ const sal_Int32 nLen = sStrArray[nNext].getLength();
+ bool bOk = (nLen == nSepLen && rPat.indexOf( sStrArray[nNext], nPat) == nPat);
+ if (!bOk)
+ {
+ bOk = lcl_IsSignedYearSep( sStrArray[nNext], rPat, nPat);
+ if (bOk)
+ rSignedYear = true;
+ }
+ if (!bOk && (nPat + nLen > rPat.getLength() && sStrArray[nNext][ nLen - 1 ] == ' '))
+ {
+ // The same ugly trailing blanks check as in
+ // IsAcceptedDatePattern().
+ using namespace comphelper::string;
+ OUStringBuffer aBuf(sStrArray[nNext]);
+ aBuf.stripEnd();
+ padToLength(aBuf, rPat.getLength() - nPat, ' ');
+ bOk = (rPat.indexOf(aBuf, nPat) == nPat);
+ }
+ if (bOk)
+ {
+ rPos = nLen; // yes, set, not add!
+ return true;
+ }
+ else
+ return false;
+ }
+ nPat += sStrArray[nNext].getLength() - 1;
+ break;
+ }
+ }
+ return false;
+}
+
+
+sal_uInt16 ImpSvNumberInputScan::GetDatePatternNumbers()
+{
+ // If not initialized yet start with first number, if any.
+ if (!IsAcceptedDatePattern( nNumericsCnt ? nNums[0] : 0 ))
+ {
+ return 0;
+ }
+ return nDatePatternNumbers;
+}
+
+
+bool ImpSvNumberInputScan::IsDatePatternNumberOfType( sal_uInt16 nNumber, sal_Unicode cType )
+{
+ if (GetDatePatternNumbers() <= nNumber)
+ return false;
+
+ sal_uInt16 nNum = 0;
+ const OUString& rPat = sDateAcceptancePatterns[nAcceptedDatePattern];
+ for (sal_Int32 nPat = 0; nPat < rPat.getLength(); ++nPat)
+ {
+ switch (rPat[nPat])
+ {
+ case 'Y':
+ case 'M':
+ case 'D':
+ if (nNum == nNumber)
+ return rPat[nPat] == cType;
+ ++nNum;
+ break;
+ }
+ }
+ return false;
+}
+
+
+sal_uInt32 ImpSvNumberInputScan::GetDatePatternOrder()
+{
+ // If not initialized yet start with first number, if any.
+ if (!IsAcceptedDatePattern( nNumericsCnt ? nNums[0] : 0 ))
+ {
+ return 0;
+ }
+ sal_uInt32 nOrder = 0;
+ const OUString& rPat = sDateAcceptancePatterns[nAcceptedDatePattern];
+ for (sal_Int32 nPat = 0; nPat < rPat.getLength() && !(nOrder & 0xff0000); ++nPat)
+ {
+ switch (rPat[nPat])
+ {
+ case 'Y':
+ case 'M':
+ case 'D':
+ nOrder = (nOrder << 8) | rPat[nPat];
+ break;
+ }
+ }
+ return nOrder;
+}
+
+
+DateOrder ImpSvNumberInputScan::GetDateOrder( bool bFromFormatIfNoPattern )
+{
+ sal_uInt32 nOrder = GetDatePatternOrder();
+ if (!nOrder)
+ {
+ if (bFromFormatIfNoPattern && mpFormat)
+ return mpFormat->GetDateOrder();
+ else
+ return pFormatter->GetLocaleData()->getDateOrder();
+ }
+ switch ((nOrder & 0xff0000) >> 16)
+ {
+ case 'Y':
+ if ((((nOrder & 0xff00) >> 8) == 'M') && ((nOrder & 0xff) == 'D'))
+ {
+ return DateOrder::YMD;
+ }
+ break;
+ case 'M':
+ if ((((nOrder & 0xff00) >> 8) == 'D') && ((nOrder & 0xff) == 'Y'))
+ {
+ return DateOrder::MDY;
+ }
+ break;
+ case 'D':
+ if ((((nOrder & 0xff00) >> 8) == 'M') && ((nOrder & 0xff) == 'Y'))
+ {
+ return DateOrder::DMY;
+ }
+ break;
+ default:
+ case 0:
+ switch ((nOrder & 0xff00) >> 8)
+ {
+ case 'Y':
+ switch (nOrder & 0xff)
+ {
+ case 'M':
+ return DateOrder::YMD;
+ }
+ break;
+ case 'M':
+ switch (nOrder & 0xff)
+ {
+ case 'Y':
+ return DateOrder::DMY;
+ case 'D':
+ return DateOrder::MDY;
+ }
+ break;
+ case 'D':
+ switch (nOrder & 0xff)
+ {
+ case 'Y':
+ return DateOrder::MDY;
+ case 'M':
+ return DateOrder::DMY;
+ }
+ break;
+ default:
+ case 0:
+ switch (nOrder & 0xff)
+ {
+ case 'Y':
+ return DateOrder::YMD;
+ case 'M':
+ return DateOrder::MDY;
+ case 'D':
+ return DateOrder::DMY;
+ }
+ break;
+ }
+ }
+ SAL_WARN( "svl.numbers", "ImpSvNumberInputScan::GetDateOrder: undefined, falling back to locale's default");
+ return pFormatter->GetLocaleData()->getDateOrder();
+}
+
+LongDateOrder ImpSvNumberInputScan::GetMiddleMonthLongDateOrder( bool bFormatTurn,
+ const LocaleDataWrapper* pLoc,
+ DateOrder eDateOrder )
+{
+ if (MayBeMonthDate())
+ return (nMayBeMonthDate == 2) ? LongDateOrder::DMY : LongDateOrder::YMD;
+
+ LongDateOrder eLDO;
+ const sal_uInt32 nExactDateOrder = (bFormatTurn ? mpFormat->GetExactDateOrder() : 0);
+ if (!nExactDateOrder)
+ eLDO = pLoc->getLongDateOrder();
+ else if ((((nExactDateOrder >> 16) & 0xff) == 'Y') && ((nExactDateOrder & 0xff) == 'D'))
+ eLDO = LongDateOrder::YMD;
+ else if ((((nExactDateOrder >> 16) & 0xff) == 'D') && ((nExactDateOrder & 0xff) == 'Y'))
+ eLDO = LongDateOrder::DMY;
+ else
+ eLDO = pLoc->getLongDateOrder();
+ if (eLDO != LongDateOrder::YMD && eLDO != LongDateOrder::DMY)
+ {
+ switch (eDateOrder)
+ {
+ case DateOrder::YMD:
+ eLDO = LongDateOrder::YMD;
+ break;
+ case DateOrder::DMY:
+ eLDO = LongDateOrder::DMY;
+ break;
+ default:
+ ; // nothing, not a date
+ }
+ }
+ else if (eLDO == LongDateOrder::DMY && eDateOrder == DateOrder::YMD)
+ {
+ // Check possible order and maybe switch.
+ if (!ImplGetDay(0) && ImplGetDay(1))
+ eLDO = LongDateOrder::YMD;
+ }
+ else if (eLDO == LongDateOrder::YMD && eDateOrder == DateOrder::DMY)
+ {
+ // Check possible order and maybe switch.
+ if (!ImplGetDay(1) && ImplGetDay(0))
+ eLDO = LongDateOrder::DMY;
+ }
+ return eLDO;
+}
+
+bool ImpSvNumberInputScan::GetDateRef( double& fDays, sal_uInt16& nCounter )
+{
+ using namespace ::com::sun::star::i18n;
+ NfEvalDateFormat eEDF;
+ int nFormatOrder;
+ if ( mpFormat && (mpFormat->GetType() & SvNumFormatType::DATE) )
+ {
+ eEDF = pFormatter->GetEvalDateFormat();
+ switch ( eEDF )
+ {
+ case NF_EVALDATEFORMAT_INTL :
+ case NF_EVALDATEFORMAT_FORMAT :
+ nFormatOrder = 1; // only one loop
+ break;
+ default:
+ nFormatOrder = 2;
+ if ( nMatchedAllStrings )
+ {
+ eEDF = NF_EVALDATEFORMAT_FORMAT_INTL;
+ // we have a complete match, use it
+ }
+ }
+ }
+ else
+ {
+ eEDF = NF_EVALDATEFORMAT_INTL;
+ nFormatOrder = 1;
+ }
+ bool res = true;
+
+ const LocaleDataWrapper* pLoc = pFormatter->GetLocaleData();
+ CalendarWrapper* pCal = pFormatter->GetCalendar();
+ for ( int nTryOrder = 1; nTryOrder <= nFormatOrder; nTryOrder++ )
+ {
+ pCal->setGregorianDateTime( Date( Date::SYSTEM ) ); // today
+ OUString aOrgCalendar; // empty => not changed yet
+ DateOrder DateFmt;
+ bool bFormatTurn;
+ switch ( eEDF )
+ {
+ case NF_EVALDATEFORMAT_INTL :
+ bFormatTurn = false;
+ DateFmt = GetDateOrder();
+ break;
+ case NF_EVALDATEFORMAT_FORMAT :
+ bFormatTurn = true;
+ DateFmt = mpFormat->GetDateOrder();
+ break;
+ case NF_EVALDATEFORMAT_INTL_FORMAT :
+ if ( nTryOrder == 1 )
+ {
+ bFormatTurn = false;
+ DateFmt = GetDateOrder();
+ }
+ else
+ {
+ bFormatTurn = true;
+ DateFmt = mpFormat->GetDateOrder();
+ }
+ break;
+ case NF_EVALDATEFORMAT_FORMAT_INTL :
+ if ( nTryOrder == 2 )
+ {
+ bFormatTurn = false;
+ DateFmt = GetDateOrder();
+ }
+ else
+ {
+ bFormatTurn = true;
+ // Even if the format pattern is to be preferred, the input may
+ // have matched a pattern of the current locale, which then
+ // again is to be preferred. Both date orders can be different
+ // so we need to obtain the actual match. For example ISO
+ // YYYY-MM-DD format vs locale's DD.MM.YY input.
+ // If no pattern was matched, obtain from format.
+ // Note that patterns may have been constructed from the
+ // format's locale and prepended to the current locale's
+ // patterns, it doesn't necessarily mean a current locale's
+ // pattern was matched, but may if the format's locale's
+ // patterns didn't match, which were tried first.
+ DateFmt = GetDateOrder(true);
+ }
+ break;
+ default:
+ SAL_WARN( "svl.numbers", "ImpSvNumberInputScan::GetDateRef: unknown NfEvalDateFormat" );
+ DateFmt = DateOrder::YMD;
+ bFormatTurn = false;
+ }
+ if ( bFormatTurn )
+ {
+/* TODO:
+We are currently not able to fully support a switch to another calendar during
+input for the following reasons:
+1. We do have a problem if both (locale's default and format's) calendars
+ define the same YMD order and use the same date separator, there is no way
+ to distinguish between them if the input results in valid calendar input for
+ both calendars. How to solve? Would NfEvalDateFormat be sufficient? Should
+ it always be set to NF_EVALDATEFORMAT_FORMAT_INTL and thus the format's
+ calendar be preferred? This could be confusing if a Calc cell was formatted
+ different to the locale's default and has no content yet, then the user has
+ no clue about the format or calendar being set.
+2. In Calc cell edit mode a date is always displayed and edited using the
+ default edit format of the default calendar (normally being Gregorian). If
+ input was ambiguous due to issue #1 we'd need a mechanism to tell that a
+ date was edited and not newly entered. Not feasible. Otherwise we'd need a
+ mechanism to use a specific edit format with a specific calendar according
+ to the format set.
+3. For some calendars like Japanese Gengou we'd need era input, which isn't
+ implemented at all. Though this is a rare and special case, forcing a
+ calendar dependent edit format as suggested in item #2 might require era
+ input, if it shouldn't result in a fallback to Gregorian calendar.
+4. Last and least: the GetMonth() method currently only matches month names of
+ the default calendar. Alternating month names of the actual format's
+ calendar would have to be implemented. No problem.
+
+*/
+#ifdef THE_FUTURE
+ if ( mpFormat->IsOtherCalendar( nStringScanNumFor ) )
+ {
+ mpFormat->SwitchToOtherCalendar( aOrgCalendar, fOrgDateTime );
+ }
+ else
+ {
+ mpFormat->SwitchToSpecifiedCalendar( aOrgCalendar, fOrgDateTime,
+ nStringScanNumFor );
+ }
+#endif
+ }
+
+ res = true;
+ nCounter = 0;
+ // For incomplete dates, always assume first day of month if not specified.
+ pCal->setValue( CalendarFieldIndex::DAY_OF_MONTH, 1 );
+
+ switch (nNumericsCnt) // count of numbers in string
+ {
+ case 0: // none
+ if (nMonthPos) // only month (Jan)
+ {
+ pCal->setValue( CalendarFieldIndex::MONTH, std::abs(nMonth)-1 );
+ }
+ else
+ {
+ res = false;
+ }
+ break;
+
+ case 1: // only one number
+ nCounter = 1;
+ switch (nMonthPos) // where is the month
+ {
+ case 0: // not found
+ {
+ // If input matched a date pattern, use the pattern
+ // to determine if it is a day, month or year. The
+ // pattern should have only one single value then,
+ // 'D-', 'M-' or 'Y-'. If input did not match a
+ // pattern assume the usual day of current month.
+ sal_uInt32 nDateOrder = (bFormatTurn ?
+ mpFormat->GetExactDateOrder() :
+ GetDatePatternOrder());
+ switch (nDateOrder)
+ {
+ case 'Y':
+ pCal->setValue( CalendarFieldIndex::YEAR, ImplGetYear(0) );
+ break;
+ case 'M':
+ pCal->setValue( CalendarFieldIndex::MONTH, ImplGetMonth(0) );
+ break;
+ case 'D':
+ default:
+ pCal->setValue( CalendarFieldIndex::DAY_OF_MONTH, ImplGetDay(0) );
+ break;
+ }
+ break;
+ }
+ case 1: // month at the beginning (Jan 01)
+ pCal->setValue( CalendarFieldIndex::MONTH, std::abs(nMonth)-1 );
+ switch (DateFmt)
+ {
+ case DateOrder::MDY:
+ case DateOrder::YMD:
+ {
+ sal_uInt16 nDay = ImplGetDay(0);
+ sal_uInt16 nYear = ImplGetYear(0);
+ if (nDay == 0 || nDay > 32)
+ {
+ pCal->setValue( CalendarFieldIndex::YEAR, nYear);
+ }
+ else
+ {
+ pCal->setValue( CalendarFieldIndex::DAY_OF_MONTH, ImplGetDay(0) );
+ }
+ break;
+ }
+ case DateOrder::DMY:
+ pCal->setValue( CalendarFieldIndex::YEAR, ImplGetYear(0) );
+ break;
+ default:
+ res = false;
+ break;
+ }
+ break;
+ case 3: // month at the end (10 Jan)
+ pCal->setValue( CalendarFieldIndex::MONTH, std::abs(nMonth)-1 );
+ switch (DateFmt)
+ {
+ case DateOrder::DMY:
+ pCal->setValue( CalendarFieldIndex::DAY_OF_MONTH, ImplGetDay(0) );
+ break;
+ case DateOrder::YMD:
+ pCal->setValue( CalendarFieldIndex::YEAR, ImplGetYear(0) );
+ break;
+ default:
+ res = false;
+ break;
+ }
+ break;
+ default:
+ res = false;
+ break;
+ } // switch (nMonthPos)
+ break;
+
+ case 2: // 2 numbers
+ nCounter = 2;
+ switch (nMonthPos) // where is the month
+ {
+ case 0: // not found
+ {
+ sal_uInt32 nExactDateOrder = (bFormatTurn ?
+ mpFormat->GetExactDateOrder() :
+ GetDatePatternOrder());
+ bool bIsExact = (0xff < nExactDateOrder && nExactDateOrder <= 0xffff);
+ if (!bIsExact && bFormatTurn && IsAcceptedDatePattern( nNums[0]))
+ {
+ // If input does not match format but pattern, use pattern
+ // instead, even if eEDF==NF_EVALDATEFORMAT_FORMAT_INTL.
+ // For example, format has "Y-M-D" and pattern is "D.M.",
+ // input with 2 numbers can't match format and 31.12. would
+ // lead to 1931-12-01 (fdo#54344)
+ nExactDateOrder = GetDatePatternOrder();
+ bIsExact = (0xff < nExactDateOrder && nExactDateOrder <= 0xffff);
+ }
+ bool bHadExact;
+ if (bIsExact)
+ {
+ // formatted as date and exactly 2 parts
+ bHadExact = true;
+ switch ( (nExactDateOrder >> 8) & 0xff )
+ {
+ case 'Y':
+ pCal->setValue( CalendarFieldIndex::YEAR, ImplGetYear(0) );
+ break;
+ case 'M':
+ pCal->setValue( CalendarFieldIndex::MONTH, ImplGetMonth(0) );
+ break;
+ case 'D':
+ pCal->setValue( CalendarFieldIndex::DAY_OF_MONTH, ImplGetDay(0) );
+ break;
+ default:
+ bHadExact = false;
+ }
+ switch ( nExactDateOrder & 0xff )
+ {
+ case 'Y':
+ pCal->setValue( CalendarFieldIndex::YEAR, ImplGetYear(1) );
+ break;
+ case 'M':
+ pCal->setValue( CalendarFieldIndex::MONTH, ImplGetMonth(1) );
+ break;
+ case 'D':
+ pCal->setValue( CalendarFieldIndex::DAY_OF_MONTH, ImplGetDay(1) );
+ break;
+ default:
+ bHadExact = false;
+ }
+ SAL_WARN_IF( !bHadExact, "svl.numbers", "ImpSvNumberInputScan::GetDateRef: error in exact date order");
+ }
+ else
+ {
+ bHadExact = false;
+ }
+ // If input matched against a date acceptance pattern
+ // do not attempt to mess around with guessing the
+ // order, either it matches or it doesn't.
+ if ((bFormatTurn || !bIsExact) && (!bHadExact || !pCal->isValid()))
+ {
+ if ( !bHadExact && nExactDateOrder )
+ {
+ pCal->setGregorianDateTime( Date( Date::SYSTEM ) ); // reset today
+ }
+ switch (DateFmt)
+ {
+ case DateOrder::MDY:
+ // M D
+ pCal->setValue( CalendarFieldIndex::DAY_OF_MONTH, ImplGetDay(1) );
+ pCal->setValue( CalendarFieldIndex::MONTH, ImplGetMonth(0) );
+ if ( !pCal->isValid() ) // 2nd try
+ { // M Y
+ pCal->setValue( CalendarFieldIndex::DAY_OF_MONTH, 1 );
+ pCal->setValue( CalendarFieldIndex::MONTH, ImplGetMonth(0) );
+ pCal->setValue( CalendarFieldIndex::YEAR, ImplGetYear(1) );
+ }
+ break;
+ case DateOrder::DMY:
+ // D M
+ pCal->setValue( CalendarFieldIndex::DAY_OF_MONTH, ImplGetDay(0) );
+ pCal->setValue( CalendarFieldIndex::MONTH, ImplGetMonth(1) );
+ if ( !pCal->isValid() ) // 2nd try
+ { // M Y
+ pCal->setValue( CalendarFieldIndex::DAY_OF_MONTH, 1 );
+ pCal->setValue( CalendarFieldIndex::MONTH, ImplGetMonth(0) );
+ pCal->setValue( CalendarFieldIndex::YEAR, ImplGetYear(1) );
+ }
+ break;
+ case DateOrder::YMD:
+ // M D
+ pCal->setValue( CalendarFieldIndex::DAY_OF_MONTH, ImplGetDay(1) );
+ pCal->setValue( CalendarFieldIndex::MONTH, ImplGetMonth(0) );
+ if ( !pCal->isValid() ) // 2nd try
+ { // Y M
+ pCal->setValue( CalendarFieldIndex::DAY_OF_MONTH, 1 );
+ pCal->setValue( CalendarFieldIndex::MONTH, ImplGetMonth(1) );
+ pCal->setValue( CalendarFieldIndex::YEAR, ImplGetYear(0) );
+ }
+ break;
+ default:
+ res = false;
+ break;
+ }
+ }
+ }
+ break;
+ case 1: // month at the beginning (Jan 01 01)
+ {
+ // The input is valid as MDY in almost any
+ // constellation, there is no date order (M)YD except if
+ // set in a format applied.
+ pCal->setValue( CalendarFieldIndex::MONTH, std::abs(nMonth)-1 );
+ sal_uInt32 nExactDateOrder = (bFormatTurn ? mpFormat->GetExactDateOrder() : 0);
+ if ((((nExactDateOrder >> 8) & 0xff) == 'Y') && ((nExactDateOrder & 0xff) == 'D'))
+ {
+ pCal->setValue( CalendarFieldIndex::DAY_OF_MONTH, ImplGetDay(1) );
+ pCal->setValue( CalendarFieldIndex::YEAR, ImplGetYear(0) );
+ }
+ else
+ {
+ pCal->setValue( CalendarFieldIndex::DAY_OF_MONTH, ImplGetDay(0) );
+ pCal->setValue( CalendarFieldIndex::YEAR, ImplGetYear(1) );
+ }
+ break;
+ }
+ case 2: // month in the middle (10 Jan 94)
+ {
+ pCal->setValue( CalendarFieldIndex::MONTH, std::abs(nMonth)-1 );
+ const LongDateOrder eLDO = GetMiddleMonthLongDateOrder( bFormatTurn, pLoc, DateFmt);
+ switch (eLDO)
+ {
+ case LongDateOrder::DMY:
+ pCal->setValue( CalendarFieldIndex::DAY_OF_MONTH, ImplGetDay(0) );
+ pCal->setValue( CalendarFieldIndex::YEAR, ImplGetYear(1) );
+ break;
+ case LongDateOrder::YMD:
+ pCal->setValue( CalendarFieldIndex::DAY_OF_MONTH, ImplGetDay(1) );
+ pCal->setValue( CalendarFieldIndex::YEAR, ImplGetYear(0) );
+ break;
+ default:
+ res = false;
+ break;
+ }
+ break;
+ }
+ case 3: // month at the end (94 10 Jan)
+ if (pLoc->getLongDateOrder() != LongDateOrder::YDM)
+ res = false;
+ else
+ {
+ pCal->setValue( CalendarFieldIndex::DAY_OF_MONTH, ImplGetDay(1) );
+ pCal->setValue( CalendarFieldIndex::MONTH, std::abs(nMonth)-1 );
+ pCal->setValue( CalendarFieldIndex::YEAR, ImplGetYear(0) );
+ }
+ break;
+ default:
+ res = false;
+ break;
+ } // switch (nMonthPos)
+ break;
+
+ default: // more than two numbers (31.12.94 8:23) (31.12. 8:23)
+ switch (nMonthPos) // where is the month
+ {
+ case 0: // not found
+ {
+ nCounter = 3;
+ if ( nTimePos > 1 )
+ { // find first time number index (should only be 3 or 2 anyway)
+ for ( sal_uInt16 j = 0; j < nNumericsCnt; j++ )
+ {
+ if ( nNums[j] == nTimePos - 2 )
+ {
+ nCounter = j;
+ break; // for
+ }
+ }
+ }
+ // ISO 8601 yyyy-mm-dd forced recognition
+ DateOrder eDF = (CanForceToIso8601( DateFmt) ? DateOrder::YMD : DateFmt);
+ switch (eDF)
+ {
+ case DateOrder::MDY:
+ pCal->setValue( CalendarFieldIndex::DAY_OF_MONTH, ImplGetDay(1) );
+ pCal->setValue( CalendarFieldIndex::MONTH, ImplGetMonth(0) );
+ if ( nCounter > 2 )
+ pCal->setValue( CalendarFieldIndex::YEAR, ImplGetYear(2) );
+ break;
+ case DateOrder::DMY:
+ pCal->setValue( CalendarFieldIndex::DAY_OF_MONTH, ImplGetDay(0) );
+ pCal->setValue( CalendarFieldIndex::MONTH, ImplGetMonth(1) );
+ if ( nCounter > 2 )
+ pCal->setValue( CalendarFieldIndex::YEAR, ImplGetYear(2) );
+ break;
+ case DateOrder::YMD:
+ if ( nCounter > 2 )
+ pCal->setValue( CalendarFieldIndex::DAY_OF_MONTH, ImplGetDay(2) );
+ pCal->setValue( CalendarFieldIndex::MONTH, ImplGetMonth(1) );
+ pCal->setValue( CalendarFieldIndex::YEAR, ImplGetYear(0) );
+ break;
+ default:
+ res = false;
+ break;
+ }
+ break;
+ }
+ case 1: // month at the beginning (Jan 01 01 8:23)
+ {
+ nCounter = 2;
+ // The input is valid as MDY in almost any
+ // constellation, there is no date order (M)YD except if
+ // set in a format applied.
+ pCal->setValue( CalendarFieldIndex::MONTH, std::abs(nMonth)-1 );
+ sal_uInt32 nExactDateOrder = (bFormatTurn ? mpFormat->GetExactDateOrder() : 0);
+ if ((((nExactDateOrder >> 8) & 0xff) == 'Y') && ((nExactDateOrder & 0xff) == 'D'))
+ {
+ pCal->setValue( CalendarFieldIndex::DAY_OF_MONTH, ImplGetDay(1) );
+ pCal->setValue( CalendarFieldIndex::YEAR, ImplGetYear(0) );
+ }
+ else
+ {
+ pCal->setValue( CalendarFieldIndex::DAY_OF_MONTH, ImplGetDay(0) );
+ pCal->setValue( CalendarFieldIndex::YEAR, ImplGetYear(1) );
+ }
+ break;
+ }
+ case 2: // month in the middle (10 Jan 94 8:23)
+ {
+ nCounter = 2;
+ pCal->setValue( CalendarFieldIndex::MONTH, std::abs(nMonth)-1 );
+ const LongDateOrder eLDO = GetMiddleMonthLongDateOrder( bFormatTurn, pLoc, DateFmt);
+ switch (eLDO)
+ {
+ case LongDateOrder::DMY:
+ pCal->setValue( CalendarFieldIndex::DAY_OF_MONTH, ImplGetDay(0) );
+ pCal->setValue( CalendarFieldIndex::YEAR, ImplGetYear(1) );
+ break;
+ case LongDateOrder::YMD:
+ pCal->setValue( CalendarFieldIndex::DAY_OF_MONTH, ImplGetDay(1) );
+ pCal->setValue( CalendarFieldIndex::YEAR, ImplGetYear(0) );
+ break;
+ default:
+ res = false;
+ break;
+ }
+ break;
+ }
+ case 3: // month at the end (94 10 Jan 8:23)
+ nCounter = 2;
+ if (pLoc->getLongDateOrder() != LongDateOrder::YDM)
+ res = false;
+ else
+ {
+ pCal->setValue( CalendarFieldIndex::DAY_OF_MONTH, ImplGetDay(1) );
+ pCal->setValue( CalendarFieldIndex::MONTH, std::abs(nMonth)-1 );
+ pCal->setValue( CalendarFieldIndex::YEAR, ImplGetYear(0) );
+ }
+ break;
+ default:
+ nCounter = 2;
+ res = false;
+ break;
+ } // switch (nMonthPos)
+ break;
+ } // switch (nNumericsCnt)
+
+ if (mbEraCE != kDefaultEra)
+ pCal->setValue( CalendarFieldIndex::ERA, mbEraCE ? 1 : 0);
+
+ if ( res && pCal->isValid() )
+ {
+ double fDiff = DateTime::Sub( DateTime(*moNullDate), pCal->getEpochStart());
+ fDays = ::rtl::math::approxFloor( pCal->getLocalDateTime() );
+ fDays -= fDiff;
+ nTryOrder = nFormatOrder; // break for
+ }
+ else
+ {
+ res = false;
+ }
+ if ( aOrgCalendar.getLength() )
+ {
+ pCal->loadCalendar( aOrgCalendar, pLoc->getLanguageTag().getLocale() ); // restore calendar
+ }
+#if NF_TEST_CALENDAR
+ {
+ using namespace ::com::sun::star;
+ struct entry { const char* lan; const char* cou; const char* cal; };
+ const entry cals[] = {
+ { "en", "US", "gregorian" },
+ { "ar", "TN", "hijri" },
+ { "he", "IL", "jewish" },
+ { "ja", "JP", "gengou" },
+ { "ko", "KR", "hanja_yoil" },
+ { "th", "TH", "buddhist" },
+ { "zh", "TW", "ROC" },
+ {0,0,0}
+ };
+ lang::Locale aLocale;
+ bool bValid;
+ sal_Int16 nDay, nMyMonth, nYear, nHour, nMinute, nSecond;
+ sal_Int16 nDaySet, nMonthSet, nYearSet, nHourSet, nMinuteSet, nSecondSet;
+ sal_Int16 nZO, nDST1, nDST2, nDST, nZOmillis, nDST1millis, nDST2millis, nDSTmillis;
+ sal_Int32 nZoneInMillis, nDST1InMillis, nDST2InMillis;
+ uno::Reference< uno::XComponentContext > xContext =
+ ::comphelper::getProcessComponentContext();
+ uno::Reference< i18n::XCalendar4 > xCal = i18n::LocaleCalendar2::create(xContext);
+ for ( const entry* p = cals; p->lan; ++p )
+ {
+ aLocale.Language = OUString::createFromAscii( p->lan );
+ aLocale.Country = OUString::createFromAscii( p->cou );
+ xCal->loadCalendar( OUString::createFromAscii( p->cal ),
+ aLocale );
+ double nDateTime = 0.0; // 1-Jan-1970 00:00:00
+ nZO = xCal->getValue( i18n::CalendarFieldIndex::ZONE_OFFSET );
+ nZOmillis = xCal->getValue( i18n::CalendarFieldIndex::ZONE_OFFSET_SECOND_MILLIS );
+ nZoneInMillis = static_cast<sal_Int32>(nZO) * 60000 +
+ (nZO < 0 ? -1 : 1) * static_cast<sal_uInt16>(nZOmillis);
+ nDST1 = xCal->getValue( i18n::CalendarFieldIndex::DST_OFFSET );
+ nDST1millis = xCal->getValue( i18n::CalendarFieldIndex::DST_OFFSET_SECOND_MILLIS );
+ nDST1InMillis = static_cast<sal_Int32>(nDST1) * 60000 +
+ (nDST1 < 0 ? -1 : 1) * static_cast<sal_uInt16>(nDST1millis);
+ nDateTime -= (double)(nZoneInMillis + nDST1InMillis) / 1000.0 / 60.0 / 60.0 / 24.0;
+ xCal->setDateTime( nDateTime );
+ nDST2 = xCal->getValue( i18n::CalendarFieldIndex::DST_OFFSET );
+ nDST2millis = xCal->getValue( i18n::CalendarFieldIndex::DST_OFFSET_SECOND_MILLIS );
+ nDST2InMillis = static_cast<sal_Int32>(nDST2) * 60000 +
+ (nDST2 < 0 ? -1 : 1) * static_cast<sal_uInt16>(nDST2millis);
+ if ( nDST1InMillis != nDST2InMillis )
+ {
+ nDateTime = 0.0 - (double)(nZoneInMillis + nDST2InMillis) / 1000.0 / 60.0 / 60.0 / 24.0;
+ xCal->setDateTime( nDateTime );
+ }
+ nDaySet = xCal->getValue( i18n::CalendarFieldIndex::DAY_OF_MONTH );
+ nMonthSet = xCal->getValue( i18n::CalendarFieldIndex::MONTH );
+ nYearSet = xCal->getValue( i18n::CalendarFieldIndex::YEAR );
+ nHourSet = xCal->getValue( i18n::CalendarFieldIndex::HOUR );
+ nMinuteSet = xCal->getValue( i18n::CalendarFieldIndex::MINUTE );
+ nSecondSet = xCal->getValue( i18n::CalendarFieldIndex::SECOND );
+ nZO = xCal->getValue( i18n::CalendarFieldIndex::ZONE_OFFSET );
+ nZOmillis = xCal->getValue( i18n::CalendarFieldIndex::ZONE_OFFSET_SECOND_MILLIS );
+ nDST = xCal->getValue( i18n::CalendarFieldIndex::DST_OFFSET );
+ nDSTmillis = xCal->getValue( i18n::CalendarFieldIndex::DST_OFFSET_SECOND_MILLIS );
+ xCal->setValue( i18n::CalendarFieldIndex::DAY_OF_MONTH, nDaySet );
+ xCal->setValue( i18n::CalendarFieldIndex::MONTH, nMonthSet );
+ xCal->setValue( i18n::CalendarFieldIndex::YEAR, nYearSet );
+ xCal->setValue( i18n::CalendarFieldIndex::HOUR, nHourSet );
+ xCal->setValue( i18n::CalendarFieldIndex::MINUTE, nMinuteSet );
+ xCal->setValue( i18n::CalendarFieldIndex::SECOND, nSecondSet );
+ bValid = xCal->isValid();
+ nDay = xCal->getValue( i18n::CalendarFieldIndex::DAY_OF_MONTH );
+ nMyMonth= xCal->getValue( i18n::CalendarFieldIndex::MONTH );
+ nYear = xCal->getValue( i18n::CalendarFieldIndex::YEAR );
+ nHour = xCal->getValue( i18n::CalendarFieldIndex::HOUR );
+ nMinute = xCal->getValue( i18n::CalendarFieldIndex::MINUTE );
+ nSecond = xCal->getValue( i18n::CalendarFieldIndex::SECOND );
+ bValid = bValid && nDay == nDaySet && nMyMonth == nMonthSet && nYear ==
+ nYearSet && nHour == nHourSet && nMinute == nMinuteSet && nSecond
+ == nSecondSet;
+ }
+ }
+#endif // NF_TEST_CALENDAR
+
+ }
+
+ return res;
+}
+
+
+/**
+ * Analyze first string
+ * All gone => true
+ * else => false
+ */
+bool ImpSvNumberInputScan::ScanStartString( const OUString& rString )
+{
+ sal_Int32 nPos = 0;
+
+ // First of all, eat leading blanks
+ SkipBlanks(rString, nPos);
+
+ // Yes, nMatchedAllStrings should know about the sign position
+ nSign = GetSign(rString, nPos);
+ if ( nSign ) // sign?
+ {
+ SkipBlanks(rString, nPos);
+ }
+ // #102371# match against format string only if start string is not a sign character
+ if ( nMatchedAllStrings && !(nSign && rString.getLength() == 1) )
+ {
+ // Match against format in any case, so later on for a "x1-2-3" input
+ // we may distinguish between a xy-m-d (or similar) date and a x0-0-0
+ // format. No sign detection here!
+ if ( ScanStringNumFor( rString, nPos, 0, true ) )
+ {
+ nMatchedAllStrings |= nMatchedStartString;
+ }
+ else
+ {
+ nMatchedAllStrings = 0;
+ }
+ }
+
+ // Bail out early for just a sign.
+ if (nSign && nPos == rString.getLength())
+ return true;
+
+ const sal_Int32 nStartBlanks = nPos;
+ if ( GetDecSep(rString, nPos) ) // decimal separator in start string
+ {
+ if (SkipBlanks(rString, nPos))
+ nPos = nStartBlanks; // `. 2` not a decimal separator
+ else
+ nDecPos = 1; // leading decimal separator
+ }
+ else if ( GetCurrency(rString, nPos) ) // currency (DM 1)?
+ {
+ eScannedType = SvNumFormatType::CURRENCY; // !!! it IS currency !!!
+ SkipBlanks(rString, nPos);
+ if (nSign == 0) // no sign yet
+ {
+ nSign = GetSign(rString, nPos);
+ if ( nSign ) // DM -1
+ {
+ SkipBlanks(rString, nPos);
+ }
+ }
+ if ( GetDecSep(rString, nPos) ) // decimal separator follows currency
+ {
+ if (SkipBlanks(rString, nPos))
+ {
+ nPos = nStartBlanks; // `DM . 2` not a decimal separator
+ eScannedType = SvNumFormatType::UNDEFINED; // !!! it is NOT currency !!!
+ }
+ else
+ nDecPos = 1; // leading decimal separator
+ }
+ }
+ else
+ {
+ const sal_Int32 nMonthStart = nPos;
+ short nTempMonth = GetMonth(rString, nPos);
+ if (nTempMonth < 0)
+ {
+ // Short month and day names may be identical in some locales, e.g.
+ // "mar" for "martes" or "marzo" in Spanish.
+ // Do not let a month name immediately take precedence if a day
+ // name was meant instead. Assume that both could be valid, until
+ // encountered differently or the final evaluation in
+ // IsNumberFormat() checks, but continue with weighing the month
+ // name higher unless we have both day of week and month name here.
+ sal_Int32 nTempPos = nMonthStart;
+ nDayOfWeek = GetDayOfWeek( rString, nTempPos);
+ if (nDayOfWeek < 0)
+ {
+ SkipChar( '.', rString, nTempPos ); // abbreviated
+ SkipString( pFormatter->GetLocaleData()->getLongDateDayOfWeekSep(), rString, nTempPos );
+ SkipBlanks( rString, nTempPos);
+ short nTempTempMonth = GetMonth( rString, nTempPos);
+ if (nTempTempMonth)
+ {
+ // Fall into the else branch below that handles both.
+ nTempMonth = 0;
+ nPos = nMonthStart;
+ nDayOfWeek = 0;
+ // Do not set nDayOfWeek hereafter, anywhere.
+ }
+ }
+ }
+ if ( nTempMonth ) // month (Jan 1)?
+ {
+ // Jan1 without separator is not a date, unless it is followed by a
+ // separator and a (year) number.
+ if (nPos < rString.getLength() || (nStringsCnt >= 4 && nNumericsCnt >= 2))
+ {
+ eScannedType = SvNumFormatType::DATE; // !!! it IS a date !!!
+ nMonth = nTempMonth;
+ nMonthPos = 1; // month at the beginning
+ if ( nMonth < 0 )
+ {
+ SkipChar( '.', rString, nPos ); // abbreviated
+ }
+ SkipBlanks(rString, nPos);
+ }
+ else
+ {
+ nPos = nMonthStart; // rewind month
+ }
+ }
+ else
+ {
+ int nTempDayOfWeek = GetDayOfWeek( rString, nPos );
+ if ( nTempDayOfWeek )
+ {
+ // day of week is just parsed away
+ eScannedType = SvNumFormatType::DATE; // !!! it IS a date !!!
+ if ( nPos < rString.getLength() )
+ {
+ if ( nTempDayOfWeek < 0 )
+ {
+ // abbreviated
+ if ( rString[ nPos ] == '.' )
+ {
+ ++nPos;
+ }
+ }
+ else
+ {
+ // full long name
+ SkipBlanks(rString, nPos);
+ SkipString( pFormatter->GetLocaleData()->getLongDateDayOfWeekSep(), rString, nPos );
+ }
+ SkipBlanks(rString, nPos);
+ nTempMonth = GetMonth(rString, nPos);
+ if ( nTempMonth ) // month (Jan 1)?
+ {
+ // Jan1 without separator is not a date, unless it is followed by a
+ // separator and a (year) number.
+ if (nPos < rString.getLength() || (nStringsCnt >= 4 && nNumericsCnt >= 2))
+ {
+ nMonth = nTempMonth;
+ nMonthPos = 1; // month at the beginning
+ if ( nMonth < 0 )
+ {
+ SkipChar( '.', rString, nPos ); // abbreviated
+ }
+ SkipBlanks(rString, nPos);
+ }
+ else
+ {
+ nPos = nMonthStart; // rewind month
+ }
+ }
+ }
+ if (!nMonth)
+ {
+ // Determine and remember following date pattern, if any.
+ IsAcceptedDatePattern( 1);
+ }
+ }
+ }
+ // Skip one trailing '-' or '/' character to recognize June-2007
+ if (nMonth && nPos + 1 == rString.getLength())
+ {
+ SkipChar('-', rString, nPos) || SkipChar('/', rString, nPos);
+ }
+ }
+
+ if (nPos < rString.getLength()) // not everything consumed
+ {
+ // Does input StartString equal StartString of format?
+ // This time with sign detection!
+ if ( !ScanStringNumFor( rString, nPos, 0 ) )
+ {
+ return MatchedReturn();
+ }
+ }
+
+ return true;
+}
+
+
+/**
+ * Analyze string in the middle
+ * All gone => true
+ * else => false
+ */
+bool ImpSvNumberInputScan::ScanMidString( const OUString& rString, sal_uInt16 nStringPos, sal_uInt16 nCurNumCount )
+{
+ sal_Int32 nPos = 0;
+ SvNumFormatType eOldScannedType = eScannedType;
+
+ if ( nMatchedAllStrings )
+ { // Match against format in any case, so later on for a "1-2-3-4" input
+ // we may distinguish between a y-m-d (or similar) date and a 0-0-0-0
+ // format.
+ if ( ScanStringNumFor( rString, 0, nStringPos ) )
+ {
+ nMatchedAllStrings |= nMatchedMidString;
+ }
+ else
+ {
+ nMatchedAllStrings = 0;
+ }
+ }
+
+ const sal_Int32 nStartBlanks = nPos;
+ const bool bBlanks = SkipBlanks(rString, nPos);
+ if (GetDecSep(rString, nPos)) // decimal separator?
+ {
+ if (nDecPos == 1 || nDecPos == 3) // .12.4 or 1.E2.1
+ {
+ return MatchedReturn();
+ }
+ else if (nDecPos == 2) // . dup: 12.4.
+ {
+ bool bSignedYear = false;
+ if (bDecSepInDateSeps || // . also date separator
+ SkipDatePatternSeparator( nStringPos, nPos, bSignedYear))
+ {
+ if ( eScannedType != SvNumFormatType::UNDEFINED &&
+ eScannedType != SvNumFormatType::DATE &&
+ eScannedType != SvNumFormatType::DATETIME) // already another type
+ {
+ return MatchedReturn();
+ }
+ if (eScannedType == SvNumFormatType::UNDEFINED)
+ {
+ eScannedType = SvNumFormatType::DATE; // !!! it IS a date
+ }
+ SkipBlanks(rString, nPos);
+ }
+ else
+ {
+ return MatchedReturn();
+ }
+ }
+ else if (bBlanks)
+ {
+ // `1 .2` or `1 . 2` not a decimal separator, reset
+ nPos = nStartBlanks;
+ }
+ else if (SkipBlanks(rString, nPos))
+ {
+ // `1. 2` not a decimal separator, reset
+ nPos = nStartBlanks;
+ }
+ else
+ {
+ nDecPos = 2; // . in mid string
+ }
+ }
+ else if ( (eScannedType & SvNumFormatType::TIME) &&
+ GetTime100SecSep( rString, nPos ) )
+ { // hundredth seconds separator
+ if ( nDecPos )
+ {
+ return MatchedReturn();
+ }
+ nDecPos = 2; // . in mid string
+
+ // If this is exactly an ISO 8601 fractional seconds separator, bail
+ // out early to not get confused by later checks for group separator or
+ // other.
+ if (bIso8601Tsep && nPos == rString.getLength() &&
+ eScannedType == SvNumFormatType::DATETIME && (rString == "." || rString == ","))
+ return true;
+
+ SkipBlanks(rString, nPos);
+ }
+
+ if (SkipChar('/', rString, nPos)) // fraction?
+ {
+ if ( eScannedType != SvNumFormatType::UNDEFINED && // already another type
+ eScannedType != SvNumFormatType::DATE) // except date
+ {
+ return MatchedReturn(); // => jan/31/1994
+ }
+ else if (eScannedType != SvNumFormatType::DATE && // analyzed no date until now
+ (eSetType == SvNumFormatType::FRACTION || // and preset was fraction
+ (nNumericsCnt == 3 && // or 3 numbers
+ (nStringPos == 3 || // and 4th string particle
+ (nStringPos == 4 && nSign)) && // or 5th if signed
+ sStrArray[nStringPos-2].indexOf('/') == -1))) // and not 23/11/1999
+ // that was not accepted as date yet
+ {
+ SkipBlanks(rString, nPos);
+ if (nPos == rString.getLength())
+ {
+ eScannedType = SvNumFormatType::FRACTION; // !!! it IS a fraction (so far)
+ if (eSetType == SvNumFormatType::FRACTION &&
+ nNumericsCnt == 2 &&
+ (nStringPos == 1 || // for 4/5
+ (nStringPos == 2 && nSign))) // or signed -4/5
+ {
+ return true; // don't fall into date trap
+ }
+ }
+ }
+ else
+ {
+ nPos--; // put '/' back
+ }
+ }
+
+ if (GetThousandSep(rString, nPos, nStringPos)) // 1,000
+ {
+ if ( eScannedType != SvNumFormatType::UNDEFINED && // already another type
+ eScannedType != SvNumFormatType::CURRENCY) // except currency
+ {
+ return MatchedReturn();
+ }
+ nThousand++;
+ }
+
+ const LocaleDataWrapper* pLoc = pFormatter->GetLocaleData();
+ bool bSignedYear = false;
+ bool bDate = SkipDatePatternSeparator( nStringPos, nPos, bSignedYear); // 12/31 31.12. 12/31/1999 31.12.1999
+ if (!bDate)
+ {
+ const OUString& rDate = pFormatter->GetDateSep();
+ SkipBlanks(rString, nPos);
+ bDate = SkipString( rDate, rString, nPos); // 10. 10- 10/
+ }
+ if (!bDate && nStringPos == 1 && mpFormat && (mpFormat->GetType() & SvNumFormatType::DATE))
+ {
+ // If a DMY format was given and a mid string starts with a literal
+ // ". " dot+space and could contain a following month name and ends
+ // with a space or LongDateMonthSeparator, like it's scanned in
+ // `14". AUG "18`, then it may be a date as well. Regardless whether
+ // defined such by the locale or not.
+ // This *could* check for presence of ". "MMM or ". "MMMM in the actual
+ // format code for further restriction to match only if present, but..
+
+ const sal_uInt32 nExactDateOrder = mpFormat->GetExactDateOrder();
+ // Exactly DMY.
+ if (((nExactDateOrder & 0xff) == 'Y') && (((nExactDateOrder >> 8) & 0xff) == 'M')
+ && (((nExactDateOrder >> 16) & 0xff) == 'D'))
+ {
+ const sal_Int32 nTmpPos = nPos;
+ if (SkipChar('.', rString, nPos) && SkipBlanks(rString, nPos) && nPos + 2 < rString.getLength()
+ && (rString.endsWith(" ") || rString.endsWith( pLoc->getLongDateMonthSep())))
+ bDate = true;
+ else
+ nPos = nTmpPos;
+ }
+ }
+ if (bDate || ((MayBeIso8601() || MayBeMonthDate()) && // 1999-12-31 31-Dec-1999
+ SkipChar( '-', rString, nPos)))
+ {
+ if ( eScannedType != SvNumFormatType::UNDEFINED && // already another type
+ eScannedType != SvNumFormatType::DATE) // except date
+ {
+ return MatchedReturn();
+ }
+ SkipBlanks(rString, nPos);
+ eScannedType = SvNumFormatType::DATE; // !!! it IS a date
+ short nTmpMonth = GetMonth(rString, nPos); // 10. Jan 94
+ if (nMonth && nTmpMonth) // month dup
+ {
+ return MatchedReturn();
+ }
+ if (nTmpMonth)
+ {
+ nMonth = nTmpMonth;
+ nMonthPos = 2; // month in the middle
+ if ( nMonth < 0 && SkipChar( '.', rString, nPos ) )
+ ; // short month may be abbreviated Jan.
+ else if ( SkipChar( '-', rString, nPos ) )
+ ; // #79632# recognize 17-Jan-2001 to be a date
+ // #99065# short and long month name
+ else
+ {
+ SkipString( pLoc->getLongDateMonthSep(), rString, nPos );
+ }
+ SkipBlanks(rString, nPos);
+ }
+ if (bSignedYear)
+ {
+ if (mbEraCE != kDefaultEra) // signed year twice?
+ return MatchedReturn();
+
+ mbEraCE = false; // BCE
+ }
+ }
+
+ const sal_Int32 nMonthStart = nPos;
+ short nTempMonth = GetMonth(rString, nPos); // month in the middle (10 Jan 94) or at the end (94 10 Jan)
+ if (nTempMonth)
+ {
+ if (nMonth != 0) // month dup
+ {
+ return MatchedReturn();
+ }
+ if ( eScannedType != SvNumFormatType::UNDEFINED && // already another type
+ eScannedType != SvNumFormatType::DATE) // except date
+ {
+ return MatchedReturn();
+ }
+ if (nMonthStart > 0 && nPos < rString.getLength()) // 10Jan or Jan94 without separator are not dates
+ {
+ eScannedType = SvNumFormatType::DATE; // !!! it IS a date
+ nMonth = nTempMonth;
+ if (nCurNumCount <= 1)
+ nMonthPos = 2; // month in the middle
+ else
+ nMonthPos = 3; // month at the end
+ if ( nMonth < 0 )
+ {
+ SkipChar( '.', rString, nPos ); // abbreviated
+ }
+ SkipString( pLoc->getLongDateMonthSep(), rString, nPos );
+ SkipBlanks(rString, nPos);
+ }
+ else
+ {
+ nPos = nMonthStart; // rewind month
+ }
+ }
+
+ if ( SkipChar('E', rString, nPos) || // 10E, 10e, 10,Ee
+ SkipChar('e', rString, nPos) )
+ {
+ if (eScannedType != SvNumFormatType::UNDEFINED) // already another type
+ {
+ return MatchedReturn();
+ }
+ else
+ {
+ SkipBlanks(rString, nPos);
+ eScannedType = SvNumFormatType::SCIENTIFIC; // !!! it IS scientific
+ if ( nThousand+2 == nNumericsCnt && nDecPos == 2 ) // special case 1.E2
+ {
+ nDecPos = 3; // 1,100.E2 1,100,100.E3
+ }
+ }
+ nESign = GetESign(rString, nPos); // signed exponent?
+ SkipBlanks(rString, nPos);
+ }
+
+ const OUString& rTime = pLoc->getTimeSep();
+ if ( SkipString(rTime, rString, nPos) ) // time separator?
+ {
+ if (nDecPos) // already . => maybe error
+ {
+ if (bDecSepInDateSeps) // . also date sep
+ {
+ if ( eScannedType != SvNumFormatType::DATE && // already another type than date
+ eScannedType != SvNumFormatType::DATETIME) // or date time
+ {
+ return MatchedReturn();
+ }
+ if (eScannedType == SvNumFormatType::DATE)
+ {
+ nDecPos = 0; // reset for time transition
+ }
+ }
+ else
+ {
+ return MatchedReturn();
+ }
+ }
+ if ((eScannedType == SvNumFormatType::DATE || // already date type
+ eScannedType == SvNumFormatType::DATETIME) && // or date time
+ nNumericsCnt > 3) // and more than 3 numbers? (31.Dez.94 8:23)
+ {
+ SkipBlanks(rString, nPos);
+ eScannedType = SvNumFormatType::DATETIME; // !!! it IS date with time
+ }
+ else if ( eScannedType != SvNumFormatType::UNDEFINED && // already another type
+ eScannedType != SvNumFormatType::TIME) // except time
+ {
+ return MatchedReturn();
+ }
+ else
+ {
+ SkipBlanks(rString, nPos);
+ eScannedType = SvNumFormatType::TIME; // !!! it IS a time
+ }
+ if ( !nTimePos )
+ {
+ nTimePos = nStringPos + 1;
+ }
+ }
+
+ if (nPos < rString.getLength())
+ {
+ switch (eScannedType)
+ {
+ case SvNumFormatType::DATE:
+ if (nMonthPos == 1 && pLoc->getLongDateOrder() == LongDateOrder::MDY)
+ {
+ // #68232# recognize long date separators like ", " in "September 5, 1999"
+ if (SkipString( pLoc->getLongDateDaySep(), rString, nPos ))
+ {
+ SkipBlanks( rString, nPos );
+ }
+ }
+ else if (nPos == 0 && rString.getLength() == 1 && MayBeIso8601())
+ {
+ if ( (nStringPos == 5 && rString[0] == 'T') ||
+ (nStringPos == 6 && rString[0] == 'T' && sStrArray[0] == "-"))
+ {
+ // ISO 8601 combined date and time, yyyy-mm-ddThh:mm or -yyyy-mm-ddThh:mm
+ ++nPos;
+ bIso8601Tsep = true;
+ }
+ else if (nStringPos == 7 && rString[0] == ':')
+ {
+ // ISO 8601 combined date and time, the time part; we reach
+ // here if the locale's separator is not ':' so it couldn't
+ // be detected above in the time block.
+ if (nNumericsCnt >= 5)
+ eScannedType = SvNumFormatType::DATETIME;
+ ++nPos;
+ }
+ }
+ break;
+ case SvNumFormatType::DATETIME:
+ if (nPos == 0 && rString.getLength() == 1 && MayBeIso8601())
+ {
+ if (nStringPos == 9 && rString[0] == ':')
+ {
+ // ISO 8601 combined date and time, the time part continued.
+ ++nPos;
+ }
+ }
+#if NF_RECOGNIZE_ISO8601_TIMEZONES
+ else if (nPos == 0 && rString.getLength() == 1 && nStringPos >= 9 && MayBeIso8601())
+ {
+ // ISO 8601 timezone offset
+ switch (rString[ 0 ])
+ {
+ case '+':
+ case '-':
+ if (nStringPos == nStringsCnt - 2 ||
+ nStringPos == nStringsCnt - 4)
+ {
+ ++nPos; // yyyy-mm-ddThh:mm[:ss]+xx[[:]yy]
+ // nTimezonePos needed for GetTimeRef()
+ if (!nTimezonePos)
+ {
+ nTimezonePos = nStringPos + 1;
+ }
+ }
+ break;
+ case ':':
+ if (nTimezonePos && nStringPos >= 11 &&
+ nStringPos == nStringsCnt - 2)
+ {
+ ++nPos; // yyyy-mm-ddThh:mm[:ss]+xx:yy
+ }
+ break;
+ }
+ }
+#endif
+ break;
+ default: break;
+ }
+ }
+
+ if (nPos < rString.getLength()) // not everything consumed?
+ {
+ if ( nMatchedAllStrings & ~nMatchedVirgin )
+ {
+ eScannedType = eOldScannedType;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+
+/**
+ * Analyze the end
+ * All gone => true
+ * else => false
+ */
+bool ImpSvNumberInputScan::ScanEndString( const OUString& rString )
+{
+ sal_Int32 nPos = 0;
+
+ if ( nMatchedAllStrings )
+ { // Match against format in any case, so later on for a "1-2-3-4" input
+ // we may distinguish between a y-m-d (or similar) date and a 0-0-0-0
+ // format.
+ if ( ScanStringNumFor( rString, 0, 0xFFFF ) )
+ {
+ nMatchedAllStrings |= nMatchedEndString;
+ }
+ else
+ {
+ nMatchedAllStrings = 0;
+ }
+ }
+
+ const sal_Int32 nStartBlanks = nPos;
+ const bool bBlanks = SkipBlanks(rString, nPos);
+ if (GetDecSep(rString, nPos)) // decimal separator?
+ {
+ if (nDecPos == 1 || nDecPos == 3) // .12.4 or 12.E4.
+ {
+ return MatchedReturn();
+ }
+ else if (nDecPos == 2) // . dup: 12.4.
+ {
+ bool bSignedYear = false;
+ if (bDecSepInDateSeps || // . also date separator
+ SkipDatePatternSeparator( nStringsCnt-1, nPos, bSignedYear))
+ {
+ if ( eScannedType != SvNumFormatType::UNDEFINED &&
+ eScannedType != SvNumFormatType::DATE &&
+ eScannedType != SvNumFormatType::DATETIME) // already another type
+ {
+ return MatchedReturn();
+ }
+ if (eScannedType == SvNumFormatType::UNDEFINED)
+ {
+ eScannedType = SvNumFormatType::DATE; // !!! it IS a date
+ }
+ SkipBlanks(rString, nPos);
+ }
+ else
+ {
+ return MatchedReturn();
+ }
+ }
+ else if (bBlanks)
+ {
+ // not a decimal separator, reset
+ nPos = nStartBlanks;
+ }
+ else
+ {
+ nDecPos = 3; // . in end string
+ SkipBlanks(rString, nPos);
+ }
+ }
+
+ bool bSignDetectedHere = false;
+ if ( nSign == 0 && // conflict - not signed
+ eScannedType != SvNumFormatType::DATE) // and not date
+ //!? catch time too?
+ { // not signed yet
+ nSign = GetSign(rString, nPos); // 1- DM
+ if (bNegCheck) // '(' as sign
+ {
+ return MatchedReturn();
+ }
+ if (nSign)
+ {
+ bSignDetectedHere = true;
+ }
+ }
+
+ SkipBlanks(rString, nPos);
+ if (bNegCheck && SkipChar(')', rString, nPos)) // skip ')' if appropriate
+ {
+ bNegCheck = false;
+ SkipBlanks(rString, nPos);
+ }
+
+ if ( GetCurrency(rString, nPos) ) // currency symbol?
+ {
+ if (eScannedType != SvNumFormatType::UNDEFINED) // currency dup
+ {
+ return MatchedReturn();
+ }
+ else
+ {
+ SkipBlanks(rString, nPos);
+ eScannedType = SvNumFormatType::CURRENCY;
+ } // behind currency a '-' is allowed
+ if (nSign == 0) // not signed yet
+ {
+ nSign = GetSign(rString, nPos); // DM -
+ SkipBlanks(rString, nPos);
+ if (bNegCheck) // 3 DM (
+ {
+ return MatchedReturn();
+ }
+ }
+ if ( bNegCheck && eScannedType == SvNumFormatType::CURRENCY &&
+ SkipChar(')', rString, nPos) )
+ {
+ bNegCheck = false; // ')' skipped
+ SkipBlanks(rString, nPos); // only if currency
+ }
+ }
+
+ if ( SkipChar('%', rString, nPos) ) // 1%
+ {
+ if (eScannedType != SvNumFormatType::UNDEFINED) // already another type
+ {
+ return MatchedReturn();
+ }
+ SkipBlanks(rString, nPos);
+ eScannedType = SvNumFormatType::PERCENT;
+ }
+
+ const LocaleDataWrapper* pLoc = pFormatter->GetLocaleData();
+ const OUString& rTime = pLoc->getTimeSep();
+ if ( SkipString(rTime, rString, nPos) ) // 10:
+ {
+ if (nDecPos) // already , => error
+ {
+ return MatchedReturn();
+ }
+ if (eScannedType == SvNumFormatType::DATE && nNumericsCnt > 2) // 31.Dez.94 8:
+ {
+ SkipBlanks(rString, nPos);
+ eScannedType = SvNumFormatType::DATETIME;
+ }
+ else if (eScannedType != SvNumFormatType::UNDEFINED &&
+ eScannedType != SvNumFormatType::TIME) // already another type
+ {
+ return MatchedReturn();
+ }
+ else
+ {
+ SkipBlanks(rString, nPos);
+ eScannedType = SvNumFormatType::TIME;
+ }
+ if ( !nTimePos )
+ {
+ nTimePos = nStringsCnt;
+ }
+ }
+
+ bool bSignedYear = false;
+ bool bDate = SkipDatePatternSeparator( nStringsCnt-1, nPos, bSignedYear); // 12/31 31.12. 12/31/1999 31.12.1999
+ if (!bDate)
+ {
+ const OUString& rDate = pFormatter->GetDateSep();
+ bDate = SkipString( rDate, rString, nPos); // 10. 10- 10/
+ }
+ if (bDate && bSignDetectedHere)
+ {
+ nSign = 0; // 'D-' takes precedence over signed date
+ }
+ if (bDate || ((MayBeIso8601() || MayBeMonthDate())
+ && SkipChar( '-', rString, nPos)))
+ {
+ if (eScannedType != SvNumFormatType::UNDEFINED &&
+ eScannedType != SvNumFormatType::DATE) // already another type
+ {
+ return MatchedReturn();
+ }
+ else
+ {
+ SkipBlanks(rString, nPos);
+ eScannedType = SvNumFormatType::DATE;
+ }
+ short nTmpMonth = GetMonth(rString, nPos); // 10. Jan
+ if (nMonth && nTmpMonth) // month dup
+ {
+ return MatchedReturn();
+ }
+ if (nTmpMonth)
+ {
+ nMonth = nTmpMonth;
+ nMonthPos = 3; // month at end
+ if ( nMonth < 0 )
+ {
+ SkipChar( '.', rString, nPos ); // abbreviated
+ }
+ SkipBlanks(rString, nPos);
+ }
+ }
+
+ const sal_Int32 nMonthStart = nPos;
+ short nTempMonth = GetMonth(rString, nPos); // 10 Jan
+ if (nTempMonth)
+ {
+ if (nMonth) // month dup
+ {
+ return MatchedReturn();
+ }
+ if (eScannedType != SvNumFormatType::UNDEFINED &&
+ eScannedType != SvNumFormatType::DATE) // already another type
+ {
+ return MatchedReturn();
+ }
+ if (nMonthStart > 0) // 10Jan without separator is not a date
+ {
+ eScannedType = SvNumFormatType::DATE;
+ nMonth = nTempMonth;
+ nMonthPos = 3; // month at end
+ if ( nMonth < 0 )
+ {
+ SkipChar( '.', rString, nPos ); // abbreviated
+ }
+ SkipBlanks(rString, nPos);
+ }
+ else
+ {
+ nPos = nMonthStart; // rewind month
+ }
+ }
+
+ sal_Int32 nOrigPos = nPos;
+ if (GetTimeAmPm(rString, nPos))
+ {
+ if (eScannedType != SvNumFormatType::UNDEFINED &&
+ eScannedType != SvNumFormatType::TIME &&
+ eScannedType != SvNumFormatType::DATETIME) // already another type
+ {
+ return MatchedReturn();
+ }
+ else
+ {
+ // If not already scanned as time, 6.78am does not result in 6
+ // seconds and 78 hundredths in the morning. Keep as suffix.
+ if (eScannedType != SvNumFormatType::TIME && nDecPos == 2 && nNumericsCnt == 2)
+ {
+ nPos = nOrigPos; // rewind am/pm
+ }
+ else
+ {
+ SkipBlanks(rString, nPos);
+ if ( eScannedType != SvNumFormatType::DATETIME )
+ {
+ eScannedType = SvNumFormatType::TIME;
+ }
+ }
+ }
+ }
+
+ if ( bNegCheck && SkipChar(')', rString, nPos) )
+ {
+ if (eScannedType == SvNumFormatType::CURRENCY) // only if currency
+ {
+ bNegCheck = false; // skip ')'
+ SkipBlanks(rString, nPos);
+ }
+ else
+ {
+ return MatchedReturn();
+ }
+ }
+
+ if ( nPos < rString.getLength() &&
+ (eScannedType == SvNumFormatType::DATE ||
+ eScannedType == SvNumFormatType::DATETIME) )
+ {
+ // day of week is just parsed away
+ sal_Int32 nOldPos = nPos;
+ const OUString& rSep = pFormatter->GetLocaleData()->getLongDateDayOfWeekSep();
+ if ( StringContains( rSep, rString, nPos ) )
+ {
+ nPos = nPos + rSep.getLength();
+ SkipBlanks(rString, nPos);
+ }
+ int nTempDayOfWeek = GetDayOfWeek( rString, nPos );
+ if ( nTempDayOfWeek )
+ {
+ if ( nPos < rString.getLength() )
+ {
+ if ( nTempDayOfWeek < 0 )
+ { // short
+ if ( rString[ nPos ] == '.' )
+ {
+ ++nPos;
+ }
+ }
+ SkipBlanks(rString, nPos);
+ }
+ }
+ else
+ {
+ nPos = nOldPos;
+ }
+ }
+
+#if NF_RECOGNIZE_ISO8601_TIMEZONES
+ if (nPos == 0 && eScannedType == SvNumFormatType::DATETIME &&
+ rString.getLength() == 1 && rString[ 0 ] == 'Z' && MayBeIso8601())
+ {
+ // ISO 8601 timezone UTC yyyy-mm-ddThh:mmZ
+ ++nPos;
+ }
+#endif
+
+ if (nPos < rString.getLength()) // everything consumed?
+ {
+ // does input EndString equal EndString in Format?
+ if ( !ScanStringNumFor( rString, nPos, 0xFFFF ) )
+ {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+
+bool ImpSvNumberInputScan::ScanStringNumFor( const OUString& rString, // String to scan
+ sal_Int32 nPos, // Position until which was consumed
+ sal_uInt16 nString, // Substring of format, 0xFFFF => last
+ bool bDontDetectNegation) // Suppress sign detection
+{
+ if ( !mpFormat )
+ {
+ return false;
+ }
+ const ::utl::TransliterationWrapper* pTransliteration = pFormatter->GetTransliteration();
+ const OUString* pStr;
+ OUString aString( rString );
+ bool bFound = false;
+ bool bFirst = true;
+ bool bContinue = true;
+ sal_uInt16 nSub;
+ do
+ {
+ // Don't try "lower" subformats ff the very first match was the second
+ // or third subformat.
+ nSub = nStringScanNumFor;
+ do
+ { // Step through subformats, first positive, then negative, then
+ // other, but not the last (text) subformat.
+ pStr = mpFormat->GetNumForString( nSub, nString, true );
+ if ( pStr && pTransliteration->isEqual( aString, *pStr ) )
+ {
+ bFound = true;
+ bContinue = false;
+ }
+ else if ( nSub < 2 )
+ {
+ ++nSub;
+ }
+ else
+ {
+ bContinue = false;
+ }
+ }
+ while ( bContinue );
+ if ( !bFound && bFirst && nPos )
+ {
+ // try remaining substring
+ bFirst = false;
+ aString = aString.copy(nPos);
+ bContinue = true;
+ }
+ }
+ while ( bContinue );
+
+ if ( !bFound )
+ {
+ if ( !bDontDetectNegation && (nString == 0) &&
+ !bFirst && (nSign < 0) && mpFormat->IsSecondSubformatRealNegative() )
+ {
+ // simply negated twice? --1
+ aString = aString.replaceAll(" ", "");
+ if ( (aString.getLength() == 1) && (aString[0] == '-') )
+ {
+ bFound = true;
+ nStringScanSign = -1;
+ nSub = 0; //! not 1
+ }
+ }
+ if ( !bFound )
+ {
+ return false;
+ }
+ }
+ else if ( !bDontDetectNegation && (nSub == 1) &&
+ mpFormat->IsSecondSubformatRealNegative() )
+ {
+ // negative
+ if ( nStringScanSign < 0 )
+ {
+ if ( (nSign < 0) && (nStringScanNumFor != 1) )
+ {
+ nStringScanSign = 1; // triple negated --1 yyy
+ }
+ }
+ else if ( nStringScanSign == 0 )
+ {
+ if ( nSign < 0 )
+ { // nSign and nStringScanSign will be combined later,
+ // flip sign if doubly negated
+ if ( (nString == 0) && !bFirst &&
+ SvNumberformat::HasStringNegativeSign( aString ) )
+ {
+ nStringScanSign = -1; // direct double negation
+ }
+ else if ( mpFormat->IsNegativeWithoutSign() )
+ {
+ nStringScanSign = -1; // indirect double negation
+ }
+ }
+ else
+ {
+ nStringScanSign = -1;
+ }
+ }
+ else // > 0
+ {
+ nStringScanSign = -1;
+ }
+ }
+ nStringScanNumFor = nSub;
+ return true;
+}
+
+
+/**
+ * Recognizes types of number, exponential, fraction, percent, currency, date, time.
+ * Else text => return false
+ */
+bool ImpSvNumberInputScan::IsNumberFormatMain( const OUString& rString, // string to be analyzed
+ const SvNumberformat* pFormat ) // maybe number format set to match against
+{
+ Reset();
+ mpFormat = pFormat;
+ NumberStringDivision( rString ); // breakdown into strings and numbers
+ if (nStringsCnt >= SV_MAX_COUNT_INPUT_STRINGS) // too many elements
+ {
+ return false; // Njet, Nope, ...
+ }
+ if (nNumericsCnt == 0) // no number in input
+ {
+ if ( nStringsCnt > 0 )
+ {
+ // Here we may change the original, we don't need it anymore.
+ // This saves copies and ToUpper() in GetLogical() and is faster.
+ sStrArray[0] = comphelper::string::strip(sStrArray[0], ' ');
+ OUString& rStrArray = sStrArray[0];
+ nLogical = GetLogical( rStrArray );
+ if ( nLogical )
+ {
+ eScannedType = SvNumFormatType::LOGICAL; // !!! it's a BOOLEAN
+ nMatchedAllStrings &= ~nMatchedVirgin;
+ return true;
+ }
+ else
+ {
+ return false; // simple text
+ }
+ }
+ else
+ {
+ return false; // simple text
+ }
+ }
+
+ sal_uInt16 i = 0; // mark any symbol
+ sal_uInt16 j = 0; // mark only numbers
+
+ switch ( nNumericsCnt )
+ {
+ case 1 : // Exactly 1 number in input
+ // nStringsCnt >= 1
+ if (GetNextNumber(i,j)) // i=1,0
+ { // Number at start
+ if (eSetType == SvNumFormatType::FRACTION) // Fraction 1 = 1/1
+ {
+ if (i >= nStringsCnt || // no end string nor decimal separator
+ pFormatter->IsDecimalSep( sStrArray[i]))
+ {
+ eScannedType = SvNumFormatType::FRACTION;
+ nMatchedAllStrings &= ~nMatchedVirgin;
+ return true;
+ }
+ }
+ }
+ else
+ { // Analyze start string
+ if (!ScanStartString( sStrArray[i] )) // i=0
+ {
+ return false; // already an error
+ }
+ i++; // next symbol, i=1
+ }
+ GetNextNumber(i,j); // i=1,2
+ if (eSetType == SvNumFormatType::FRACTION) // Fraction -1 = -1/1
+ {
+ if (nSign && !bNegCheck && // Sign +, -
+ eScannedType == SvNumFormatType::UNDEFINED && // not date or currency
+ nDecPos == 0 && // no previous decimal separator
+ (i >= nStringsCnt || // no end string nor decimal separator
+ pFormatter->IsDecimalSep( sStrArray[i]))
+ )
+ {
+ eScannedType = SvNumFormatType::FRACTION;
+ nMatchedAllStrings &= ~nMatchedVirgin;
+ return true;
+ }
+ }
+ if (i < nStringsCnt && !ScanEndString( sStrArray[i] ))
+ {
+ return false;
+ }
+ break;
+ case 2 : // Exactly 2 numbers in input
+ // nStringsCnt >= 3
+ if (!GetNextNumber(i,j)) // i=1,0
+ { // Analyze start string
+ if (!ScanStartString( sStrArray[i] ))
+ return false; // already an error
+ i++; // i=1
+ }
+ GetNextNumber(i,j); // i=1,2
+ if ( !ScanMidString( sStrArray[i], i, j ) )
+ {
+ return false;
+ }
+ i++; // next symbol, i=2,3
+ GetNextNumber(i,j); // i=3,4
+ if (i < nStringsCnt && !ScanEndString( sStrArray[i] ))
+ {
+ return false;
+ }
+ if (eSetType == SvNumFormatType::FRACTION) // -1,200. as fraction
+ {
+ if (!bNegCheck && // no sign '('
+ eScannedType == SvNumFormatType::UNDEFINED &&
+ (nDecPos == 0 || nDecPos == 3) // no decimal separator or at end
+ )
+ {
+ eScannedType = SvNumFormatType::FRACTION;
+ nMatchedAllStrings &= ~nMatchedVirgin;
+ return true;
+ }
+ }
+ break;
+ case 3 : // Exactly 3 numbers in input
+ // nStringsCnt >= 5
+ if (!GetNextNumber(i,j)) // i=1,0
+ { // Analyze start string
+ if (!ScanStartString( sStrArray[i] ))
+ {
+ return false; // already an error
+ }
+ i++; // i=1
+ if (nDecPos == 1) // decimal separator at start => error
+ {
+ return false;
+ }
+ }
+ GetNextNumber(i,j); // i=1,2
+ if ( !ScanMidString( sStrArray[i], i, j ) )
+ {
+ return false;
+ }
+ i++; // i=2,3
+ if (eScannedType == SvNumFormatType::SCIENTIFIC) // E only at end
+ {
+ return false;
+ }
+ GetNextNumber(i,j); // i=3,4
+ if ( !ScanMidString( sStrArray[i], i, j ) )
+ {
+ return false;
+ }
+ i++; // i=4,5
+ GetNextNumber(i,j); // i=5,6
+ if (i < nStringsCnt && !ScanEndString( sStrArray[i] ))
+ {
+ return false;
+ }
+ if (eSetType == SvNumFormatType::FRACTION) // -1,200,100. as fraction
+ {
+ if (!bNegCheck && // no sign '('
+ eScannedType == SvNumFormatType::UNDEFINED &&
+ (nDecPos == 0 || nDecPos == 3) // no decimal separator or at end
+ )
+ {
+ eScannedType = SvNumFormatType::FRACTION;
+ nMatchedAllStrings &= ~nMatchedVirgin;
+ return true;
+ }
+ }
+ if ( eScannedType == SvNumFormatType::FRACTION && nDecPos )
+ {
+ return false; // #36857# not a real fraction
+ }
+ break;
+ default: // More than 3 numbers in input
+ // nStringsCnt >= 7
+ if (!GetNextNumber(i,j)) // i=1,0
+ { // Analyze startstring
+ if (!ScanStartString( sStrArray[i] ))
+ return false; // already an error
+ i++; // i=1
+ if (nDecPos == 1) // decimal separator at start => error
+ return false;
+ }
+ GetNextNumber(i,j); // i=1,2
+ if ( !ScanMidString( sStrArray[i], i, j ) )
+ {
+ return false;
+ }
+ i++; // i=2,3
+ {
+ sal_uInt16 nThOld = 10; // just not 0 or 1
+ while (nThOld != nThousand && j < nNumericsCnt-1) // Execute at least one time
+ // but leave one number.
+ { // Loop over group separators
+ nThOld = nThousand;
+ if (eScannedType == SvNumFormatType::SCIENTIFIC) // E only at end
+ {
+ return false;
+ }
+ GetNextNumber(i,j);
+ if ( i < nStringsCnt && !ScanMidString( sStrArray[i], i, j ) )
+ {
+ return false;
+ }
+ i++;
+ }
+ }
+ if (eScannedType == SvNumFormatType::DATE || // long date or
+ eScannedType == SvNumFormatType::TIME || // long time or
+ eScannedType == SvNumFormatType::UNDEFINED) // long number
+ {
+ for (sal_uInt16 k = j; k < nNumericsCnt-1; k++)
+ {
+ if (eScannedType == SvNumFormatType::SCIENTIFIC) // E only at endd
+ {
+ return false;
+ }
+ GetNextNumber(i,j);
+ if ( i < nStringsCnt && !ScanMidString( sStrArray[i], i, j ) )
+ {
+ return false;
+ }
+ i++;
+ }
+ }
+ GetNextNumber(i,j);
+ if (i < nStringsCnt && !ScanEndString( sStrArray[i] ))
+ {
+ return false;
+ }
+ if (eSetType == SvNumFormatType::FRACTION) // -1,200,100. as fraction
+ {
+ if (!bNegCheck && // no sign '('
+ eScannedType == SvNumFormatType::UNDEFINED &&
+ (nDecPos == 0 || nDecPos == 3) // no decimal separator or at end
+ )
+ {
+ eScannedType = SvNumFormatType::FRACTION;
+ nMatchedAllStrings &= ~nMatchedVirgin;
+ return true;
+ }
+ }
+ if ( eScannedType == SvNumFormatType::FRACTION && nDecPos )
+ {
+ return false; // #36857# not a real fraction
+ }
+ break;
+ }
+
+ if (eScannedType == SvNumFormatType::UNDEFINED)
+ {
+ nMatchedAllStrings &= ~nMatchedVirgin;
+ // did match including nMatchedUsedAsReturn
+ bool bDidMatch = (nMatchedAllStrings != 0);
+ if ( nMatchedAllStrings )
+ {
+ bool bMatch = mpFormat && mpFormat->IsNumForStringElementCountEqual(
+ nStringScanNumFor, nStringsCnt, nNumericsCnt );
+ if ( !bMatch )
+ {
+ nMatchedAllStrings = 0;
+ }
+ }
+ if ( nMatchedAllStrings )
+ {
+ // A type DEFINED means that no category could be assigned to the
+ // overall format because of mixed type subformats. Use the scan
+ // matched subformat's type if any.
+ SvNumFormatType eForType = eSetType;
+ if ((eForType == SvNumFormatType::UNDEFINED || eForType == SvNumFormatType::DEFINED) && mpFormat)
+ eForType = mpFormat->GetNumForInfoScannedType( nStringScanNumFor);
+ if (eForType != SvNumFormatType::UNDEFINED && eForType != SvNumFormatType::DEFINED)
+ eScannedType = eForType;
+ else
+ eScannedType = SvNumFormatType::NUMBER;
+ }
+ else if ( bDidMatch )
+ {
+ // Accept a plain fractional number like 123.45 as there may be a
+ // decimal separator also present as literal like in a 0"."0 weirdo
+ // format.
+ if (nDecPos != 2 || nNumericsCnt != 2)
+ return false;
+ eScannedType = SvNumFormatType::NUMBER;
+ }
+ else
+ {
+ eScannedType = SvNumFormatType::NUMBER;
+ // everything else should have been recognized by now
+ }
+ }
+ else if ( eScannedType == SvNumFormatType::DATE )
+ {
+ // the very relaxed date input checks may interfere with a preset format
+ nMatchedAllStrings &= ~nMatchedVirgin;
+ bool bWasReturn = ((nMatchedAllStrings & nMatchedUsedAsReturn) != 0);
+ if ( nMatchedAllStrings )
+ {
+ bool bMatch = mpFormat && mpFormat->IsNumForStringElementCountEqual(
+ nStringScanNumFor, nStringsCnt, nNumericsCnt );
+ if ( !bMatch )
+ {
+ nMatchedAllStrings = 0;
+ }
+ }
+ if ( nMatchedAllStrings )
+ {
+ // A type DEFINED means that no category could be assigned to the
+ // overall format because of mixed type subformats. Do not override
+ // the scanned type in this case. Otherwise in IsNumberFormat() the
+ // first numeric particle would be accepted as number.
+ SvNumFormatType eForType = eSetType;
+ if ((eForType == SvNumFormatType::UNDEFINED || eForType == SvNumFormatType::DEFINED) && mpFormat)
+ eForType = mpFormat->GetNumForInfoScannedType( nStringScanNumFor);
+ if (eForType != SvNumFormatType::UNDEFINED && eForType != SvNumFormatType::DEFINED)
+ eScannedType = eForType;
+ }
+ else if ( bWasReturn )
+ {
+ return false;
+ }
+ }
+ else
+ {
+ nMatchedAllStrings = 0; // reset flag to no substrings matched
+ }
+ return true;
+}
+
+
+/**
+ * Return true or false depending on the nMatched... state and remember usage
+ */
+bool ImpSvNumberInputScan::MatchedReturn()
+{
+ if ( nMatchedAllStrings & ~nMatchedVirgin )
+ {
+ nMatchedAllStrings |= nMatchedUsedAsReturn;
+ return true;
+ }
+ return false;
+}
+
+
+/**
+ * Initialize uppercase months and weekdays
+ */
+void ImpSvNumberInputScan::InitText()
+{
+ sal_Int32 j, nElems;
+ const CharClass* pChrCls = pFormatter->GetCharClass();
+ const CalendarWrapper* pCal = pFormatter->GetCalendar();
+
+ pUpperMonthText.reset();
+ pUpperAbbrevMonthText.reset();
+ css::uno::Sequence< css::i18n::CalendarItem2 > xElems = pCal->getMonths();
+ nElems = xElems.getLength();
+ pUpperMonthText.reset( new OUString[nElems] );
+ pUpperAbbrevMonthText.reset( new OUString[nElems] );
+ for ( j = 0; j < nElems; j++ )
+ {
+ pUpperMonthText[j] = pChrCls->uppercase( xElems[j].FullName );
+ pUpperAbbrevMonthText[j] = pChrCls->uppercase( xElems[j].AbbrevName );
+ }
+
+ pUpperGenitiveMonthText.reset();
+ pUpperGenitiveAbbrevMonthText.reset();
+ xElems = pCal->getGenitiveMonths();
+ bScanGenitiveMonths = (nElems != xElems.getLength());
+ nElems = xElems.getLength();
+ pUpperGenitiveMonthText.reset( new OUString[nElems] );
+ pUpperGenitiveAbbrevMonthText.reset( new OUString[nElems] );
+ for ( j = 0; j < nElems; j++ )
+ {
+ pUpperGenitiveMonthText[j] = pChrCls->uppercase( xElems[j].FullName );
+ pUpperGenitiveAbbrevMonthText[j] = pChrCls->uppercase( xElems[j].AbbrevName );
+ if (!bScanGenitiveMonths &&
+ (pUpperGenitiveMonthText[j] != pUpperMonthText[j] ||
+ pUpperGenitiveAbbrevMonthText[j] != pUpperAbbrevMonthText[j]))
+ {
+ bScanGenitiveMonths = true;
+ }
+ }
+
+ pUpperPartitiveMonthText.reset();
+ pUpperPartitiveAbbrevMonthText.reset();
+ xElems = pCal->getPartitiveMonths();
+ bScanPartitiveMonths = (nElems != xElems.getLength());
+ nElems = xElems.getLength();
+ pUpperPartitiveMonthText.reset( new OUString[nElems] );
+ pUpperPartitiveAbbrevMonthText.reset( new OUString[nElems] );
+ for ( j = 0; j < nElems; j++ )
+ {
+ pUpperPartitiveMonthText[j] = pChrCls->uppercase( xElems[j].FullName );
+ pUpperPartitiveAbbrevMonthText[j] = pChrCls->uppercase( xElems[j].AbbrevName );
+ if (!bScanPartitiveMonths &&
+ (pUpperPartitiveMonthText[j] != pUpperGenitiveMonthText[j] ||
+ pUpperPartitiveAbbrevMonthText[j] != pUpperGenitiveAbbrevMonthText[j]))
+ {
+ bScanPartitiveMonths = true;
+ }
+ }
+
+ pUpperDayText.reset();
+ pUpperAbbrevDayText.reset();
+ xElems = pCal->getDays();
+ nElems = xElems.getLength();
+ pUpperDayText.reset( new OUString[nElems] );
+ pUpperAbbrevDayText.reset( new OUString[nElems] );
+ for ( j = 0; j < nElems; j++ )
+ {
+ pUpperDayText[j] = pChrCls->uppercase( xElems[j].FullName );
+ pUpperAbbrevDayText[j] = pChrCls->uppercase( xElems[j].AbbrevName );
+ }
+
+ bTextInitialized = true;
+}
+
+
+/**
+ * MUST be called if International/Locale is changed
+ */
+void ImpSvNumberInputScan::ChangeIntl()
+{
+ sal_Unicode cDecSep = pFormatter->GetNumDecimalSep()[0];
+ bDecSepInDateSeps = ( cDecSep == '-' ||
+ cDecSep == pFormatter->GetDateSep()[0] );
+ if (!bDecSepInDateSeps)
+ {
+ sal_Unicode cDecSepAlt = pFormatter->GetNumDecimalSepAlt().toChar();
+ bDecSepInDateSeps = cDecSepAlt && (cDecSepAlt == '-' || cDecSepAlt == pFormatter->GetDateSep()[0]);
+ }
+ bTextInitialized = false;
+ aUpperCurrSymbol.clear();
+ InvalidateDateAcceptancePatterns();
+}
+
+
+void ImpSvNumberInputScan::InvalidateDateAcceptancePatterns()
+{
+ if (sDateAcceptancePatterns.hasElements())
+ {
+ sDateAcceptancePatterns = css::uno::Sequence< OUString >();
+ }
+}
+
+
+void ImpSvNumberInputScan::ChangeNullDate( const sal_uInt16 Day,
+ const sal_uInt16 Month,
+ const sal_Int16 Year )
+{
+ moNullDate = Date(Day, Month, Year);
+}
+
+
+/**
+ * Does rString represent a number (also date, time et al)
+ */
+bool ImpSvNumberInputScan::IsNumberFormat( const OUString& rString, // string to be analyzed
+ SvNumFormatType& F_Type, // IN: old type, OUT: new type
+ double& fOutNumber, // OUT: number if convertible
+ const SvNumberformat* pFormat, // maybe a number format to match against
+ SvNumInputOptions eInputOptions )
+{
+ bool res; // return value
+ sal_uInt16 k;
+ eSetType = F_Type; // old type set
+
+ if ( !rString.getLength() )
+ {
+ res = false;
+ }
+ else if (rString.getLength() > 308) // arbitrary
+ {
+ res = false;
+ }
+ else
+ {
+ // NoMoreUpperNeeded, all comparisons on UpperCase
+ OUString aString = pFormatter->GetCharClass()->uppercase( rString );
+ // convert native number to ASCII if necessary
+ TransformInput(pFormatter, aString);
+ res = IsNumberFormatMain( aString, pFormat );
+ }
+
+ if (res)
+ {
+ // Accept signed date only for ISO date with at least four digits in
+ // year to not have an input of -M-D-Y arbitrarily recognized. The
+ // final order is only determined in GetDateRef().
+ // Also accept for Y/M/D date pattern match, i.e. if the first number
+ // is year.
+ // Accept only if the year immediately follows the sign character with
+ // no space in between.
+ if (nSign && (eScannedType == SvNumFormatType::DATE ||
+ eScannedType == SvNumFormatType::DATETIME) && mbEraCE == kDefaultEra &&
+ (IsDatePatternNumberOfType(0,'Y') || (MayBeIso8601() && sStrArray[nNums[0]].getLength() >= 4)))
+ {
+ const sal_Unicode c = sStrArray[0][sStrArray[0].getLength()-1];
+ if (c == '-' || c == '+')
+ {
+ // A '+' sign doesn't change the era.
+ if (nSign < 0)
+ mbEraCE = false; // BCE
+ nSign = 0;
+ }
+ }
+ if ( bNegCheck || // ')' not found for '('
+ (nSign && (eScannedType == SvNumFormatType::DATE ||
+ eScannedType == SvNumFormatType::DATETIME))) // signed date/datetime
+ {
+ res = false;
+ }
+ else
+ { // check count of partial number strings
+ switch (eScannedType)
+ {
+ case SvNumFormatType::PERCENT:
+ case SvNumFormatType::CURRENCY:
+ case SvNumFormatType::NUMBER:
+ if (nDecPos == 1) // .05
+ {
+ // Matched MidStrings function like group separators, but
+ // there can't be an integer part numeric input, so
+ // effectively 0 thousands groups.
+ if ( nMatchedAllStrings )
+ {
+ nThousand = 0;
+ }
+ else if ( nNumericsCnt != 1 )
+ {
+ res = false;
+ }
+ }
+ else if (nDecPos == 2) // 1.05
+ {
+ // Matched MidStrings function like group separators, but
+ // let a decimal separator override a literal separator
+ // string; like 0"." with input 123.45
+ if ( nMatchedAllStrings )
+ {
+ if (nNumericsCnt == 2)
+ nThousand = 0;
+ else
+ {
+ // Assume that if there was a decimal separator
+ // matching also a literal string then it was the
+ // last. We could find the last possible match to
+ // support literals in fractions, but really..
+ nThousand = nNumericsCnt - 1;
+ }
+ }
+ else if ( nNumericsCnt != nThousand+2 )
+ {
+ res = false;
+ }
+ }
+ else // 1,100 or 1,100.
+ {
+ // matched MidStrings function like group separators
+ if ( nMatchedAllStrings )
+ {
+ nThousand = nNumericsCnt - 1;
+ }
+ else if ( nNumericsCnt != nThousand+1 )
+ {
+ res = false;
+ }
+ }
+ break;
+
+ case SvNumFormatType::SCIENTIFIC: // 1.0e-2
+ if (nDecPos == 1) // .05
+ {
+ if (nNumericsCnt != 2)
+ {
+ res = false;
+ }
+ }
+ else if (nDecPos == 2) // 1.05
+ {
+ if (nNumericsCnt != nThousand+3)
+ {
+ res = false;
+ }
+ }
+ else // 1,100 or 1,100.
+ {
+ if (nNumericsCnt != nThousand+2)
+ {
+ res = false;
+ }
+ }
+ break;
+
+ case SvNumFormatType::DATE:
+ if (nMonth < 0 && nDayOfWeek < 0 && nNumericsCnt == 3)
+ {
+ // If both, short month name and day of week name were
+ // detected, and also numbers for full date, assume that we
+ // have a day of week instead of month name.
+ nMonth = 0;
+ nMonthPos = 0;
+ }
+ if (nMonth)
+ { // month name and numbers
+ if (nNumericsCnt > 2)
+ {
+ res = false;
+ }
+ }
+ else
+ {
+ if (nNumericsCnt > 3)
+ {
+ res = false;
+ }
+ else
+ {
+ // Even if a date pattern was matched, for abbreviated
+ // pattern like "D.M." an input of "D.M. #" was
+ // accepted because # could had been a time. Here we do
+ // not have a combined date/time input though and #
+ // would be taken as Year in this example, which it is
+ // not. The count of numbers in pattern must match the
+ // count of numbers in input.
+ res = (GetDatePatternNumbers() == nNumericsCnt)
+ || IsAcceptableIso8601() || nMatchedAllStrings;
+ }
+ }
+ break;
+
+ case SvNumFormatType::TIME:
+ if (nDecPos)
+ { // hundredth seconds included
+ if (nNumericsCnt > 4)
+ {
+ res = false;
+ }
+ }
+ else
+ {
+ if (nNumericsCnt > 3)
+ {
+ res = false;
+ }
+ }
+ break;
+
+ case SvNumFormatType::DATETIME:
+ if (nMonth < 0 && nDayOfWeek < 0 && nNumericsCnt >= 5)
+ {
+ // If both, abbreviated month name and day of week name
+ // were detected, and also at least numbers for full date
+ // plus time including minutes, assume that we have a day
+ // of week instead of month name.
+ nMonth = 0;
+ nMonthPos = 0;
+ }
+ if (nMonth)
+ { // month name and numbers
+ if (nDecPos)
+ { // hundredth seconds included
+ if (nNumericsCnt > 6)
+ {
+ res = false;
+ }
+ }
+ else
+ {
+ if (nNumericsCnt > 5)
+ {
+ res = false;
+ }
+ }
+ }
+ else
+ {
+ if (nDecPos)
+ { // hundredth seconds included
+ if (nNumericsCnt > 7)
+ {
+ res = false;
+ }
+ }
+ else
+ {
+ if (nNumericsCnt > 6)
+ {
+ res = false;
+ }
+ }
+ if (res)
+ {
+ res = IsAcceptedDatePattern( nNums[0]) || MayBeIso8601() || nMatchedAllStrings;
+ }
+ }
+ break;
+
+ default:
+ break;
+ } // switch
+ } // else
+ } // if (res)
+
+ OUStringBuffer sResString;
+
+ if (res)
+ { // we finally have a number
+ switch (eScannedType)
+ {
+ case SvNumFormatType::LOGICAL:
+ if (nLogical == 1)
+ {
+ fOutNumber = 1.0; // True
+ }
+ else if (nLogical == -1)
+ {
+ fOutNumber = 0.0; // False
+ }
+ else
+ {
+ res = false; // Oops
+ }
+ break;
+
+ case SvNumFormatType::PERCENT:
+ case SvNumFormatType::CURRENCY:
+ case SvNumFormatType::NUMBER:
+ case SvNumFormatType::SCIENTIFIC:
+ case SvNumFormatType::DEFINED: // if no category detected handle as number
+ if ( nDecPos == 1 ) // . at start
+ {
+ sResString.append("0.");
+ }
+
+ for ( k = 0; k <= nThousand; k++)
+ {
+ sResString.append(sStrArray[nNums[k]]); // integer part
+ }
+ if ( nDecPos == 2 && k < nNumericsCnt ) // . somewhere
+ {
+ sResString.append('.');
+ sal_uInt16 nStop = (eScannedType == SvNumFormatType::SCIENTIFIC ?
+ nNumericsCnt-1 : nNumericsCnt);
+ for ( ; k < nStop; k++)
+ {
+ sResString.append(sStrArray[nNums[k]]); // fractional part
+ }
+ }
+
+ if (eScannedType != SvNumFormatType::SCIENTIFIC)
+ {
+ fOutNumber = StringToDouble(sResString);
+ }
+ else
+ { // append exponent
+ sResString.append('E');
+ if ( nESign == -1 )
+ {
+ sResString.append('-');
+ }
+ sResString.append(sStrArray[nNums[nNumericsCnt-1]]);
+ rtl_math_ConversionStatus eStatus;
+ fOutNumber = ::rtl::math::stringToDouble( sResString, '.', ',', &eStatus );
+ if ( eStatus == rtl_math_ConversionStatus_OutOfRange )
+ {
+ F_Type = SvNumFormatType::TEXT; // overflow/underflow -> Text
+ if (nESign == -1)
+ {
+ fOutNumber = 0.0;
+ }
+ else
+ {
+ fOutNumber = DBL_MAX;
+ }
+ return true;
+ }
+ }
+
+ if ( nStringScanSign )
+ {
+ if ( nSign )
+ {
+ nSign *= nStringScanSign;
+ }
+ else
+ {
+ nSign = nStringScanSign;
+ }
+ }
+ if ( nSign < 0 )
+ {
+ fOutNumber = -fOutNumber;
+ }
+
+ if (eScannedType == SvNumFormatType::PERCENT)
+ {
+ fOutNumber/= 100.0;
+ }
+ break;
+
+ case SvNumFormatType::FRACTION:
+ if (nNumericsCnt == 1)
+ {
+ fOutNumber = StringToDouble(sStrArray[nNums[0]]);
+ }
+ else if (nNumericsCnt == 2)
+ {
+ if (nThousand == 1)
+ {
+ sResString = sStrArray[nNums[0]];
+ sResString.append(sStrArray[nNums[1]]); // integer part
+ fOutNumber = StringToDouble(sResString);
+ }
+ else
+ {
+ double fNumerator = StringToDouble(sStrArray[nNums[0]]);
+ double fDenominator = StringToDouble(sStrArray[nNums[1]]);
+ if (fDenominator != 0.0)
+ {
+ fOutNumber = fNumerator/fDenominator;
+ }
+ else
+ {
+ res = false;
+ }
+ }
+ }
+ else // nNumericsCnt > 2
+ {
+ k = 1;
+ sResString = sStrArray[nNums[0]];
+ if (nThousand > 0)
+ {
+ for (; k <= nThousand; k++)
+ {
+ sResString.append(sStrArray[nNums[k]]);
+ }
+ }
+ fOutNumber = StringToDouble(sResString);
+
+ if (k == nNumericsCnt-2)
+ {
+ double fNumerator = StringToDouble(sStrArray[nNums[k]]);
+ double fDenominator = StringToDouble(sStrArray[nNums[k + 1]]);
+ if (fDenominator != 0.0)
+ {
+ fOutNumber += fNumerator/fDenominator;
+ }
+ else
+ {
+ res = false;
+ }
+ }
+ }
+
+ if ( nStringScanSign )
+ {
+ if ( nSign )
+ {
+ nSign *= nStringScanSign;
+ }
+ else
+ {
+ nSign = nStringScanSign;
+ }
+ }
+ if ( nSign < 0 )
+ {
+ fOutNumber = -fOutNumber;
+ }
+ break;
+
+ case SvNumFormatType::TIME:
+ res = GetTimeRef(fOutNumber, 0, nNumericsCnt, eInputOptions);
+ if ( nSign < 0 )
+ {
+ fOutNumber = -fOutNumber;
+ }
+ break;
+
+ case SvNumFormatType::DATE:
+ res = GetDateRef( fOutNumber, k );
+ break;
+
+ case SvNumFormatType::DATETIME:
+ res = GetDateRef( fOutNumber, k );
+ if ( res )
+ {
+ double fTime;
+ res = GetTimeRef( fTime, k, nNumericsCnt - k, eInputOptions);
+ fOutNumber += fTime;
+ }
+ break;
+
+ default:
+ SAL_WARN( "svl.numbers", "Some number recognized but what's it?" );
+ fOutNumber = 0.0;
+ break;
+ }
+ }
+
+ if (res) // overflow/underflow -> Text
+ {
+ if (fOutNumber < -DBL_MAX) // -1.7E308
+ {
+ F_Type = SvNumFormatType::TEXT;
+ fOutNumber = -DBL_MAX;
+ return true;
+ }
+ else if (fOutNumber > DBL_MAX) // 1.7E308
+ {
+ F_Type = SvNumFormatType::TEXT;
+ fOutNumber = DBL_MAX;
+ return true;
+ }
+ }
+
+ if (!res)
+ {
+ eScannedType = SvNumFormatType::TEXT;
+ fOutNumber = 0.0;
+ }
+
+ F_Type = eScannedType;
+ return res;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/numbers/zforfind.hxx b/svl/source/numbers/zforfind.hxx
new file mode 100644
index 0000000000..dea732b932
--- /dev/null
+++ b/svl/source/numbers/zforfind.hxx
@@ -0,0 +1,443 @@
+/* -*- 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 .
+ */
+
+#ifndef INCLUDED_SVL_SOURCE_NUMBERS_ZFORFIND_HXX
+#define INCLUDED_SVL_SOURCE_NUMBERS_ZFORFIND_HXX
+
+#include <com/sun/star/uno/Sequence.hxx>
+#include <rtl/ustring.hxx>
+#include <svl/zforlist.hxx>
+#include <tools/date.hxx>
+#include <memory>
+#include <optional>
+
+class SvNumberformat;
+class SvNumberFormatter;
+enum class SvNumFormatType : sal_Int16;
+
+#define SV_MAX_COUNT_INPUT_STRINGS 20 // max count of substrings in input scanner
+
+class ImpSvNumberInputScan
+{
+public:
+ explicit ImpSvNumberInputScan( SvNumberFormatter* pFormatter );
+ ~ImpSvNumberInputScan();
+
+/*!*/ void ChangeIntl(); // MUST be called if language changes
+
+ /// set reference date for offset calculation
+ void ChangeNullDate( const sal_uInt16 nDay,
+ const sal_uInt16 nMonth,
+ const sal_Int16 nYear );
+
+ /// convert input string to number
+ bool IsNumberFormat( const OUString& rString, /// input string
+ SvNumFormatType& F_Type, /// format type (in + out)
+ double& fOutNumber, /// value determined (out)
+ const SvNumberformat* pFormat, /// number format to which compare against
+ SvNumInputOptions eInputOptions);
+
+ /// after IsNumberFormat: get decimal position
+ short GetDecPos() const { return nDecPos; }
+ /// after IsNumberFormat: get count of numeric substrings in input string
+ sal_uInt16 GetNumericsCount() const { return nNumericsCnt; }
+
+ /// set threshold of two-digit year input
+ void SetYear2000( sal_uInt16 nVal ) { nYear2000 = nVal; }
+ /// get threshold of two-digit year input
+ sal_uInt16 GetYear2000() const { return nYear2000; }
+
+ /** Whether input can be forced to ISO 8601 format.
+
+ Depends on locale's date separator and a specific date format order.
+ */
+ bool CanForceToIso8601( DateOrder eDateOrder );
+
+ void InvalidateDateAcceptancePatterns();
+
+ /** Whether 'T' separator was detected in an ISO 8601 date+time format.
+ */
+ bool HasIso8601Tsep() const { return bIso8601Tsep; }
+
+private:
+ SvNumberFormatter* pFormatter;
+ const SvNumberformat* mpFormat; //* The format to compare against, if any
+ std::unique_ptr<OUString[]> pUpperMonthText; //* Array of month names, uppercase
+ std::unique_ptr<OUString[]> pUpperAbbrevMonthText; //* Array of month names, abbreviated, uppercase
+ std::unique_ptr<OUString[]> pUpperGenitiveMonthText; //* Array of genitive month names, uppercase
+ std::unique_ptr<OUString[]> pUpperGenitiveAbbrevMonthText; //* Array of genitive month names, abbreviated, uppercase
+ std::unique_ptr<OUString[]> pUpperPartitiveMonthText; //* Array of partitive month names, uppercase
+ std::unique_ptr<OUString[]> pUpperPartitiveAbbrevMonthText;//* Array of partitive month names, abbreviated, uppercase
+ std::unique_ptr<OUString[]> pUpperDayText; //* Array of day of week names, uppercase
+ std::unique_ptr<OUString[]> pUpperAbbrevDayText; //* Array of day of week names, abbreviated, uppercase
+ OUString aUpperCurrSymbol; //* Currency symbol, uppercase
+ bool bTextInitialized; //* Whether days and months are initialized
+ bool bScanGenitiveMonths; //* Whether to scan an input for genitive months
+ bool bScanPartitiveMonths; //* Whether to scan an input for partitive months
+ std::optional<Date> moNullDate; //* 30Dec1899
+ // Variables for provisional results:
+ OUString sStrArray[SV_MAX_COUNT_INPUT_STRINGS];//* Array of scanned substrings
+ bool IsNum[SV_MAX_COUNT_INPUT_STRINGS]; //* Whether a substring is numeric
+ sal_uInt16 nNums[SV_MAX_COUNT_INPUT_STRINGS]; //* Sequence of offsets to numeric strings
+ sal_uInt16 nStringsCnt; //* Total count of scanned substrings
+ sal_uInt16 nNumericsCnt; //* Count of numeric substrings
+ bool bDecSepInDateSeps; //* True <=> DecSep in {.,-,/,DateSep}
+ sal_uInt8 nMatchedAllStrings; //* Scan...String() matched all substrings,
+
+ // bit mask of nMatched... constants
+ static const sal_uInt8 nMatchedEndString; // 0x01
+ static const sal_uInt8 nMatchedMidString; // 0x02
+ static const sal_uInt8 nMatchedStartString; // 0x04
+ static const sal_uInt8 nMatchedVirgin; // 0x08
+ static const sal_uInt8 nMatchedUsedAsReturn; // 0x10
+
+ int nSign; // Sign of number
+ int nMonth; // Month (1..x) if date
+ // negative => short format
+ short nMonthPos; // 1 = front, 2 = middle
+ // 3 = end
+ int nDayOfWeek; // Temporary (!) day of week (1..7,-1..-7) if date
+ sal_uInt16 nTimePos; // Index of first time separator (+1)
+ short nDecPos; // Index of substring containing "," (+1)
+ bool bNegCheck; // '( )' for negative
+ short nESign; // Sign of exponent
+ short nAmPm; // +1 AM, -1 PM, 0 if none
+ short nLogical; // -1 => False, 1 => True
+ bool mbEraCE; // Era if date, 0 => BCE, 1 => CE (currently only Gregorian)
+ sal_uInt16 nThousand; // Count of group (AKA thousand) separators
+ sal_uInt16 nPosThousandString; // Position of concatenated 000,000,000 string
+ SvNumFormatType eScannedType; // Scanned type
+ SvNumFormatType eSetType; // Preset Type
+
+ sal_uInt16 nStringScanNumFor; // Fixed strings recognized in
+ // pFormat->NumFor[nNumForStringScan]
+ short nStringScanSign; // Sign resulting of FixString
+ sal_uInt16 nYear2000; // Two-digit threshold
+ // Year as 20xx
+ // default 18
+ // number <= nYear2000 => 20xx
+ // number > nYear2000 => 19xx
+
+ /** State of ISO 8601 detection.
+
+ 0:= don't know yet
+ 1:= no
+ 2:= yes, <=2 digits in year
+ 3:= yes, 3 digits in year
+ 4:= yes, >=4 digits in year
+
+ @see MayBeIso8601()
+ */
+ sal_uInt8 nMayBeIso8601;
+
+ /** Whether the 'T' time separator was detected in an ISO 8601 string. */
+ bool bIso8601Tsep;
+
+ /** State of dd-month-yy or yy-month-dd detection, with month name.
+
+ 0:= don't know yet
+ 1:= no
+ 2:= yes, dd-month-yy
+ 3:= yes, yy-month-dd
+
+ @see MayBeMonthDate()
+ */
+ sal_uInt8 nMayBeMonthDate;
+
+ /** Input matched this locale dependent date acceptance pattern.
+ -2 if not checked yet, -1 if no match, >=0 matched pattern.
+
+ @see IsAcceptedDatePattern()
+ */
+ sal_Int32 nAcceptedDatePattern;
+ css::uno::Sequence< OUString > sDateAcceptancePatterns;
+
+ /** If input matched a date acceptance pattern that starts at input
+ particle sStrArray[nDatePatternStart].
+
+ @see IsAcceptedDatePattern()
+ */
+ sal_uInt16 nDatePatternStart;
+
+ /** Count of numbers that matched the accepted pattern, if any, else 0.
+
+ @see GetDatePatternNumbers()
+ */
+ sal_uInt16 nDatePatternNumbers;
+
+ // Copy assignment is forbidden and not implemented.
+ ImpSvNumberInputScan (const ImpSvNumberInputScan &) = delete;
+ ImpSvNumberInputScan & operator= (const ImpSvNumberInputScan &) = delete;
+
+ void Reset(); // Reset all variables before start of analysis
+
+ void InitText(); // Init of months and days of week
+
+ // Convert string to double.
+ // Only simple unsigned floating point values without any error detection,
+ // decimal separator has to be '.'
+ // If bForceFraction==true the string is taken to be the fractional part
+ // of 0.1234 without the leading 0. (thus being just "1234").
+ static double StringToDouble( std::u16string_view aStr,
+ bool bForceFraction = false );
+
+ // Next number/string symbol
+ static bool NextNumberStringSymbol( const sal_Unicode*& pStr,
+ OUString& rSymbol );
+
+ // Concatenate ,000,23 blocks
+ // in input to 000123
+ bool SkipThousands( const sal_Unicode*& pStr, OUString& rSymbol ) const;
+
+ // Divide numbers/strings into
+ // arrays and variables above.
+ // Leading blanks and blanks
+ // after numbers are thrown away
+ void NumberStringDivision( const OUString& rString );
+
+
+ /** Whether rString contains word (!) rWhat at nPos.
+ rWhat will not be matched if it is a substring of a word.
+ */
+ bool StringContainsWord( const OUString& rWhat,
+ const OUString& rString,
+ sal_Int32 nPos ) const;
+
+ // optimized substring versions
+
+ // Whether rString contains rWhat at nPos
+ static bool StringContains( const OUString& rWhat,
+ const OUString& rString,
+ sal_Int32 nPos )
+ {
+ if (rWhat.isEmpty() || rString.getLength() <= nPos)
+ {
+ return false;
+ }
+ // mostly used with one character
+ if ( rWhat[ 0 ] != rString[ nPos ] )
+ {
+ return false;
+ }
+ return StringContainsImpl( rWhat, rString, nPos );
+ }
+
+ // Whether pString contains rWhat at nPos
+ static bool StringPtrContains( const OUString& rWhat,
+ const sal_Unicode* pString,
+ sal_Int32 nPos ) // nPos MUST be a valid offset from pString
+ {
+ // mostly used with one character
+ if ( rWhat[ 0 ] != pString[ nPos ] )
+ {
+ return false;
+ }
+ return StringPtrContainsImpl( rWhat, pString, nPos );
+ }
+
+ //! DO NOT use directly
+ static bool StringContainsImpl( const OUString& rWhat,
+ const OUString& rString,
+ sal_Int32 nPos );
+ //! DO NOT use directly
+ static bool StringPtrContainsImpl( const OUString& rWhat,
+ const sal_Unicode* pString,
+ sal_Int32 nPos );
+
+ // Skip a special character
+ static inline bool SkipChar( sal_Unicode c,
+ std::u16string_view rString,
+ sal_Int32& nPos );
+
+ // Skip blank
+ static inline bool SkipBlanks( const OUString& rString,
+ sal_Int32& nPos );
+
+ // Jump over rWhat in rString at nPos
+ static inline bool SkipString( const OUString& rWhat,
+ const OUString& rString,
+ sal_Int32& nPos );
+
+ // Recognizes exactly ,111 as group separator
+ inline bool GetThousandSep( std::u16string_view rString,
+ sal_Int32& nPos,
+ sal_uInt16 nStringPos ) const;
+ // Get boolean value
+ short GetLogical( std::u16string_view rString ) const;
+
+ // Get month and advance string position
+ short GetMonth( const OUString& rString,
+ sal_Int32& nPos );
+
+ // Get day of week and advance string position
+ int GetDayOfWeek( const OUString& rString,
+ sal_Int32& nPos );
+
+ // Get currency symbol and advance string position
+ bool GetCurrency( const OUString& rString,
+ sal_Int32& nPos );
+
+ // Get symbol AM or PM and advance string position
+ bool GetTimeAmPm( const OUString& rString,
+ sal_Int32& nPos );
+
+ // Get decimal separator and advance string position
+ inline bool GetDecSep( std::u16string_view rString,
+ sal_Int32& nPos ) const;
+
+ // Get hundredth seconds separator and advance string position
+ inline bool GetTime100SecSep( std::u16string_view rString,
+ sal_Int32& nPos ) const;
+
+ // Get sign and advance string position
+ // Including special case '('
+ int GetSign( std::u16string_view rString,
+ sal_Int32& nPos );
+
+ // Get sign of exponent and advance string position
+ static short GetESign( std::u16string_view rString,
+ sal_Int32& nPos );
+
+ // Get next number as array offset
+ inline bool GetNextNumber( sal_uInt16& i,
+ sal_uInt16& j ) const;
+
+ /** Converts time -> double (only decimals)
+
+ @return TRUE if time, FALSE if not (e.g. hours >12 with AM/PM)
+ */
+ bool GetTimeRef( double& fOutNumber, // result as double
+ sal_uInt16 nIndex, // Index of hour in input
+ sal_uInt16 nCnt, // Count of time substrings in input
+ SvNumInputOptions eInputOptions ) const;
+ sal_uInt16 ImplGetDay ( sal_uInt16 nIndex ) const; // Day input, 0 if no match
+ sal_uInt16 ImplGetMonth( sal_uInt16 nIndex ) const; // Month input, zero based return, NumberOfMonths if no match
+ sal_uInt16 ImplGetYear ( sal_uInt16 nIndex ); // Year input, 0 if no match
+
+ // Conversion of date to number
+ bool GetDateRef( double& fDays, // OUT: days diff to null date
+ sal_uInt16& nCounter ); // Count of date substrings
+
+ // Analyze start of string
+ bool ScanStartString( const OUString& rString );
+
+ // Analyze middle substring
+ bool ScanMidString( const OUString& rString,
+ sal_uInt16 nStringPos,
+ sal_uInt16 nCurNumCount );
+
+
+ // Analyze end of string
+ bool ScanEndString( const OUString& rString );
+
+ // Compare rString to substring of array indexed by nString
+ // nString == 0xFFFF => last substring
+ bool ScanStringNumFor( const OUString& rString,
+ sal_Int32 nPos,
+ sal_uInt16 nString,
+ bool bDontDetectNegation = false );
+
+ // if nMatchedAllStrings set nMatchedUsedAsReturn and return true,
+ // else do nothing and return false
+ bool MatchedReturn();
+
+ //! Be sure that the string to be analyzed is already converted to upper
+ //! case and if it contained native number digits that they are already
+ //! converted to ASCII.
+
+ // Main analyzing function
+ bool IsNumberFormatMain( const OUString& rString,
+ const SvNumberformat* pFormat); // number format to match against
+
+ /** Whether input matches locale dependent date acceptance pattern.
+
+ @param nStartPatternAt
+ The pattern matching starts at input particle
+ sStrArray[nStartPatternAt].
+
+ NOTE: once called the result is remembered, subsequent calls with
+ different parameters do not check for a match and do not lead to a
+ different result.
+ */
+ bool IsAcceptedDatePattern( sal_uInt16 nStartPatternAt );
+
+ /** Sets (not advances!) rPos to sStrArray[nParticle].getLength() if string
+ matches separator in pattern at nParticle.
+
+ Also detects a signed year case like M/D/-Y
+
+ @returns TRUE if separator matched.
+ */
+ bool SkipDatePatternSeparator( sal_uInt16 nParticle, sal_Int32 & rPos, bool & rSignedYear );
+
+ /** Returns count of numbers in accepted date pattern.
+ */
+ sal_uInt16 GetDatePatternNumbers();
+
+ /** Whether numeric string nNumber is of type cType in accepted date
+ pattern, 'Y', 'M' or 'D'.
+ */
+ bool IsDatePatternNumberOfType( sal_uInt16 nNumber, sal_Unicode cType );
+
+ /** Obtain order of accepted date pattern coded as, for example,
+ ('D'<<16)|('M'<<8)|'Y'
+ */
+ sal_uInt32 GetDatePatternOrder();
+
+ /** Obtain date format order, from accepted date pattern if available or
+ otherwise the locale's default order.
+
+ @param bFromFormatIfNoPattern
+ If <TRUE/> and no pattern was matched, obtain date order from
+ format if available, instead from format's or current locale.
+ */
+ DateOrder GetDateOrder( bool bFromFormatIfNoPattern = false );
+
+ /** Whether input may be an ISO 8601 date format, yyyy-mm-dd...
+
+ Checks if input has at least 3 numbers for yyyy-mm-dd and the separator
+ is '-', and 1<=mm<=12 and 1<=dd<=31.
+
+ @see nMayBeIso8601
+ */
+ bool MayBeIso8601();
+
+ /** Whether input may be a dd-month-yy format, with month name, not
+ number.
+
+ @see nMayBeMonthDate
+ */
+ bool MayBeMonthDate();
+
+ /** Whether input is acceptable as ISO 8601 date format in the current
+ NfEvalDateFormat setting.
+ */
+ bool IsAcceptableIso8601();
+
+ /** If month name in the middle was parsed, get the corresponding
+ LongDateOrder in GetDateRef().
+ */
+ LongDateOrder GetMiddleMonthLongDateOrder( bool bFormatTurn,
+ const LocaleDataWrapper* pLoc,
+ DateOrder eDateOrder );
+};
+
+#endif // INCLUDED_SVL_SOURCE_NUMBERS_ZFORFIND_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/numbers/zforlist.cxx b/svl/source/numbers/zforlist.cxx
new file mode 100644
index 0000000000..3b6c2bd7f1
--- /dev/null
+++ b/svl/source/numbers/zforlist.cxx
@@ -0,0 +1,4979 @@
+/* -*- 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 <officecfg/Office/Common.hxx>
+#include <svl/zforlist.hxx>
+#include <svl/currencytable.hxx>
+
+#include <comphelper/string.hxx>
+#include <o3tl/string_view.hxx>
+#include <tools/debug.hxx>
+#include <unotools/charclass.hxx>
+#include <unotools/configmgr.hxx>
+#include <i18nlangtag/mslangid.hxx>
+#include <unotools/localedatawrapper.hxx>
+#include <com/sun/star/i18n/KNumberFormatUsage.hpp>
+#include <com/sun/star/i18n/KNumberFormatType.hpp>
+#include <com/sun/star/i18n/FormatElement.hpp>
+#include <com/sun/star/i18n/Currency2.hpp>
+#include <com/sun/star/i18n/NumberFormatCode.hpp>
+#include <com/sun/star/i18n/XNumberFormatCode.hpp>
+#include <com/sun/star/i18n/NumberFormatMapper.hpp>
+#include <comphelper/processfactory.hxx>
+
+#include <osl/mutex.hxx>
+
+#include "zforscan.hxx"
+#include "zforfind.hxx"
+#include <svl/zformat.hxx>
+#include <i18npool/reservedconstants.hxx>
+
+#include <unotools/syslocaleoptions.hxx>
+#include <unotools/digitgroupingiterator.hxx>
+#include <rtl/strbuf.hxx>
+#include <rtl/math.hxx>
+
+#include <math.h>
+#include <limits>
+#include <memory>
+#include <set>
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::i18n;
+using namespace ::com::sun::star::lang;
+
+// Constants for type offsets per Country/Language (CL)
+#define ZF_STANDARD 0
+#define ZF_STANDARD_PERCENT 10
+#define ZF_STANDARD_CURRENCY 20
+#define ZF_STANDARD_DATE 30
+#define ZF_STANDARD_TIME 60
+#define ZF_STANDARD_DURATION (ZF_STANDARD_TIME + 4)
+#define ZF_STANDARD_DATETIME 70
+#define ZF_STANDARD_SCIENTIFIC 80
+#define ZF_STANDARD_FRACTION 85
+
+// Additional builtin formats, historically not fitting into the first 10 of a
+// category. Make sure it doesn't spill over to the next category.
+#define ZF_STANDARD_DATE_SYS_DMMMYYYY (ZF_STANDARD_DATE + 10)
+#define ZF_STANDARD_DATE_SYS_DMMMMYYYY (ZF_STANDARD_DATE + 11)
+#define ZF_STANDARD_DATE_SYS_NNDMMMYY (ZF_STANDARD_DATE + 12)
+#define ZF_STANDARD_DATE_SYS_NNDMMMMYYYY (ZF_STANDARD_DATE + 13)
+#define ZF_STANDARD_DATE_SYS_NNNNDMMMMYYYY (ZF_STANDARD_DATE + 14)
+#define ZF_STANDARD_DATE_DIN_DMMMYYYY (ZF_STANDARD_DATE + 15)
+#define ZF_STANDARD_DATE_DIN_DMMMMYYYY (ZF_STANDARD_DATE + 16)
+#define ZF_STANDARD_DATE_DIN_MMDD (ZF_STANDARD_DATE + 17)
+#define ZF_STANDARD_DATE_DIN_YYMMDD (ZF_STANDARD_DATE + 18)
+#define ZF_STANDARD_DATE_DIN_YYYYMMDD (ZF_STANDARD_DATE + 19)
+#define ZF_STANDARD_DATE_WW (ZF_STANDARD_DATE + 20)
+
+#define ZF_STANDARD_LOGICAL SV_MAX_COUNT_STANDARD_FORMATS-1 // 99
+#define ZF_STANDARD_TEXT SV_MAX_COUNT_STANDARD_FORMATS // 100
+
+static_assert( ZF_STANDARD_TEXT == NF_STANDARD_FORMAT_TEXT, "definition mismatch" );
+
+static_assert( NF_INDEX_TABLE_RESERVED_START == i18npool::nStopPredefinedFormatIndex,
+ "NfIndexTableOffset does not match i18npool's locale data predefined format code index bounds.");
+
+static_assert( NF_INDEX_TABLE_ENTRIES <= i18npool::nFirstFreeFormatIndex,
+ "NfIndexTableOffset crosses i18npool's locale data reserved format code index bounds.\n"
+ "You will need to adapt all locale data files defining index values "
+ "(formatIndex=\"...\") in that range and increment those and when done "
+ "adjust nFirstFreeFormatIndex in include/i18npool/reservedconstants.hxx");
+
+/* Locale that is set if an unknown locale (from another system) is loaded of
+ * legacy documents. Can not be SYSTEM because else, for example, a German "DM"
+ * (old currency) is recognized as a date (#53155#). */
+#define UNKNOWN_SUBSTITUTE LANGUAGE_ENGLISH_US
+
+// Same order as in include/svl/zforlist.hxx enum NfIndexTableOffset
+sal_uInt32 const indexTable[NF_INDEX_TABLE_ENTRIES] = {
+ ZF_STANDARD, // NF_NUMBER_STANDARD
+ ZF_STANDARD + 1, // NF_NUMBER_INT
+ ZF_STANDARD + 2, // NF_NUMBER_DEC2
+ ZF_STANDARD + 3, // NF_NUMBER_1000INT
+ ZF_STANDARD + 4, // NF_NUMBER_1000DEC2
+ ZF_STANDARD + 5, // NF_NUMBER_SYSTEM
+ ZF_STANDARD_SCIENTIFIC, // NF_SCIENTIFIC_000E000
+ ZF_STANDARD_SCIENTIFIC + 1, // NF_SCIENTIFIC_000E00
+ ZF_STANDARD_PERCENT, // NF_PERCENT_INT
+ ZF_STANDARD_PERCENT + 1, // NF_PERCENT_DEC2
+ ZF_STANDARD_FRACTION, // NF_FRACTION_1D
+ ZF_STANDARD_FRACTION + 1, // NF_FRACTION_2D
+ ZF_STANDARD_CURRENCY, // NF_CURRENCY_1000INT
+ ZF_STANDARD_CURRENCY + 1, // NF_CURRENCY_1000DEC2
+ ZF_STANDARD_CURRENCY + 2, // NF_CURRENCY_1000INT_RED
+ ZF_STANDARD_CURRENCY + 3, // NF_CURRENCY_1000DEC2_RED
+ ZF_STANDARD_CURRENCY + 4, // NF_CURRENCY_1000DEC2_CCC
+ ZF_STANDARD_CURRENCY + 5, // NF_CURRENCY_1000DEC2_DASHED
+ ZF_STANDARD_DATE, // NF_DATE_SYSTEM_SHORT
+ ZF_STANDARD_DATE + 8, // NF_DATE_SYSTEM_LONG
+ ZF_STANDARD_DATE + 7, // NF_DATE_SYS_DDMMYY
+ ZF_STANDARD_DATE + 6, // NF_DATE_SYS_DDMMYYYY
+ ZF_STANDARD_DATE + 9, // NF_DATE_SYS_DMMMYY
+ ZF_STANDARD_DATE_SYS_DMMMYYYY, // NF_DATE_SYS_DMMMYYYY
+ ZF_STANDARD_DATE_DIN_DMMMYYYY, // NF_DATE_DIN_DMMMYYYY
+ ZF_STANDARD_DATE_SYS_DMMMMYYYY, // NF_DATE_SYS_DMMMMYYYY
+ ZF_STANDARD_DATE_DIN_DMMMMYYYY, // NF_DATE_DIN_DMMMMYYYY
+ ZF_STANDARD_DATE_SYS_NNDMMMYY, // NF_DATE_SYS_NNDMMMYY
+ ZF_STANDARD_DATE + 1, // NF_DATE_DEF_NNDDMMMYY
+ ZF_STANDARD_DATE_SYS_NNDMMMMYYYY, // NF_DATE_SYS_NNDMMMMYYYY
+ ZF_STANDARD_DATE_SYS_NNNNDMMMMYYYY, // NF_DATE_SYS_NNNNDMMMMYYYY
+ ZF_STANDARD_DATE_DIN_MMDD, // NF_DATE_DIN_MMDD
+ ZF_STANDARD_DATE_DIN_YYMMDD, // NF_DATE_DIN_YYMMDD
+ ZF_STANDARD_DATE_DIN_YYYYMMDD, // NF_DATE_DIN_YYYYMMDD
+ ZF_STANDARD_DATE + 2, // NF_DATE_SYS_MMYY
+ ZF_STANDARD_DATE + 3, // NF_DATE_SYS_DDMMM
+ ZF_STANDARD_DATE + 4, // NF_DATE_MMMM
+ ZF_STANDARD_DATE + 5, // NF_DATE_QQJJ
+ ZF_STANDARD_DATE_WW, // NF_DATE_WW
+ ZF_STANDARD_TIME, // NF_TIME_HHMM
+ ZF_STANDARD_TIME + 1, // NF_TIME_HHMMSS
+ ZF_STANDARD_TIME + 2, // NF_TIME_HHMMAMPM
+ ZF_STANDARD_TIME + 3, // NF_TIME_HHMMSSAMPM
+ ZF_STANDARD_TIME + 4, // NF_TIME_HH_MMSS
+ ZF_STANDARD_TIME + 5, // NF_TIME_MMSS00
+ ZF_STANDARD_TIME + 6, // NF_TIME_HH_MMSS00
+ ZF_STANDARD_DATETIME, // NF_DATETIME_SYSTEM_SHORT_HHMM
+ ZF_STANDARD_DATETIME + 1, // NF_DATETIME_SYS_DDMMYYYY_HHMMSS
+ ZF_STANDARD_LOGICAL, // NF_BOOLEAN
+ ZF_STANDARD_TEXT, // NF_TEXT
+ ZF_STANDARD_DATETIME + 2, // NF_DATETIME_SYS_DDMMYYYY_HHMM
+ ZF_STANDARD_FRACTION + 2, // NF_FRACTION_3D
+ ZF_STANDARD_FRACTION + 3, // NF_FRACTION_2
+ ZF_STANDARD_FRACTION + 4, // NF_FRACTION_4
+ ZF_STANDARD_FRACTION + 5, // NF_FRACTION_8
+ ZF_STANDARD_FRACTION + 6, // NF_FRACTION_16
+ ZF_STANDARD_FRACTION + 7, // NF_FRACTION_10
+ ZF_STANDARD_FRACTION + 8, // NF_FRACTION_100
+ ZF_STANDARD_DATETIME + 3, // NF_DATETIME_ISO_YYYYMMDD_HHMMSS
+ ZF_STANDARD_DATETIME + 4, // NF_DATETIME_ISO_YYYYMMDD_HHMMSS000
+ ZF_STANDARD_DATETIME + 5, // NF_DATETIME_ISO_YYYYMMDDTHHMMSS
+ ZF_STANDARD_DATETIME + 6 // NF_DATETIME_ISO_YYYYMMDDTHHMMSS000
+};
+
+/**
+ instead of every number formatter being a listener we have a registry which
+ also handles one instance of the SysLocale options
+ */
+
+class SvNumberFormatterRegistry_Impl : public utl::ConfigurationListener
+{
+ std::vector< SvNumberFormatter* >
+ aFormatters;
+ SvtSysLocaleOptions aSysLocaleOptions;
+ LanguageType eSysLanguage;
+
+public:
+ SvNumberFormatterRegistry_Impl();
+ virtual ~SvNumberFormatterRegistry_Impl() override;
+
+ void Insert( SvNumberFormatter* pThis )
+ { aFormatters.push_back( pThis ); }
+
+ void Remove( SvNumberFormatter const * pThis );
+
+ size_t Count() const
+ { return aFormatters.size(); }
+
+ virtual void ConfigurationChanged( utl::ConfigurationBroadcaster*, ConfigurationHints ) override;
+};
+
+SvNumberFormatterRegistry_Impl::SvNumberFormatterRegistry_Impl()
+ : eSysLanguage(MsLangId::getRealLanguage( LANGUAGE_SYSTEM ))
+{
+ aSysLocaleOptions.AddListener( this );
+}
+
+
+SvNumberFormatterRegistry_Impl::~SvNumberFormatterRegistry_Impl()
+{
+ aSysLocaleOptions.RemoveListener( this );
+}
+
+
+void SvNumberFormatterRegistry_Impl::Remove( SvNumberFormatter const * pThis )
+{
+ auto it = std::find(aFormatters.begin(), aFormatters.end(), pThis);
+ if (it != aFormatters.end())
+ aFormatters.erase( it );
+}
+
+void SvNumberFormatterRegistry_Impl::ConfigurationChanged( utl::ConfigurationBroadcaster*,
+ ConfigurationHints nHint)
+{
+ ::osl::MutexGuard aGuard( SvNumberFormatter::GetGlobalMutex() );
+
+ if ( nHint & ConfigurationHints::Locale )
+ {
+ for(SvNumberFormatter* pFormatter : aFormatters)
+ pFormatter->ReplaceSystemCL( eSysLanguage );
+ eSysLanguage = MsLangId::getRealLanguage( LANGUAGE_SYSTEM );
+ }
+ if ( nHint & ConfigurationHints::Currency )
+ {
+ for(SvNumberFormatter* pFormatter : aFormatters)
+ pFormatter->ResetDefaultSystemCurrency();
+ }
+ if ( nHint & ConfigurationHints::DatePatterns )
+ {
+ for(SvNumberFormatter* pFormatter : aFormatters)
+ pFormatter->InvalidateDateAcceptancePatterns();
+ }
+}
+
+
+SvNumberFormatterRegistry_Impl* SvNumberFormatter::pFormatterRegistry = nullptr;
+volatile bool SvNumberFormatter::bCurrencyTableInitialized = false;
+namespace
+{
+ NfCurrencyTable& theCurrencyTable()
+ {
+ static NfCurrencyTable SINGLETON;
+ return SINGLETON;
+ }
+
+ NfCurrencyTable& theLegacyOnlyCurrencyTable()
+ {
+ static NfCurrencyTable SINGLETON;
+ return SINGLETON;
+ }
+
+ /** THE set of installed locales. */
+ std::set< LanguageType > theInstalledLocales;
+
+}
+sal_uInt16 SvNumberFormatter::nSystemCurrencyPosition = 0;
+
+// Whether BankSymbol (not CurrencySymbol!) is always at the end (1 $;-1 $) or
+// language dependent.
+#define NF_BANKSYMBOL_FIX_POSITION 1
+
+const sal_uInt16 SvNumberFormatter::UNLIMITED_PRECISION = ::std::numeric_limits<sal_uInt16>::max();
+const sal_uInt16 SvNumberFormatter::INPUTSTRING_PRECISION = ::std::numeric_limits<sal_uInt16>::max()-1;
+
+SvNumberFormatter::SvNumberFormatter( const Reference< XComponentContext >& rxContext,
+ LanguageType eLang )
+ : m_xContext( rxContext )
+ , maLanguageTag( eLang)
+{
+ ImpConstruct( eLang );
+}
+
+SvNumberFormatter::~SvNumberFormatter()
+{
+ {
+ ::osl::MutexGuard aGuard( GetGlobalMutex() );
+ pFormatterRegistry->Remove( this );
+ if ( !pFormatterRegistry->Count() )
+ {
+ delete pFormatterRegistry;
+ pFormatterRegistry = nullptr;
+ }
+ }
+
+ aFTable.clear();
+ ClearMergeTable();
+}
+
+
+void SvNumberFormatter::ImpConstruct( LanguageType eLang )
+{
+ if ( eLang == LANGUAGE_DONTKNOW )
+ {
+ eLang = UNKNOWN_SUBSTITUTE;
+ }
+ IniLnge = eLang;
+ ActLnge = eLang;
+ eEvalDateFormat = NF_EVALDATEFORMAT_INTL;
+ nDefaultSystemCurrencyFormat = NUMBERFORMAT_ENTRY_NOT_FOUND;
+
+ maLanguageTag.reset( eLang );
+ xCharClass.changeLocale( m_xContext, maLanguageTag );
+ xLocaleData.init( m_xContext, maLanguageTag );
+ xCalendar.init( m_xContext, maLanguageTag.getLocale() );
+ xTransliteration.init( m_xContext, eLang );
+ xNatNum.init( m_xContext );
+
+ // cached locale data items
+ const LocaleDataWrapper* pLoc = GetLocaleData();
+ aDecimalSep = pLoc->getNumDecimalSep();
+ aDecimalSepAlt = pLoc->getNumDecimalSepAlt();
+ aThousandSep = pLoc->getNumThousandSep();
+ aDateSep = pLoc->getDateSep();
+
+ pStringScanner.reset( new ImpSvNumberInputScan( this ) );
+ pFormatScanner.reset( new ImpSvNumberformatScan( this ) );
+ pFormatTable = nullptr;
+ MaxCLOffset = 0;
+ ImpGenerateFormats( 0, false ); // 0 .. 999 for initialized language formats
+ pMergeTable = nullptr;
+ bNoZero = false;
+
+ ::osl::MutexGuard aGuard( GetGlobalMutex() );
+ GetFormatterRegistry().Insert( this );
+}
+
+
+void SvNumberFormatter::ChangeIntl(LanguageType eLnge)
+{
+ ::osl::MutexGuard aGuard( GetInstanceMutex() );
+ if (ActLnge == eLnge)
+ return;
+
+ ActLnge = eLnge;
+
+ maLanguageTag.reset( eLnge );
+ xCharClass.changeLocale( m_xContext, maLanguageTag );
+ xLocaleData.changeLocale( maLanguageTag );
+ xCalendar.changeLocale( maLanguageTag.getLocale() );
+ xTransliteration.changeLocale( eLnge );
+
+ // cached locale data items, initialize BEFORE calling ChangeIntl below
+ const LocaleDataWrapper* pLoc = GetLocaleData();
+ aDecimalSep = pLoc->getNumDecimalSep();
+ aDecimalSepAlt = pLoc->getNumDecimalSepAlt();
+ aThousandSep = pLoc->getNumThousandSep();
+ aDateSep = pLoc->getDateSep();
+
+ pFormatScanner->ChangeIntl();
+ pStringScanner->ChangeIntl();
+}
+
+
+// static
+::osl::Mutex& SvNumberFormatter::GetGlobalMutex()
+{
+ // #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 osl::Mutex* persistentMutex(new osl::Mutex);
+
+ return *persistentMutex;
+}
+
+
+// static
+SvNumberFormatterRegistry_Impl& SvNumberFormatter::GetFormatterRegistry()
+{
+ ::osl::MutexGuard aGuard( GetGlobalMutex() );
+ if ( !pFormatterRegistry )
+ {
+ pFormatterRegistry = new SvNumberFormatterRegistry_Impl;
+ }
+ return *pFormatterRegistry;
+}
+
+void SvNumberFormatter::SetColorLink( const Link<sal_uInt16,Color*>& rColorTableCallBack )
+{
+ ::osl::MutexGuard aGuard( GetInstanceMutex() );
+ aColorLink = rColorTableCallBack;
+}
+
+Color* SvNumberFormatter::GetUserDefColor(sal_uInt16 nIndex)
+{
+ ::osl::MutexGuard aGuard( GetInstanceMutex() );
+ if( aColorLink.IsSet() )
+ {
+ return aColorLink.Call(nIndex);
+ }
+ else
+ {
+ return nullptr;
+ }
+}
+
+void SvNumberFormatter::ChangeNullDate(sal_uInt16 nDay,
+ sal_uInt16 nMonth,
+ sal_Int16 nYear)
+{
+ ::osl::MutexGuard aGuard( GetInstanceMutex() );
+ pFormatScanner->ChangeNullDate(nDay, nMonth, nYear);
+ pStringScanner->ChangeNullDate(nDay, nMonth, nYear);
+}
+
+const Date& SvNumberFormatter::GetNullDate() const
+{
+ ::osl::MutexGuard aGuard( GetInstanceMutex() );
+ return pFormatScanner->GetNullDate();
+}
+
+void SvNumberFormatter::ChangeStandardPrec(short nPrec)
+{
+ ::osl::MutexGuard aGuard( GetInstanceMutex() );
+ pFormatScanner->ChangeStandardPrec(nPrec);
+}
+
+void SvNumberFormatter::SetNoZero(bool bNZ)
+{
+ ::osl::MutexGuard aGuard( GetInstanceMutex() );
+ bNoZero = bNZ;
+}
+
+sal_uInt16 SvNumberFormatter::GetStandardPrec() const
+{
+ ::osl::MutexGuard aGuard( GetInstanceMutex() );
+ return pFormatScanner->GetStandardPrec();
+}
+
+bool SvNumberFormatter::GetNoZero() const
+{
+ ::osl::MutexGuard aGuard( GetInstanceMutex() );
+ return bNoZero;
+}
+
+void SvNumberFormatter::ReplaceSystemCL( LanguageType eOldLanguage )
+{
+ sal_uInt32 nCLOffset = ImpGetCLOffset( LANGUAGE_SYSTEM );
+ if ( nCLOffset > MaxCLOffset )
+ {
+ return ; // no SYSTEM entries to replace
+ }
+ const sal_uInt32 nMaxBuiltin = nCLOffset + SV_MAX_COUNT_STANDARD_FORMATS;
+ const sal_uInt32 nNextCL = nCLOffset + SV_COUNTRY_LANGUAGE_OFFSET;
+ sal_uInt32 nKey;
+
+ // remove old builtin formats
+ auto it = aFTable.find( nCLOffset );
+ while ( it != aFTable.end() && (nKey = it->first) >= nCLOffset && nKey <= nMaxBuiltin )
+ {
+ it = aFTable.erase(it);
+ }
+
+ // move additional and user defined to temporary table
+ SvNumberFormatTable aOldTable;
+ while ( it != aFTable.end() && (nKey = it->first) >= nCLOffset && nKey < nNextCL )
+ {
+ aOldTable[ nKey ] = it->second.release();
+ it = aFTable.erase(it);
+ }
+
+ // generate new old builtin formats
+ // reset ActLnge otherwise ChangeIntl() wouldn't switch if already LANGUAGE_SYSTEM
+ ActLnge = LANGUAGE_DONTKNOW;
+ ChangeIntl( LANGUAGE_SYSTEM );
+ ImpGenerateFormats( nCLOffset, true );
+
+ // convert additional and user defined from old system to new system
+ SvNumberformat* pStdFormat = GetFormatEntry( nCLOffset + ZF_STANDARD );
+ sal_uInt32 nLastKey = nMaxBuiltin;
+ pFormatScanner->SetConvertMode( eOldLanguage, LANGUAGE_SYSTEM, true , true);
+ while ( !aOldTable.empty() )
+ {
+ nKey = aOldTable.begin()->first;
+ if ( nLastKey < nKey )
+ {
+ nLastKey = nKey;
+ }
+ std::unique_ptr<SvNumberformat> pOldEntry(aOldTable.begin()->second);
+ aOldTable.erase( nKey );
+ OUString aString( pOldEntry->GetFormatstring() );
+
+ // Same as PutEntry() but assures key position even if format code is
+ // a duplicate. Also won't mix up any LastInsertKey.
+ ChangeIntl( eOldLanguage );
+ LanguageType eLge = eOldLanguage; // ConvertMode changes this
+ bool bCheck = false;
+ sal_Int32 nCheckPos = -1;
+ std::unique_ptr<SvNumberformat> pNewEntry(new SvNumberformat( aString, pFormatScanner.get(),
+ pStringScanner.get(), nCheckPos, eLge ));
+ if ( nCheckPos == 0 )
+ {
+ SvNumFormatType eCheckType = pNewEntry->GetType();
+ if ( eCheckType != SvNumFormatType::UNDEFINED )
+ {
+ pNewEntry->SetType( eCheckType | SvNumFormatType::DEFINED );
+ }
+ else
+ {
+ pNewEntry->SetType( SvNumFormatType::DEFINED );
+ }
+
+ if ( aFTable.emplace( nKey, std::move(pNewEntry) ).second )
+ {
+ bCheck = true;
+ }
+ }
+ DBG_ASSERT( bCheck, "SvNumberFormatter::ReplaceSystemCL: couldn't convert" );
+ }
+ pFormatScanner->SetConvertMode(false);
+ pStdFormat->SetLastInsertKey( sal_uInt16(nLastKey - nCLOffset), SvNumberformat::FormatterPrivateAccess() );
+
+ // append new system additional formats
+ css::uno::Reference< css::i18n::XNumberFormatCode > xNFC = i18n::NumberFormatMapper::create( m_xContext );
+ ImpGenerateAdditionalFormats( nCLOffset, xNFC, true );
+}
+
+const css::uno::Reference<css::uno::XComponentContext>& SvNumberFormatter::GetComponentContext() const
+{
+ return m_xContext;
+}
+
+const ImpSvNumberformatScan* SvNumberFormatter::GetFormatScanner() const { return pFormatScanner.get(); }
+
+const LanguageTag& SvNumberFormatter::GetLanguageTag() const { return maLanguageTag; }
+
+const ::utl::TransliterationWrapper* SvNumberFormatter::GetTransliteration() const
+{
+ return xTransliteration.get();
+}
+
+const CharClass* SvNumberFormatter::GetCharClass() const { return xCharClass.get(); }
+
+const LocaleDataWrapper* SvNumberFormatter::GetLocaleData() const { return xLocaleData.get(); }
+
+CalendarWrapper* SvNumberFormatter::GetCalendar() const { return xCalendar.get(); }
+
+const NativeNumberWrapper* SvNumberFormatter::GetNatNum() const { return xNatNum.get(); }
+
+const OUString& SvNumberFormatter::GetNumDecimalSep() const { return aDecimalSep; }
+
+const OUString& SvNumberFormatter::GetNumDecimalSepAlt() const { return aDecimalSepAlt; }
+
+const OUString& SvNumberFormatter::GetNumThousandSep() const { return aThousandSep; }
+
+const OUString& SvNumberFormatter::GetDateSep() const { return aDateSep; }
+
+bool SvNumberFormatter::IsDecimalSep( std::u16string_view rStr ) const
+{
+ if (rStr == GetNumDecimalSep())
+ return true;
+ if (GetNumDecimalSepAlt().isEmpty())
+ return false;
+ return rStr == GetNumDecimalSepAlt();
+}
+
+bool SvNumberFormatter::IsTextFormat(sal_uInt32 F_Index) const
+{
+ ::osl::MutexGuard aGuard( GetInstanceMutex() );
+ const SvNumberformat* pFormat = GetFormatEntry(F_Index);
+
+ return pFormat && pFormat->IsTextFormat();
+}
+
+bool SvNumberFormatter::PutEntry(OUString& rString,
+ sal_Int32& nCheckPos,
+ SvNumFormatType& nType,
+ sal_uInt32& nKey, // format key
+ LanguageType eLnge,
+ bool bReplaceBooleanEquivalent)
+{
+ ::osl::MutexGuard aGuard( GetInstanceMutex() );
+ nKey = 0;
+ if (rString.isEmpty()) // empty string
+ {
+ nCheckPos = 1; // -> Error
+ return false;
+ }
+ if (eLnge == LANGUAGE_DONTKNOW)
+ {
+ eLnge = IniLnge;
+ }
+ ChangeIntl(eLnge); // change locale if necessary
+ LanguageType eLge = eLnge; // non-const for ConvertMode
+ bool bCheck = false;
+ std::unique_ptr<SvNumberformat> p_Entry(new SvNumberformat(rString,
+ pFormatScanner.get(),
+ pStringScanner.get(),
+ nCheckPos,
+ eLge,
+ bReplaceBooleanEquivalent));
+
+ if (nCheckPos == 0) // Format ok
+ { // Type comparison:
+ SvNumFormatType eCheckType = p_Entry->GetType();
+ if ( eCheckType != SvNumFormatType::UNDEFINED)
+ {
+ p_Entry->SetType(eCheckType | SvNumFormatType::DEFINED);
+ nType = eCheckType;
+ }
+ else
+ {
+ p_Entry->SetType(SvNumFormatType::DEFINED);
+ nType = SvNumFormatType::DEFINED;
+ }
+
+ sal_uInt32 CLOffset = ImpGenerateCL(eLge); // create new standard formats if necessary
+
+ nKey = ImpIsEntry(p_Entry->GetFormatstring(),CLOffset, eLge);
+ if (nKey == NUMBERFORMAT_ENTRY_NOT_FOUND) // only in not yet present
+ {
+ SvNumberformat* pStdFormat = GetFormatEntry(CLOffset + ZF_STANDARD);
+ sal_uInt32 nPos = CLOffset + pStdFormat->GetLastInsertKey( SvNumberformat::FormatterPrivateAccess() );
+ if (nPos+1 - CLOffset >= SV_COUNTRY_LANGUAGE_OFFSET)
+ {
+ SAL_WARN( "svl.numbers", "SvNumberFormatter::PutEntry: too many formats for CL");
+ }
+ else if (!aFTable.emplace( nPos+1, std::move(p_Entry)).second)
+ {
+ SAL_WARN( "svl.numbers", "SvNumberFormatter::PutEntry: dup position");
+ }
+ else
+ {
+ bCheck = true;
+ nKey = nPos+1;
+ pStdFormat->SetLastInsertKey(static_cast<sal_uInt16>(nKey-CLOffset), SvNumberformat::FormatterPrivateAccess());
+ }
+ }
+ }
+ return bCheck;
+}
+
+bool SvNumberFormatter::PutandConvertEntry(OUString& rString,
+ sal_Int32& nCheckPos,
+ SvNumFormatType& nType,
+ sal_uInt32& nKey,
+ LanguageType eLnge,
+ LanguageType eNewLnge,
+ bool bConvertDateOrder,
+ bool bReplaceBooleanEquivalent )
+{
+ ::osl::MutexGuard aGuard( GetInstanceMutex() );
+ bool bRes;
+ if (eNewLnge == LANGUAGE_DONTKNOW)
+ {
+ eNewLnge = IniLnge;
+ }
+ pFormatScanner->SetConvertMode(eLnge, eNewLnge, false, bConvertDateOrder);
+ bRes = PutEntry(rString, nCheckPos, nType, nKey, eLnge, bReplaceBooleanEquivalent);
+ pFormatScanner->SetConvertMode(false);
+
+ if (bReplaceBooleanEquivalent && nCheckPos == 0 && nType == SvNumFormatType::DEFINED
+ && nKey != NUMBERFORMAT_ENTRY_NOT_FOUND)
+ {
+ // The boolean string formats are always "user defined" without any
+ // other type.
+ const SvNumberformat* pEntry = GetFormatEntry(nKey);
+ if (pEntry && pEntry->GetType() == SvNumFormatType::DEFINED)
+ {
+ // Replace boolean string format with Boolean in target locale, in
+ // case the source strings are the target locale's.
+ const OUString aSaveString = rString;
+ ChangeIntl(eNewLnge);
+ if (pFormatScanner->ReplaceBooleanEquivalent( rString))
+ {
+ const sal_Int32 nSaveCheckPos = nCheckPos;
+ const SvNumFormatType nSaveType = nType;
+ const sal_uInt32 nSaveKey = nKey;
+ const bool bTargetRes = PutEntry(rString, nCheckPos, nType, nKey, eNewLnge, false);
+ if (nCheckPos == 0 && nType == SvNumFormatType::LOGICAL && nKey != NUMBERFORMAT_ENTRY_NOT_FOUND)
+ {
+ bRes = bTargetRes;
+ }
+ else
+ {
+ SAL_WARN("svl.numbers", "SvNumberFormatter::PutandConvertEntry: can't scan boolean replacement");
+ // Live with the source boolean string format.
+ rString = aSaveString;
+ nCheckPos = nSaveCheckPos;
+ nType = nSaveType;
+ nKey = nSaveKey;
+ }
+ }
+ }
+ }
+ return bRes;
+}
+
+bool SvNumberFormatter::PutandConvertEntrySystem(OUString& rString,
+ sal_Int32& nCheckPos,
+ SvNumFormatType& nType,
+ sal_uInt32& nKey,
+ LanguageType eLnge,
+ LanguageType eNewLnge)
+{
+ ::osl::MutexGuard aGuard( GetInstanceMutex() );
+ bool bRes;
+ if (eNewLnge == LANGUAGE_DONTKNOW)
+ {
+ eNewLnge = IniLnge;
+ }
+ pFormatScanner->SetConvertMode(eLnge, eNewLnge, true, true);
+ bRes = PutEntry(rString, nCheckPos, nType, nKey, eLnge);
+ pFormatScanner->SetConvertMode(false);
+ return bRes;
+}
+
+sal_uInt32 SvNumberFormatter::GetIndexPuttingAndConverting( OUString & rString, LanguageType eLnge,
+ LanguageType eSysLnge, SvNumFormatType & rType,
+ bool & rNewInserted, sal_Int32 & rCheckPos )
+{
+ ::osl::MutexGuard aGuard( GetInstanceMutex() );
+ sal_uInt32 nKey = NUMBERFORMAT_ENTRY_NOT_FOUND;
+ rNewInserted = false;
+ rCheckPos = 0;
+
+ // #62389# empty format string (of Writer) => General standard format
+ if (rString.isEmpty())
+ {
+ // nothing
+ }
+ else if (eLnge == LANGUAGE_SYSTEM && eSysLnge != SvtSysLocale().GetLanguageTag().getLanguageType())
+ {
+ sal_uInt32 nOrig = GetEntryKey( rString, eSysLnge );
+ if (nOrig == NUMBERFORMAT_ENTRY_NOT_FOUND)
+ {
+ nKey = nOrig; // none available, maybe user-defined
+ }
+ else
+ {
+ nKey = GetFormatForLanguageIfBuiltIn( nOrig, SvtSysLocale().GetLanguageTag().getLanguageType() );
+ }
+ if (nKey == nOrig)
+ {
+ // Not a builtin format, convert.
+ // The format code string may get modified and adapted to the real
+ // language and wouldn't match eSysLnge anymore, do that on a copy.
+ OUString aTmp( rString);
+ rNewInserted = PutandConvertEntrySystem( aTmp, rCheckPos, rType,
+ nKey, eLnge, SvtSysLocale().GetLanguageTag().getLanguageType());
+ if (rCheckPos > 0)
+ {
+ SAL_WARN( "svl.numbers", "SvNumberFormatter::GetIndexPuttingAndConverting: bad format code string for current locale");
+ nKey = NUMBERFORMAT_ENTRY_NOT_FOUND;
+ }
+ }
+ }
+ else
+ {
+ nKey = GetEntryKey( rString, eLnge);
+ if (nKey == NUMBERFORMAT_ENTRY_NOT_FOUND)
+ {
+ rNewInserted = PutEntry( rString, rCheckPos, rType, nKey, eLnge);
+ if (rCheckPos > 0)
+ {
+ SAL_WARN( "svl.numbers", "SvNumberFormatter::GetIndexPuttingAndConverting: bad format code string for specified locale");
+ nKey = NUMBERFORMAT_ENTRY_NOT_FOUND;
+ }
+ }
+ }
+ if (nKey == NUMBERFORMAT_ENTRY_NOT_FOUND)
+ {
+ nKey = GetStandardIndex( eLnge);
+ }
+ rType = GetType( nKey);
+ // Convert any (!) old "automatic" currency format to new fixed currency
+ // default format.
+ if (rType & SvNumFormatType::CURRENCY)
+ {
+ const SvNumberformat* pFormat = GetEntry( nKey);
+ if (!pFormat->HasNewCurrency())
+ {
+ if (rNewInserted)
+ {
+ DeleteEntry( nKey); // don't leave trails of rubbish
+ rNewInserted = false;
+ }
+ nKey = GetStandardFormat( SvNumFormatType::CURRENCY, eLnge);
+ }
+ }
+ return nKey;
+}
+
+void SvNumberFormatter::DeleteEntry(sal_uInt32 nKey)
+{
+ ::osl::MutexGuard aGuard( GetInstanceMutex() );
+ aFTable.erase(nKey);
+}
+
+void SvNumberFormatter::GetUsedLanguages( std::vector<LanguageType>& rList )
+{
+ ::osl::MutexGuard aGuard( GetInstanceMutex() );
+ rList.clear();
+
+ sal_uInt32 nOffset = 0;
+ while (nOffset <= MaxCLOffset)
+ {
+ SvNumberformat* pFormat = GetFormatEntry(nOffset);
+ if (pFormat)
+ {
+ rList.push_back( pFormat->GetLanguage() );
+ }
+ nOffset += SV_COUNTRY_LANGUAGE_OFFSET;
+ }
+}
+
+
+void SvNumberFormatter::FillKeywordTable( NfKeywordTable& rKeywords,
+ LanguageType eLang )
+{
+ ::osl::MutexGuard aGuard( GetInstanceMutex() );
+ ChangeIntl( eLang );
+ const NfKeywordTable & rTable = pFormatScanner->GetKeywords();
+ for ( sal_uInt16 i = 0; i < NF_KEYWORD_ENTRIES_COUNT; ++i )
+ {
+ rKeywords[i] = rTable[i];
+ }
+}
+
+
+void SvNumberFormatter::FillKeywordTableForExcel( NfKeywordTable& rKeywords )
+{
+ ::osl::MutexGuard aGuard( GetInstanceMutex() );
+ FillKeywordTable( rKeywords, LANGUAGE_ENGLISH_US );
+
+ // Replace upper case "GENERAL" with proper case "General".
+ rKeywords[ NF_KEY_GENERAL ] = GetStandardName( LANGUAGE_ENGLISH_US );
+
+ // Excel or OOXML do not specify format code keywords case sensitivity,
+ // but given and writes them lower case. Using upper case even lead to an
+ // odd misrepresentation in iOS viewer and OSX Quicklook viewer that
+ // strangely use "D" and "DD" for "days since beginning of year", which is
+ // nowhere defined. See tdf#126773
+ // Use lower case for all date and time keywords where known. See OOXML
+ // ECMA-376-1:2016 18.8.31 numFmts (Number Formats)
+ rKeywords[ NF_KEY_MI ] = "m";
+ rKeywords[ NF_KEY_MMI ] = "mm";
+ rKeywords[ NF_KEY_M ] = "m";
+ rKeywords[ NF_KEY_MM ] = "mm";
+ rKeywords[ NF_KEY_MMM ] = "mmm";
+ rKeywords[ NF_KEY_MMMM ] = "mmmm";
+ rKeywords[ NF_KEY_MMMMM ] = "mmmmm";
+ rKeywords[ NF_KEY_H ] = "h";
+ rKeywords[ NF_KEY_HH ] = "hh";
+ rKeywords[ NF_KEY_S ] = "s";
+ rKeywords[ NF_KEY_SS ] = "ss";
+ /* XXX: not defined in OOXML: rKeywords[ NF_KEY_Q ] = "q"; */
+ /* XXX: not defined in OOXML: rKeywords[ NF_KEY_QQ ] = "qq"; */
+ rKeywords[ NF_KEY_D ] = "d";
+ rKeywords[ NF_KEY_DD ] = "dd";
+ rKeywords[ NF_KEY_DDD ] = "ddd";
+ rKeywords[ NF_KEY_DDDD ] = "dddd";
+ rKeywords[ NF_KEY_YY ] = "yy";
+ rKeywords[ NF_KEY_YYYY ] = "yyyy";
+ /* XXX: not defined in OOXML: rKeywords[ NF_KEY_AAA ] = "aaa"; */
+ /* XXX: not defined in OOXML: rKeywords[ NF_KEY_AAAA ] = "aaaa"; */
+ rKeywords[ NF_KEY_EC ] = "e";
+ rKeywords[ NF_KEY_EEC ] = "ee";
+ rKeywords[ NF_KEY_G ] = "g";
+ rKeywords[ NF_KEY_GG ] = "gg";
+ rKeywords[ NF_KEY_GGG ] = "ggg";
+ rKeywords[ NF_KEY_R ] = "r";
+ rKeywords[ NF_KEY_RR ] = "rr";
+ /* XXX: not defined in OOXML: rKeywords[ NF_KEY_WW ] = "ww"; */
+
+ // Remap codes unknown to Excel.
+ rKeywords[ NF_KEY_NN ] = "ddd";
+ rKeywords[ NF_KEY_NNN ] = "dddd";
+ // NNNN gets a separator appended in SvNumberformat::GetMappedFormatString()
+ rKeywords[ NF_KEY_NNNN ] = "dddd";
+ // Export the Thai T NatNum modifier. This must be uppercase for internal reasons.
+ rKeywords[ NF_KEY_THAI_T ] = "T";
+}
+
+
+static OUString lcl_buildBooleanStringFormat( SvNumberformat* pEntry )
+{
+ // Build Boolean number format, which needs non-zero and zero subformat
+ // codes with TRUE and FALSE strings.
+ const Color* pColor = nullptr;
+ OUString aFormatStr, aTemp;
+ pEntry->GetOutputString( 1.0, aTemp, &pColor );
+ aFormatStr += "\"" + aTemp + "\";\"" + aTemp + "\";\"";
+ pEntry->GetOutputString( 0.0, aTemp, &pColor );
+ aFormatStr += aTemp + "\"";
+ return aFormatStr;
+}
+
+
+OUString SvNumberFormatter::GetFormatStringForExcel( sal_uInt32 nKey, const NfKeywordTable& rKeywords,
+ SvNumberFormatter& rTempFormatter ) const
+{
+ ::osl::MutexGuard aGuard( GetInstanceMutex() );
+ OUString aFormatStr;
+ if (const SvNumberformat* pEntry = GetEntry( nKey))
+ {
+ if (pEntry->GetType() == SvNumFormatType::LOGICAL)
+ {
+ // Build a source locale dependent string boolean. This is
+ // expected when loading the document in the same locale or if
+ // several locales are used, but not for other system/default
+ // locales. You can't have both. We could force to English for all
+ // locales like below, but Excel would display English strings then
+ // even for the system locale matching this locale. YMMV.
+ aFormatStr = lcl_buildBooleanStringFormat( const_cast< SvNumberformat* >(pEntry));
+ }
+ else
+ {
+ bool bSystemLanguage = false;
+ LanguageType nLang = pEntry->GetLanguage();
+ if (nLang == LANGUAGE_SYSTEM)
+ {
+ bSystemLanguage = true;
+ nLang = SvtSysLocale().GetLanguageTag().getLanguageType();
+ }
+ if (nLang != LANGUAGE_ENGLISH_US)
+ {
+ sal_Int32 nCheckPos;
+ SvNumFormatType nType = SvNumFormatType::DEFINED;
+ sal_uInt32 nTempKey;
+ OUString aTemp( pEntry->GetFormatstring());
+ /* TODO: do we want bReplaceBooleanEquivalent=true in any case
+ * to write it as English string boolean? */
+ rTempFormatter.PutandConvertEntry( aTemp, nCheckPos, nType, nTempKey, nLang, LANGUAGE_ENGLISH_US,
+ false /*bConvertDateOrder*/, false /*bReplaceBooleanEquivalent*/);
+ SAL_WARN_IF( nCheckPos != 0, "svl.numbers",
+ "SvNumberFormatter::GetFormatStringForExcel - format code not convertible");
+ if (nTempKey != NUMBERFORMAT_ENTRY_NOT_FOUND)
+ pEntry = rTempFormatter.GetEntry( nTempKey);
+ }
+
+ if (pEntry)
+ {
+ if (pEntry->GetType() == SvNumFormatType::LOGICAL)
+ {
+ // This would be reached if bReplaceBooleanEquivalent was
+ // true and the source format is a string boolean like
+ // >"VRAI";"VRAI";"FAUX"< recognized as real boolean and
+ // properly converted. Then written as
+ // >"TRUE";"TRUE";"FALSE"<
+ aFormatStr = lcl_buildBooleanStringFormat( const_cast< SvNumberformat* >(pEntry));
+ }
+ else
+ {
+ // GetLocaleData() returns the current locale's data, so switch
+ // before (which doesn't do anything if it was the same locale
+ // already).
+ rTempFormatter.ChangeIntl( LANGUAGE_ENGLISH_US);
+ aFormatStr = pEntry->GetMappedFormatstring( rKeywords, *rTempFormatter.GetLocaleData(), nLang,
+ bSystemLanguage);
+ }
+ }
+ }
+ }
+ else
+ {
+ SAL_WARN("svl.numbers","SvNumberFormatter::GetFormatStringForExcel - format not found: " << nKey);
+ }
+
+ if (aFormatStr.isEmpty())
+ aFormatStr = "General";
+ return aFormatStr;
+}
+
+
+OUString SvNumberFormatter::GetKeyword( LanguageType eLnge, sal_uInt16 nIndex )
+{
+ ::osl::MutexGuard aGuard( GetInstanceMutex() );
+ ChangeIntl(eLnge);
+ const NfKeywordTable & rTable = pFormatScanner->GetKeywords();
+ if ( nIndex < NF_KEYWORD_ENTRIES_COUNT )
+ {
+ return rTable[nIndex];
+ }
+ SAL_WARN( "svl.numbers", "GetKeyword: invalid index");
+ return OUString();
+}
+
+
+OUString SvNumberFormatter::GetStandardName( LanguageType eLnge )
+{
+ ::osl::MutexGuard aGuard( GetInstanceMutex() );
+ ChangeIntl( eLnge );
+ return pFormatScanner->GetStandardName();
+}
+
+
+sal_uInt32 SvNumberFormatter::ImpGetCLOffset(LanguageType eLnge) const
+{
+ sal_uInt32 nOffset = 0;
+ while (nOffset <= MaxCLOffset)
+ {
+ const SvNumberformat* pFormat = GetFormatEntry(nOffset);
+ if (pFormat && pFormat->GetLanguage() == eLnge)
+ {
+ return nOffset;
+ }
+ nOffset += SV_COUNTRY_LANGUAGE_OFFSET;
+ }
+ return nOffset;
+}
+
+sal_uInt32 SvNumberFormatter::ImpIsEntry(std::u16string_view rString,
+ sal_uInt32 nCLOffset,
+ LanguageType eLnge)
+{
+ sal_uInt32 res = NUMBERFORMAT_ENTRY_NOT_FOUND;
+ auto it = aFTable.find( nCLOffset);
+ while ( res == NUMBERFORMAT_ENTRY_NOT_FOUND &&
+ it != aFTable.end() && it->second->GetLanguage() == eLnge )
+ {
+ if ( rString == it->second->GetFormatstring() )
+ {
+ res = it->first;
+ }
+ else
+ {
+ ++it;
+ }
+ }
+ return res;
+}
+
+
+SvNumberFormatTable& SvNumberFormatter::GetFirstEntryTable(
+ SvNumFormatType& eType,
+ sal_uInt32& FIndex,
+ LanguageType& rLnge)
+{
+ ::osl::MutexGuard aGuard( GetInstanceMutex() );
+ SvNumFormatType eTypetmp = eType;
+ if (eType == SvNumFormatType::ALL) // empty cell or don't care
+ {
+ rLnge = IniLnge;
+ }
+ else
+ {
+ SvNumberformat* pFormat = GetFormatEntry(FIndex);
+ if (!pFormat)
+ {
+ rLnge = IniLnge;
+ eType = SvNumFormatType::ALL;
+ eTypetmp = eType;
+ }
+ else
+ {
+ rLnge = pFormat->GetLanguage();
+ eType = pFormat->GetMaskedType();
+ if (eType == SvNumFormatType::ALL)
+ {
+ eType = SvNumFormatType::DEFINED;
+ eTypetmp = eType;
+ }
+ else if (eType == SvNumFormatType::DATETIME)
+ {
+ eTypetmp = eType;
+ eType = SvNumFormatType::DATE;
+ }
+ else
+ {
+ eTypetmp = eType;
+ }
+ }
+ }
+ ChangeIntl(rLnge);
+ return GetEntryTable(eTypetmp, FIndex, rLnge);
+}
+
+sal_uInt32 SvNumberFormatter::ImpGenerateCL( LanguageType eLnge )
+{
+ ChangeIntl(eLnge);
+ sal_uInt32 CLOffset = ImpGetCLOffset(ActLnge);
+ if (CLOffset > MaxCLOffset)
+ {
+ // new CL combination
+ if (LocaleDataWrapper::areChecksEnabled())
+ {
+ const LanguageTag& rLoadedLocale = xLocaleData->getLoadedLanguageTag();
+ if ( !rLoadedLocale.equals( maLanguageTag ) )
+ {
+ LocaleDataWrapper::outputCheckMessage( xLocaleData->appendLocaleInfo( u"SvNumberFormatter::ImpGenerateCL: locales don't match:" ));
+ }
+ // test XML locale data FormatElement entries
+ {
+ uno::Sequence< i18n::FormatElement > xSeq = xLocaleData->getAllFormats();
+ // A test for completeness of formatindex="0" ...
+ // formatindex="47" is not needed here since it is done in
+ // ImpGenerateFormats().
+
+ // Test for dupes of formatindex="..."
+ for ( sal_Int32 j = 0; j < xSeq.getLength(); j++ )
+ {
+ sal_Int16 nIdx = xSeq[j].formatIndex;
+ OUStringBuffer aDupes;
+ for ( sal_Int32 i = 0; i < xSeq.getLength(); i++ )
+ {
+ if ( i != j && xSeq[i].formatIndex == nIdx )
+ {
+ aDupes.append( OUString::number(i) + "(" + xSeq[i].formatKey + ") ");
+ }
+ }
+ if ( !aDupes.isEmpty() )
+ {
+ OUString aMsg = "XML locale data FormatElement formatindex dupe: "
+ + OUString::number(nIdx)
+ + "\nFormatElements: "
+ + OUString::number( j )
+ + "("
+ + xSeq[j].formatKey
+ + ") "
+ + aDupes;
+ LocaleDataWrapper::outputCheckMessage( xLocaleData->appendLocaleInfo( aMsg ));
+ }
+ }
+ }
+ }
+
+ MaxCLOffset += SV_COUNTRY_LANGUAGE_OFFSET;
+ ImpGenerateFormats( MaxCLOffset, false/*bNoAdditionalFormats*/ );
+ CLOffset = MaxCLOffset;
+ }
+ return CLOffset;
+}
+
+SvNumberFormatTable& SvNumberFormatter::ChangeCL(SvNumFormatType eType,
+ sal_uInt32& FIndex,
+ LanguageType eLnge)
+{
+ ::osl::MutexGuard aGuard( GetInstanceMutex() );
+ ImpGenerateCL(eLnge);
+ return GetEntryTable(eType, FIndex, ActLnge);
+}
+
+SvNumberFormatTable& SvNumberFormatter::GetEntryTable(
+ SvNumFormatType eType,
+ sal_uInt32& FIndex,
+ LanguageType eLnge)
+{
+ ::osl::MutexGuard aGuard( GetInstanceMutex() );
+ if ( pFormatTable )
+ {
+ pFormatTable->clear();
+ }
+ else
+ {
+ pFormatTable.reset( new SvNumberFormatTable );
+ }
+ ChangeIntl(eLnge);
+ sal_uInt32 CLOffset = ImpGetCLOffset(ActLnge);
+
+ // Might generate and insert a default format for the given type
+ // (e.g. currency) => has to be done before collecting formats.
+ sal_uInt32 nDefaultIndex = GetStandardFormat( eType, ActLnge );
+
+ auto it = aFTable.find( CLOffset);
+
+ if (eType == SvNumFormatType::ALL)
+ {
+ while (it != aFTable.end() && it->second->GetLanguage() == ActLnge)
+ { // copy all entries to output table
+ (*pFormatTable)[ it->first ] = it->second.get();
+ ++it;
+ }
+ }
+ else
+ {
+ while (it != aFTable.end() && it->second->GetLanguage() == ActLnge)
+ { // copy entries of queried type to output table
+ if ((it->second->GetType()) & eType)
+ (*pFormatTable)[ it->first ] = it->second.get();
+ ++it;
+ }
+ }
+ if ( !pFormatTable->empty() )
+ { // select default if queried format doesn't exist or queried type or
+ // language differ from existing format
+ SvNumberformat* pEntry = GetFormatEntry(FIndex);
+ if ( !pEntry || !(pEntry->GetType() & eType) || pEntry->GetLanguage() != ActLnge )
+ {
+ FIndex = nDefaultIndex;
+ }
+ }
+ return *pFormatTable;
+}
+
+bool SvNumberFormatter::IsNumberFormat(const OUString& sString,
+ sal_uInt32& F_Index,
+ double& fOutNumber,
+ SvNumInputOptions eInputOptions)
+{
+ ::osl::MutexGuard aGuard( GetInstanceMutex() );
+
+ SvNumFormatType FType;
+ // For the 0 General format directly use the init/system locale and avoid
+ // all overhead that is associated with a format passed to the scanner.
+ const SvNumberformat* pFormat = (F_Index == 0 ? nullptr : ImpSubstituteEntry( GetFormatEntry(F_Index)));
+ if (!pFormat)
+ {
+ ChangeIntl(IniLnge);
+ FType = SvNumFormatType::NUMBER;
+ }
+ else
+ {
+ FType = pFormat->GetMaskedType();
+ if (FType == SvNumFormatType::ALL)
+ {
+ FType = SvNumFormatType::DEFINED;
+ }
+ ChangeIntl(pFormat->GetLanguage());
+ // Avoid scanner overhead with the General format of any locale.
+ // These are never substituted above so safe to ignore.
+ if ((F_Index % SV_COUNTRY_LANGUAGE_OFFSET) == 0)
+ {
+ assert(FType == SvNumFormatType::NUMBER);
+ pFormat = nullptr;
+ }
+ }
+
+ bool res;
+ SvNumFormatType RType = FType;
+ if (RType == SvNumFormatType::TEXT)
+ {
+ res = false; // type text preset => no conversion to number
+ }
+ else
+ {
+ res = pStringScanner->IsNumberFormat(sString, RType, fOutNumber, pFormat, eInputOptions);
+ }
+ if (res && !IsCompatible(FType, RType)) // non-matching type
+ {
+ switch ( RType )
+ {
+ case SvNumFormatType::DATE :
+ // Preserve ISO 8601 input.
+ if (pStringScanner->CanForceToIso8601( DateOrder::Invalid))
+ {
+ F_Index = GetFormatIndex( NF_DATE_DIN_YYYYMMDD, ActLnge );
+ }
+ else
+ {
+ F_Index = GetStandardFormat( RType, ActLnge );
+ }
+ break;
+ case SvNumFormatType::TIME :
+ if ( pStringScanner->GetDecPos() )
+ {
+ // 100th seconds
+ if ( pStringScanner->GetNumericsCount() > 3 || fOutNumber < 0.0 )
+ {
+ F_Index = GetFormatIndex( NF_TIME_HH_MMSS00, ActLnge );
+ }
+ else
+ {
+ F_Index = GetFormatIndex( NF_TIME_MMSS00, ActLnge );
+ }
+ }
+ else if ( fOutNumber >= 1.0 || fOutNumber < 0.0 )
+ {
+ F_Index = GetFormatIndex( NF_TIME_HH_MMSS, ActLnge );
+ }
+ else
+ {
+ F_Index = GetStandardFormat( RType, ActLnge );
+ }
+ break;
+ case SvNumFormatType::DATETIME :
+ // Preserve ISO 8601 input.
+ if (pStringScanner->HasIso8601Tsep())
+ {
+ if (pStringScanner->GetDecPos())
+ F_Index = GetFormatIndex( NF_DATETIME_ISO_YYYYMMDDTHHMMSS000, ActLnge );
+ else
+ F_Index = GetFormatIndex( NF_DATETIME_ISO_YYYYMMDDTHHMMSS, ActLnge );
+ }
+ else if (pStringScanner->CanForceToIso8601( DateOrder::Invalid))
+ {
+ if (pStringScanner->GetDecPos())
+ F_Index = GetFormatIndex( NF_DATETIME_ISO_YYYYMMDD_HHMMSS000, ActLnge );
+ else
+ F_Index = GetFormatIndex( NF_DATETIME_ISO_YYYYMMDD_HHMMSS, ActLnge );
+ }
+ else
+ {
+ F_Index = GetStandardFormat( RType, ActLnge );
+ }
+ break;
+ default:
+ F_Index = GetStandardFormat( RType, ActLnge );
+ }
+ }
+ return res;
+}
+
+LanguageType SvNumberFormatter::GetLanguage() const
+{
+ ::osl::MutexGuard aGuard( GetInstanceMutex() );
+ return IniLnge;
+}
+
+// static
+bool SvNumberFormatter::IsCompatible(SvNumFormatType eOldType, SvNumFormatType eNewType)
+{
+ if (eOldType == eNewType)
+ {
+ return true;
+ }
+ else if (eOldType == SvNumFormatType::DEFINED)
+ {
+ return true;
+ }
+ else
+ {
+ switch (eNewType)
+ {
+ case SvNumFormatType::NUMBER:
+ switch (eOldType)
+ {
+ case SvNumFormatType::PERCENT:
+ case SvNumFormatType::CURRENCY:
+ case SvNumFormatType::SCIENTIFIC:
+ case SvNumFormatType::FRACTION:
+ case SvNumFormatType::DEFINED:
+ return true;
+ case SvNumFormatType::LOGICAL:
+ default:
+ return false;
+ }
+ break;
+ case SvNumFormatType::DATE:
+ switch (eOldType)
+ {
+ case SvNumFormatType::DATETIME:
+ return true;
+ default:
+ return false;
+ }
+ break;
+ case SvNumFormatType::TIME:
+ switch (eOldType)
+ {
+ case SvNumFormatType::DATETIME:
+ return true;
+ default:
+ return false;
+ }
+ break;
+ case SvNumFormatType::DATETIME:
+ switch (eOldType)
+ {
+ case SvNumFormatType::TIME:
+ case SvNumFormatType::DATE:
+ return true;
+ default:
+ return false;
+ }
+ break;
+ case SvNumFormatType::DURATION:
+ return false;
+ default:
+ return false;
+ }
+ }
+}
+
+
+sal_uInt32 SvNumberFormatter::ImpGetDefaultFormat( SvNumFormatType nType )
+{
+ sal_uInt32 CLOffset = ImpGetCLOffset( ActLnge );
+ sal_uInt32 nSearch;
+ switch( nType )
+ {
+ case SvNumFormatType::DATE:
+ nSearch = CLOffset + ZF_STANDARD_DATE;
+ break;
+ case SvNumFormatType::TIME:
+ nSearch = CLOffset + ZF_STANDARD_TIME;
+ break;
+ case SvNumFormatType::DATETIME:
+ nSearch = CLOffset + ZF_STANDARD_DATETIME;
+ break;
+ case SvNumFormatType::DURATION:
+ nSearch = CLOffset + ZF_STANDARD_DURATION;
+ break;
+ case SvNumFormatType::PERCENT:
+ nSearch = CLOffset + ZF_STANDARD_PERCENT;
+ break;
+ case SvNumFormatType::SCIENTIFIC:
+ nSearch = CLOffset + ZF_STANDARD_SCIENTIFIC;
+ break;
+ default:
+ nSearch = CLOffset + ZF_STANDARD;
+ }
+
+ DefaultFormatKeysMap::const_iterator it = aDefaultFormatKeys.find( nSearch);
+ sal_uInt32 nDefaultFormat = (it != aDefaultFormatKeys.end() ?
+ it->second : NUMBERFORMAT_ENTRY_NOT_FOUND);
+ if ( nDefaultFormat == NUMBERFORMAT_ENTRY_NOT_FOUND )
+ {
+ // look for a defined standard
+ sal_uInt32 nStopKey = CLOffset + SV_COUNTRY_LANGUAGE_OFFSET;
+ sal_uInt32 nKey(0);
+ auto it2 = aFTable.find( CLOffset );
+ while ( it2 != aFTable.end() && (nKey = it2->first ) >= CLOffset && nKey < nStopKey )
+ {
+ const SvNumberformat* pEntry = it2->second.get();
+ if ( pEntry->IsStandard() && (pEntry->GetMaskedType() == nType) )
+ {
+ nDefaultFormat = nKey;
+ break; // while
+ }
+ ++it2;
+ }
+
+ if ( nDefaultFormat == NUMBERFORMAT_ENTRY_NOT_FOUND )
+ { // none found, use old fixed standards
+ switch( nType )
+ {
+ case SvNumFormatType::DATE:
+ nDefaultFormat = CLOffset + ZF_STANDARD_DATE;
+ break;
+ case SvNumFormatType::TIME:
+ nDefaultFormat = CLOffset + ZF_STANDARD_TIME+1;
+ break;
+ case SvNumFormatType::DATETIME:
+ nDefaultFormat = CLOffset + ZF_STANDARD_DATETIME;
+ break;
+ case SvNumFormatType::DURATION:
+ nDefaultFormat = CLOffset + ZF_STANDARD_DURATION;
+ break;
+ case SvNumFormatType::PERCENT:
+ nDefaultFormat = CLOffset + ZF_STANDARD_PERCENT+1;
+ break;
+ case SvNumFormatType::SCIENTIFIC:
+ nDefaultFormat = CLOffset + ZF_STANDARD_SCIENTIFIC;
+ break;
+ default:
+ nDefaultFormat = CLOffset + ZF_STANDARD;
+ }
+ }
+ aDefaultFormatKeys[ nSearch ] = nDefaultFormat;
+ }
+ return nDefaultFormat;
+}
+
+
+sal_uInt32 SvNumberFormatter::GetStandardFormat( SvNumFormatType eType, LanguageType eLnge )
+{
+ ::osl::MutexGuard aGuard( GetInstanceMutex() );
+ if (eLnge == LANGUAGE_DONTKNOW)
+ {
+ eLnge = IniLnge;
+ }
+ sal_uInt32 CLOffset = ImpGenerateCL(eLnge);
+ switch(eType)
+ {
+ case SvNumFormatType::CURRENCY:
+ return ( eLnge == LANGUAGE_SYSTEM ) ? ImpGetDefaultSystemCurrencyFormat() : ImpGetDefaultCurrencyFormat();
+ case SvNumFormatType::DURATION :
+ return GetFormatIndex( NF_TIME_HH_MMSS, eLnge);
+ case SvNumFormatType::DATE:
+ case SvNumFormatType::TIME:
+ case SvNumFormatType::DATETIME:
+ case SvNumFormatType::PERCENT:
+ case SvNumFormatType::SCIENTIFIC:
+ return ImpGetDefaultFormat( eType );
+ case SvNumFormatType::FRACTION:
+ return CLOffset + ZF_STANDARD_FRACTION;
+ case SvNumFormatType::LOGICAL:
+ return CLOffset + ZF_STANDARD_LOGICAL;
+ case SvNumFormatType::TEXT:
+ return CLOffset + ZF_STANDARD_TEXT;
+ case SvNumFormatType::ALL:
+ case SvNumFormatType::DEFINED:
+ case SvNumFormatType::NUMBER:
+ case SvNumFormatType::UNDEFINED:
+ default:
+ return CLOffset + ZF_STANDARD;
+ }
+}
+
+bool SvNumberFormatter::IsSpecialStandardFormat( sal_uInt32 nFIndex,
+ LanguageType eLnge )
+{
+ ::osl::MutexGuard aGuard( GetInstanceMutex() );
+ return
+ nFIndex == GetFormatIndex( NF_TIME_MMSS00, eLnge ) ||
+ nFIndex == GetFormatIndex( NF_TIME_HH_MMSS00, eLnge ) ||
+ nFIndex == GetFormatIndex( NF_TIME_HH_MMSS, eLnge )
+ ;
+}
+
+sal_uInt32 SvNumberFormatter::GetStandardFormat( sal_uInt32 nFIndex, SvNumFormatType eType,
+ LanguageType eLnge )
+{
+ ::osl::MutexGuard aGuard( GetInstanceMutex() );
+ if ( IsSpecialStandardFormat( nFIndex, eLnge ) )
+ return nFIndex;
+ else
+ return GetStandardFormat( eType, eLnge );
+}
+
+sal_uInt32 SvNumberFormatter::GetTimeFormat( double fNumber, LanguageType eLnge, bool bForceDuration )
+{
+ ::osl::MutexGuard aGuard( GetInstanceMutex() );
+ bool bSign;
+ if ( fNumber < 0.0 )
+ {
+ bSign = true;
+ fNumber = -fNumber;
+ }
+ else
+ bSign = false;
+ double fSeconds = fNumber * 86400;
+ if ( floor( fSeconds + 0.5 ) * 100 != floor( fSeconds * 100 + 0.5 ) )
+ { // with 100th seconds
+ if ( bForceDuration || bSign || fSeconds >= 3600 )
+ return GetFormatIndex( NF_TIME_HH_MMSS00, eLnge );
+ else
+ return GetFormatIndex( NF_TIME_MMSS00, eLnge );
+ }
+ else
+ {
+ if ( bForceDuration || bSign || fNumber >= 1.0 )
+ return GetFormatIndex( NF_TIME_HH_MMSS, eLnge );
+ else
+ return GetStandardFormat( SvNumFormatType::TIME, eLnge );
+ }
+}
+
+sal_uInt32 SvNumberFormatter::GetStandardFormat( double fNumber, sal_uInt32 nFIndex,
+ SvNumFormatType eType, LanguageType eLnge )
+{
+ ::osl::MutexGuard aGuard( GetInstanceMutex() );
+ if ( IsSpecialStandardFormat( nFIndex, eLnge ) )
+ return nFIndex;
+
+ switch( eType )
+ {
+ case SvNumFormatType::DURATION :
+ return GetTimeFormat( fNumber, eLnge, true);
+ case SvNumFormatType::TIME :
+ return GetTimeFormat( fNumber, eLnge, false);
+ default:
+ return GetStandardFormat( eType, eLnge );
+ }
+}
+
+sal_uInt32 SvNumberFormatter::GuessDateTimeFormat( SvNumFormatType& rType, double fNumber, LanguageType eLnge )
+{
+ ::osl::MutexGuard aGuard( GetInstanceMutex() );
+ // Categorize the format according to the implementation of
+ // SvNumberFormatter::GetEditFormat(), making assumptions about what
+ // would be time only.
+ sal_uInt32 nRet;
+ if (0.0 <= fNumber && fNumber < 1.0)
+ {
+ // Clearly a time.
+ rType = SvNumFormatType::TIME;
+ nRet = GetTimeFormat( fNumber, eLnge, false);
+ }
+ else if (fabs( fNumber) * 24 < 0x7fff)
+ {
+ // Assuming duration within 32k hours or 3.7 years.
+ // This should be SvNumFormatType::DURATION instead, but the outer
+ // world can't cope with that.
+ rType = SvNumFormatType::TIME;
+ nRet = GetTimeFormat( fNumber, eLnge, true);
+ }
+ else if (rtl::math::approxFloor( fNumber) != fNumber)
+ {
+ // Date+Time.
+ rType = SvNumFormatType::DATETIME;
+ nRet = GetFormatIndex( NF_DATETIME_SYS_DDMMYYYY_HHMMSS, eLnge);
+ }
+ else
+ {
+ // Date only.
+ rType = SvNumFormatType::DATE;
+ nRet = GetFormatIndex( NF_DATE_SYS_DDMMYYYY, eLnge);
+ }
+ return nRet;
+}
+
+sal_uInt32 SvNumberFormatter::GetEditFormat( double fNumber, sal_uInt32 nFIndex,
+ SvNumFormatType eType,
+ SvNumberformat const * pFormat,
+ LanguageType eForLocale )
+{
+ ::osl::MutexGuard aGuard( GetInstanceMutex() );
+ const LanguageType eLang = (pFormat ? pFormat->GetLanguage() : LANGUAGE_SYSTEM);
+ if (eForLocale == LANGUAGE_DONTKNOW)
+ eForLocale = eLang;
+ sal_uInt32 nKey = nFIndex;
+ switch ( eType )
+ {
+ // #61619# always edit using 4-digit year
+ case SvNumFormatType::DATE :
+ {
+ // Preserve ISO 8601 format.
+ bool bIsoDate =
+ nFIndex == GetFormatIndex( NF_DATE_DIN_YYYYMMDD, eLang) ||
+ nFIndex == GetFormatIndex( NF_DATE_DIN_YYMMDD, eLang) ||
+ nFIndex == GetFormatIndex( NF_DATE_DIN_MMDD, eLang) ||
+ (pFormat && pFormat->IsIso8601( 0 ));
+ if (rtl::math::approxFloor( fNumber) != fNumber)
+ {
+ // fdo#34977 preserve time when editing even if only date was
+ // displayed.
+ if (bIsoDate)
+ nKey = GetFormatIndex( NF_DATETIME_ISO_YYYYMMDD_HHMMSS, eForLocale);
+ else
+ nKey = GetFormatIndex( NF_DATETIME_SYS_DDMMYYYY_HHMMSS, eForLocale );
+ }
+ else
+ {
+ if (bIsoDate)
+ nKey = GetFormatIndex( NF_DATE_ISO_YYYYMMDD, eForLocale);
+ else
+ nKey = GetFormatIndex( NF_DATE_SYS_DDMMYYYY, eForLocale );
+ }
+ }
+ break;
+ case SvNumFormatType::TIME :
+ if (fNumber < 0.0 || fNumber >= 1.0)
+ {
+ /* XXX NOTE: this is a purely arbitrary value within the limits
+ * of a signed 16-bit. 32k hours are 3.7 years ... or
+ * 1903-09-26 if date. */
+ if (fabs( fNumber) * 24 < 0x7fff)
+ nKey = GetTimeFormat( fNumber, eForLocale, true);
+ // Preserve duration, use [HH]:MM:SS instead of time.
+ else
+ nKey = GetFormatIndex( NF_DATETIME_SYS_DDMMYYYY_HHMMSS, eForLocale );
+ // Assume that a large value is a datetime with only time
+ // displayed.
+ }
+ else
+ nKey = GetStandardFormat( fNumber, nFIndex, eType, eForLocale );
+ break;
+ case SvNumFormatType::DURATION :
+ nKey = GetTimeFormat( fNumber, eForLocale, true);
+ break;
+ case SvNumFormatType::DATETIME :
+ if (nFIndex == GetFormatIndex( NF_DATETIME_ISO_YYYYMMDDTHHMMSS, eLang))
+ nKey = GetFormatIndex( NF_DATETIME_ISO_YYYYMMDDTHHMMSS, eForLocale );
+ else if (nFIndex == GetFormatIndex( NF_DATETIME_ISO_YYYYMMDDTHHMMSS000, eLang))
+ nKey = GetFormatIndex( NF_DATETIME_ISO_YYYYMMDDTHHMMSS000, eForLocale );
+ else if (nFIndex == GetFormatIndex( NF_DATETIME_ISO_YYYYMMDD_HHMMSS000, eLang))
+ nKey = GetFormatIndex( NF_DATETIME_ISO_YYYYMMDD_HHMMSS000, eForLocale );
+ else if (nFIndex == GetFormatIndex( NF_DATETIME_ISO_YYYYMMDD_HHMMSS, eLang) || (pFormat && pFormat->IsIso8601( 0 )))
+ nKey = GetFormatIndex( NF_DATETIME_ISO_YYYYMMDD_HHMMSS, eForLocale );
+ else
+ nKey = GetFormatIndex( NF_DATETIME_SYS_DDMMYYYY_HHMMSS, eForLocale );
+ break;
+ case SvNumFormatType::NUMBER:
+ nKey = GetStandardFormat( eType, eForLocale );
+ break;
+ default:
+ nKey = GetStandardFormat( fNumber, nFIndex, eType, eForLocale );
+ }
+ return nKey;
+}
+
+void SvNumberFormatter::GetInputLineString(const double& fOutNumber,
+ sal_uInt32 nFIndex,
+ OUString& sOutString,
+ bool bFiltering, bool bForceSystemLocale)
+{
+ ::osl::MutexGuard aGuard( GetInstanceMutex() );
+ const Color* pColor;
+ sal_uInt32 nRealKey = nFIndex;
+ SvNumberformat* pFormat = ImpSubstituteEntry( GetFormatEntry( nFIndex ), &nRealKey);
+ if (!pFormat)
+ {
+ pFormat = GetFormatEntry(ZF_STANDARD);
+ }
+
+ LanguageType eLang = pFormat->GetLanguage();
+ ChangeIntl( eLang );
+
+ SvNumFormatType eType = pFormat->GetMaskedType();
+ if (eType == SvNumFormatType::ALL)
+ {
+ // Mixed types in subformats, use first.
+ /* XXX we could choose a subformat according to fOutNumber and
+ * subformat conditions, but they may exist to suppress 0 or negative
+ * numbers so wouldn't be a safe bet. */
+ eType = pFormat->GetNumForInfoScannedType(0);
+ }
+ const SvNumFormatType eTypeOrig = eType;
+
+ sal_uInt16 nOldPrec = pFormatScanner->GetStandardPrec();
+ bool bPrecChanged = false;
+ if (eType == SvNumFormatType::NUMBER ||
+ eType == SvNumFormatType::PERCENT ||
+ eType == SvNumFormatType::CURRENCY ||
+ eType == SvNumFormatType::SCIENTIFIC ||
+ eType == SvNumFormatType::FRACTION)
+ {
+ if (eType != SvNumFormatType::PERCENT) // special treatment of % later
+ {
+ eType = SvNumFormatType::NUMBER;
+ }
+ ChangeStandardPrec(INPUTSTRING_PRECISION);
+ bPrecChanged = true;
+ }
+
+ // if bFiltering true keep the nRealKey format
+ if (!bFiltering)
+ {
+ sal_uInt32 nKey = GetEditFormat( fOutNumber, nRealKey, eType, pFormat,
+ bForceSystemLocale ? LANGUAGE_SYSTEM : LANGUAGE_DONTKNOW);
+ if (nKey != nRealKey)
+ {
+ pFormat = GetFormatEntry( nKey );
+ }
+ }
+ assert(pFormat);
+ if (pFormat)
+ {
+ if ( eType == SvNumFormatType::TIME && pFormat->GetFormatPrecision() )
+ {
+ ChangeStandardPrec(INPUTSTRING_PRECISION);
+ bPrecChanged = true;
+ }
+ pFormat->GetOutputString(fOutNumber, sOutString, &pColor);
+
+ // The #FMT error string must not be used for input as it would lead to
+ // data loss. This can happen for at least date(+time). Fall back to a
+ // last resort of plain number in the locale the formatter was
+ // constructed with.
+ if (eTypeOrig != SvNumFormatType::NUMBER && sOutString == ImpSvNumberformatScan::sErrStr)
+ {
+ pFormat = GetFormatEntry(ZF_STANDARD);
+ assert(pFormat);
+ if (pFormat)
+ {
+ ChangeStandardPrec(INPUTSTRING_PRECISION);
+ bPrecChanged = true;
+ pFormat->GetOutputString(fOutNumber, sOutString, &pColor);
+ }
+ }
+ assert(sOutString != ImpSvNumberformatScan::sErrStr);
+ }
+ if (bPrecChanged)
+ {
+ ChangeStandardPrec(nOldPrec);
+ }
+}
+
+void SvNumberFormatter::GetOutputString(const OUString& sString,
+ sal_uInt32 nFIndex,
+ OUString& sOutString,
+ const Color** ppColor,
+ bool bUseStarFormat )
+{
+ ::osl::MutexGuard aGuard( GetInstanceMutex() );
+ SvNumberformat* pFormat = GetFormatEntry( nFIndex );
+ // ImpSubstituteEntry() is unnecessary here because so far only numeric
+ // (time and date) are substituted.
+ if (!pFormat)
+ {
+ pFormat = GetFormatEntry(ZF_STANDARD_TEXT);
+ }
+ if (!pFormat->IsTextFormat() && !pFormat->HasTextFormat())
+ {
+ *ppColor = nullptr;
+ sOutString = sString;
+ }
+ else
+ {
+ ChangeIntl(pFormat->GetLanguage());
+ if ( bUseStarFormat )
+ {
+ pFormat->SetStarFormatSupport( true );
+ }
+ pFormat->GetOutputString(sString, sOutString, ppColor);
+ if ( bUseStarFormat )
+ {
+ pFormat->SetStarFormatSupport( false );
+ }
+ }
+}
+
+void SvNumberFormatter::GetOutputString(const double& fOutNumber,
+ sal_uInt32 nFIndex,
+ OUString& sOutString,
+ const Color** ppColor,
+ bool bUseStarFormat )
+{
+ ::osl::MutexGuard aGuard( GetInstanceMutex() );
+ if (bNoZero && fOutNumber == 0.0)
+ {
+ sOutString.clear();
+ return;
+ }
+ SvNumberformat* pFormat = ImpSubstituteEntry( GetFormatEntry( nFIndex ));
+ if (!pFormat)
+ pFormat = GetFormatEntry(ZF_STANDARD);
+ ChangeIntl(pFormat->GetLanguage());
+ if ( bUseStarFormat )
+ pFormat->SetStarFormatSupport( true );
+ pFormat->GetOutputString(fOutNumber, sOutString, ppColor);
+ if ( bUseStarFormat )
+ pFormat->SetStarFormatSupport( false );
+}
+
+bool SvNumberFormatter::GetPreviewString(const OUString& sFormatString,
+ double fPreviewNumber,
+ OUString& sOutString,
+ const Color** ppColor,
+ LanguageType eLnge,
+ bool bUseStarFormat )
+{
+ ::osl::MutexGuard aGuard( GetInstanceMutex() );
+ if (sFormatString.isEmpty()) // no empty string
+ {
+ return false;
+ }
+ if (eLnge == LANGUAGE_DONTKNOW)
+ {
+ eLnge = IniLnge;
+ }
+ ChangeIntl(eLnge); // change locale if necessary
+ eLnge = ActLnge;
+ sal_Int32 nCheckPos = -1;
+ OUString sTmpString = sFormatString;
+ SvNumberformat aEntry(sTmpString,
+ pFormatScanner.get(),
+ pStringScanner.get(),
+ nCheckPos,
+ eLnge);
+ if (nCheckPos == 0) // String ok
+ {
+ sal_uInt32 CLOffset = ImpGenerateCL(eLnge); // create new standard formats if necessary
+ sal_uInt32 nKey = ImpIsEntry(aEntry.GetFormatstring(),CLOffset, eLnge);
+ if (nKey != NUMBERFORMAT_ENTRY_NOT_FOUND) // already present
+ {
+ GetOutputString(fPreviewNumber, nKey, sOutString, ppColor, bUseStarFormat);
+ }
+ else
+ {
+ if ( bUseStarFormat )
+ {
+ aEntry.SetStarFormatSupport( true );
+ }
+ aEntry.GetOutputString(fPreviewNumber, sOutString, ppColor);
+ if ( bUseStarFormat )
+ {
+ aEntry.SetStarFormatSupport( false );
+ }
+ }
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+bool SvNumberFormatter::GetPreviewStringGuess( const OUString& sFormatString,
+ double fPreviewNumber,
+ OUString& sOutString,
+ const Color** ppColor,
+ LanguageType eLnge )
+{
+ ::osl::MutexGuard aGuard( GetInstanceMutex() );
+ if (sFormatString.isEmpty()) // no empty string
+ {
+ return false;
+ }
+ if (eLnge == LANGUAGE_DONTKNOW)
+ {
+ eLnge = IniLnge;
+ }
+ ChangeIntl( eLnge );
+ eLnge = ActLnge;
+ bool bEnglish = (eLnge == LANGUAGE_ENGLISH_US);
+
+ OUString aFormatStringUpper( xCharClass->uppercase( sFormatString ) );
+ sal_uInt32 nCLOffset = ImpGenerateCL( eLnge );
+ sal_uInt32 nKey = ImpIsEntry( aFormatStringUpper, nCLOffset, eLnge );
+ if ( nKey != NUMBERFORMAT_ENTRY_NOT_FOUND )
+ {
+ // Target format present
+ GetOutputString( fPreviewNumber, nKey, sOutString, ppColor );
+ return true;
+ }
+
+ std::optional<SvNumberformat> pEntry;
+ sal_Int32 nCheckPos = -1;
+ OUString sTmpString;
+
+ if ( bEnglish )
+ {
+ sTmpString = sFormatString;
+ pEntry.emplace( sTmpString, pFormatScanner.get(),
+ pStringScanner.get(), nCheckPos, eLnge );
+ }
+ else
+ {
+ nCLOffset = ImpGenerateCL( LANGUAGE_ENGLISH_US );
+ nKey = ImpIsEntry( aFormatStringUpper, nCLOffset, LANGUAGE_ENGLISH_US );
+ bool bEnglishFormat = (nKey != NUMBERFORMAT_ENTRY_NOT_FOUND);
+
+ // Try English -> other or convert english to other
+ LanguageType eFormatLang = LANGUAGE_ENGLISH_US;
+ pFormatScanner->SetConvertMode( LANGUAGE_ENGLISH_US, eLnge, false, false);
+ sTmpString = sFormatString;
+ pEntry.emplace( sTmpString, pFormatScanner.get(),
+ pStringScanner.get(), nCheckPos, eFormatLang );
+ pFormatScanner->SetConvertMode( false );
+ ChangeIntl( eLnge );
+
+ if ( !bEnglishFormat )
+ {
+ if ( nCheckPos != 0 || xTransliteration->isEqual( sFormatString,
+ pEntry->GetFormatstring() ) )
+ {
+ // other Format
+ // Force locale's keywords.
+ pFormatScanner->ChangeIntl( ImpSvNumberformatScan::KeywordLocalization::LocaleLegacy );
+ sTmpString = sFormatString;
+ pEntry.emplace( sTmpString, pFormatScanner.get(),
+ pStringScanner.get(), nCheckPos, eLnge );
+ }
+ else
+ {
+ // verify english
+ sal_Int32 nCheckPos2 = -1;
+ // try other --> english
+ eFormatLang = eLnge;
+ pFormatScanner->SetConvertMode( eLnge, LANGUAGE_ENGLISH_US, false, false);
+ sTmpString = sFormatString;
+ SvNumberformat aEntry2( sTmpString, pFormatScanner.get(),
+ pStringScanner.get(), nCheckPos2, eFormatLang );
+ pFormatScanner->SetConvertMode( false );
+ ChangeIntl( eLnge );
+ if ( nCheckPos2 == 0 && !xTransliteration->isEqual( sFormatString,
+ aEntry2.GetFormatstring() ) )
+ {
+ // other Format
+ // Force locale's keywords.
+ pFormatScanner->ChangeIntl( ImpSvNumberformatScan::KeywordLocalization::LocaleLegacy );
+ sTmpString = sFormatString;
+ pEntry.emplace( sTmpString, pFormatScanner.get(),
+ pStringScanner.get(), nCheckPos, eLnge );
+ }
+ }
+ }
+ }
+
+ if (nCheckPos == 0) // String ok
+ {
+ ImpGenerateCL( eLnge ); // create new standard formats if necessary
+ pEntry->GetOutputString( fPreviewNumber, sOutString, ppColor );
+ return true;
+ }
+ return false;
+}
+
+bool SvNumberFormatter::GetPreviewString( const OUString& sFormatString,
+ const OUString& sPreviewString,
+ OUString& sOutString,
+ const Color** ppColor,
+ LanguageType eLnge )
+{
+ ::osl::MutexGuard aGuard( GetInstanceMutex() );
+ if (sFormatString.isEmpty()) // no empty string
+ {
+ return false;
+ }
+ if (eLnge == LANGUAGE_DONTKNOW)
+ {
+ eLnge = IniLnge;
+ }
+ ChangeIntl(eLnge); // switch if needed
+ eLnge = ActLnge;
+ sal_Int32 nCheckPos = -1;
+ OUString sTmpString = sFormatString;
+ SvNumberformat aEntry( sTmpString,
+ pFormatScanner.get(),
+ pStringScanner.get(),
+ nCheckPos,
+ eLnge);
+ if (nCheckPos == 0) // String ok
+ {
+ // May have to create standard formats for this locale.
+ sal_uInt32 CLOffset = ImpGenerateCL(eLnge);
+ sal_uInt32 nKey = ImpIsEntry( aEntry.GetFormatstring(), CLOffset, eLnge);
+ if (nKey != NUMBERFORMAT_ENTRY_NOT_FOUND) // already present
+ {
+ GetOutputString( sPreviewString, nKey, sOutString, ppColor);
+ }
+ else
+ {
+ // If the format is valid but not a text format and does not
+ // include a text subformat, an empty string would result. Same as
+ // in SvNumberFormatter::GetOutputString()
+ if (aEntry.IsTextFormat() || aEntry.HasTextFormat())
+ {
+ aEntry.GetOutputString( sPreviewString, sOutString, ppColor);
+ }
+ else
+ {
+ *ppColor = nullptr;
+ sOutString = sPreviewString;
+ }
+ }
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+sal_uInt32 SvNumberFormatter::TestNewString(const OUString& sFormatString,
+ LanguageType eLnge)
+{
+ ::osl::MutexGuard aGuard( GetInstanceMutex() );
+ if (sFormatString.isEmpty()) // no empty string
+ {
+ return NUMBERFORMAT_ENTRY_NOT_FOUND;
+ }
+ if (eLnge == LANGUAGE_DONTKNOW)
+ {
+ eLnge = IniLnge;
+ }
+ ChangeIntl(eLnge); // change locale if necessary
+ eLnge = ActLnge;
+ sal_uInt32 nRes;
+ sal_Int32 nCheckPos = -1;
+ OUString sTmpString = sFormatString;
+ SvNumberformat aEntry(sTmpString,
+ pFormatScanner.get(),
+ pStringScanner.get(),
+ nCheckPos,
+ eLnge);
+ if (nCheckPos == 0) // String ok
+ {
+ sal_uInt32 CLOffset = ImpGenerateCL(eLnge); // create new standard formats if necessary
+ nRes = ImpIsEntry(aEntry.GetFormatstring(),CLOffset, eLnge);
+ // already present?
+ }
+ else
+ {
+ nRes = NUMBERFORMAT_ENTRY_NOT_FOUND;
+ }
+ return nRes;
+}
+
+SvNumberformat* SvNumberFormatter::ImpInsertFormat( const css::i18n::NumberFormatCode& rCode,
+ sal_uInt32 nPos, bool bAfterChangingSystemCL,
+ sal_Int16 nOrgIndex )
+{
+ SAL_WARN_IF( NF_INDEX_TABLE_RESERVED_START <= rCode.Index && rCode.Index < NF_INDEX_TABLE_ENTRIES,
+ "svl.numbers", "i18npool locale '" << maLanguageTag.getBcp47() <<
+ "' uses reserved formatIndex value " << rCode.Index << ", next free: " << NF_INDEX_TABLE_ENTRIES <<
+ " Please see description in include/svl/zforlist.hxx at end of enum NfIndexTableOffset");
+ assert( (rCode.Index < NF_INDEX_TABLE_RESERVED_START || NF_INDEX_TABLE_ENTRIES <= rCode.Index) &&
+ "reserved formatIndex, see warning above");
+
+ OUString aCodeStr( rCode.Code );
+ if ( rCode.Index < NF_INDEX_TABLE_RESERVED_START &&
+ rCode.Usage == css::i18n::KNumberFormatUsage::CURRENCY &&
+ rCode.Index != NF_CURRENCY_1000DEC2_CCC )
+ { // strip surrounding [$...] on automatic currency
+ if ( aCodeStr.indexOf( "[$" ) >= 0)
+ aCodeStr = SvNumberformat::StripNewCurrencyDelimiters( aCodeStr );
+ else
+ {
+ if (LocaleDataWrapper::areChecksEnabled() &&
+ rCode.Index != NF_CURRENCY_1000DEC2_CCC )
+ {
+ OUString aMsg = "SvNumberFormatter::ImpInsertFormat: no [$...] on currency format code, index " +
+ OUString::number( rCode.Index) +
+ ":\n" +
+ rCode.Code;
+ LocaleDataWrapper::outputCheckMessage( xLocaleData->appendLocaleInfo( aMsg));
+ }
+ }
+ }
+ sal_Int32 nCheckPos = 0;
+ std::unique_ptr<SvNumberformat> pFormat(new SvNumberformat(aCodeStr,
+ pFormatScanner.get(),
+ pStringScanner.get(),
+ nCheckPos,
+ ActLnge));
+ if (nCheckPos != 0)
+ {
+ if (LocaleDataWrapper::areChecksEnabled())
+ {
+ OUString aMsg = "SvNumberFormatter::ImpInsertFormat: bad format code, index " +
+ OUString::number( rCode.Index ) +
+ "\n" +
+ rCode.Code;
+ LocaleDataWrapper::outputCheckMessage( xLocaleData->appendLocaleInfo( aMsg));
+ }
+ return nullptr;
+ }
+ if ( rCode.Index >= NF_INDEX_TABLE_RESERVED_START )
+ {
+ sal_uInt32 nCLOffset = nPos - (nPos % SV_COUNTRY_LANGUAGE_OFFSET);
+ sal_uInt32 nKey = ImpIsEntry( aCodeStr, nCLOffset, ActLnge );
+ if ( nKey != NUMBERFORMAT_ENTRY_NOT_FOUND )
+ {
+ // If bAfterChangingSystemCL there will definitely be some dups,
+ // don't cry then.
+ if (LocaleDataWrapper::areChecksEnabled() && !bAfterChangingSystemCL)
+ {
+ // Test for duplicate indexes in locale data.
+ switch ( nOrgIndex )
+ {
+ // These may be dups of integer versions for locales where
+ // currencies have no decimals like Italian Lira.
+ case NF_CURRENCY_1000DEC2 : // NF_CURRENCY_1000INT
+ case NF_CURRENCY_1000DEC2_RED : // NF_CURRENCY_1000INT_RED
+ case NF_CURRENCY_1000DEC2_DASHED : // NF_CURRENCY_1000INT_RED
+ break;
+ default:
+ {
+ OUString aMsg = "SvNumberFormatter::ImpInsertFormat: dup format code, index "
+ + OUString::number( rCode.Index )
+ + "\n"
+ + rCode.Code;
+ LocaleDataWrapper::outputCheckMessage( xLocaleData->appendLocaleInfo( aMsg));
+ }
+ }
+ }
+ return nullptr;
+ }
+ else if ( nPos - nCLOffset >= SV_COUNTRY_LANGUAGE_OFFSET )
+ {
+ if (LocaleDataWrapper::areChecksEnabled())
+ {
+ OUString aMsg = "SvNumberFormatter::ImpInsertFormat: too many format codes, index "
+ + OUString::number( rCode.Index )
+ + "\n"
+ + rCode.Code;
+ LocaleDataWrapper::outputCheckMessage( xLocaleData->appendLocaleInfo( aMsg));
+ }
+ return nullptr;
+ }
+ }
+ auto pFormat2 = pFormat.get();
+ if ( !aFTable.emplace( nPos, std::move(pFormat) ).second )
+ {
+ if (LocaleDataWrapper::areChecksEnabled())
+ {
+ OUString aMsg = "ImpInsertFormat: can't insert number format key pos: "
+ + OUString::number( nPos )
+ + ", code index "
+ + OUString::number( rCode.Index )
+ + "\n"
+ + rCode.Code;
+ LocaleDataWrapper::outputCheckMessage( xLocaleData->appendLocaleInfo( aMsg));
+ }
+ else
+ {
+ SAL_WARN( "svl.numbers", "SvNumberFormatter::ImpInsertFormat: dup position");
+ }
+ return nullptr;
+ }
+ if ( rCode.Default )
+ pFormat2->SetStandard();
+ if ( !rCode.DefaultName.isEmpty() )
+ pFormat2->SetComment( rCode.DefaultName );
+ return pFormat2;
+}
+
+void SvNumberFormatter::GetFormatSpecialInfo(sal_uInt32 nFormat,
+ bool& bThousand,
+ bool& IsRed,
+ sal_uInt16& nPrecision,
+ sal_uInt16& nLeadingCnt)
+
+{
+ ::osl::MutexGuard aGuard( GetInstanceMutex() );
+ SvNumberformat* pFormat = GetFormatEntry( nFormat );
+ if (pFormat)
+ pFormat->GetFormatSpecialInfo(bThousand, IsRed,
+ nPrecision, nLeadingCnt);
+ else
+ {
+ bThousand = false;
+ IsRed = false;
+ nPrecision = pFormatScanner->GetStandardPrec();
+ nLeadingCnt = 0;
+ }
+}
+
+sal_uInt16 SvNumberFormatter::GetFormatPrecision( sal_uInt32 nFormat ) const
+{
+ ::osl::MutexGuard aGuard( GetInstanceMutex() );
+ const SvNumberformat* pFormat = GetFormatEntry( nFormat );
+ if ( pFormat )
+ return pFormat->GetFormatPrecision();
+ else
+ return pFormatScanner->GetStandardPrec();
+}
+
+sal_uInt16 SvNumberFormatter::GetFormatIntegerDigits( sal_uInt32 nFormat ) const
+{
+ ::osl::MutexGuard aGuard( GetInstanceMutex() );
+ const SvNumberformat* pFormat = GetFormatEntry( nFormat );
+ if ( pFormat )
+ return pFormat->GetFormatIntegerDigits();
+ else
+ return 1;
+}
+
+OUString SvNumberFormatter::GetFormatDecimalSep( sal_uInt32 nFormat ) const
+{
+ ::osl::MutexGuard aGuard( GetInstanceMutex() );
+ const SvNumberformat* pFormat = GetFormatEntry(nFormat);
+ if (!pFormat)
+ {
+ return GetNumDecimalSep();
+ }
+ return GetLangDecimalSep( pFormat->GetLanguage());
+}
+
+OUString SvNumberFormatter::GetLangDecimalSep( LanguageType nLang ) const
+{
+ ::osl::MutexGuard aGuard( GetInstanceMutex() );
+ if (nLang == ActLnge)
+ {
+ return GetNumDecimalSep();
+ }
+ OUString aRet;
+ LanguageType eSaveLang = xLocaleData.getCurrentLanguage();
+ if (nLang == eSaveLang)
+ {
+ aRet = xLocaleData->getNumDecimalSep();
+ }
+ else
+ {
+ LanguageTag aSaveLocale( xLocaleData->getLanguageTag() );
+ const_cast<SvNumberFormatter*>(this)->xLocaleData.changeLocale( LanguageTag( nLang));
+ aRet = xLocaleData->getNumDecimalSep();
+ const_cast<SvNumberFormatter*>(this)->xLocaleData.changeLocale( aSaveLocale );
+ }
+ return aRet;
+}
+
+bool SvNumberFormatter::IsNatNum12( sal_uInt32 nFIndex ) const
+{
+ ::osl::MutexGuard aGuard( GetInstanceMutex() );
+ const SvNumberformat* pFormat = GetFormatEntry( nFIndex );
+
+ return pFormat && pFormat->GetNatNumModifierString().startsWith( "[NatNum12" );
+}
+
+sal_uInt32 SvNumberFormatter::GetFormatSpecialInfo( const OUString& rFormatString,
+ bool& bThousand, bool& IsRed, sal_uInt16& nPrecision,
+ sal_uInt16& nLeadingCnt, LanguageType eLnge )
+
+{
+ ::osl::MutexGuard aGuard( GetInstanceMutex() );
+ if (eLnge == LANGUAGE_DONTKNOW)
+ {
+ eLnge = IniLnge;
+ }
+ ChangeIntl(eLnge); // change locale if necessary
+ eLnge = ActLnge;
+ OUString aTmpStr( rFormatString );
+ sal_Int32 nCheckPos = 0;
+ SvNumberformat aFormat( aTmpStr, pFormatScanner.get(),
+ pStringScanner.get(), nCheckPos, eLnge );
+ if ( nCheckPos == 0 )
+ {
+ aFormat.GetFormatSpecialInfo( bThousand, IsRed, nPrecision, nLeadingCnt );
+ }
+ else
+ {
+ bThousand = false;
+ IsRed = false;
+ nPrecision = pFormatScanner->GetStandardPrec();
+ nLeadingCnt = 0;
+ }
+ return nCheckPos;
+}
+
+OUString SvNumberFormatter::GetCalcCellReturn( sal_uInt32 nFormat ) const
+{
+ ::osl::MutexGuard aGuard( GetInstanceMutex() );
+ const SvNumberformat* pFormat = GetFormatEntry( nFormat );
+ if (!pFormat)
+ return "G";
+
+ OUString aStr;
+ bool bAppendPrec = true;
+ sal_uInt16 nPrec, nLeading;
+ bool bThousand, bIsRed;
+ pFormat->GetFormatSpecialInfo( bThousand, bIsRed, nPrec, nLeading );
+
+ switch (pFormat->GetMaskedType())
+ {
+ case SvNumFormatType::NUMBER:
+ if (bThousand)
+ aStr = ",";
+ else
+ aStr = "F";
+ break;
+ case SvNumFormatType::CURRENCY:
+ aStr = "C";
+ break;
+ case SvNumFormatType::SCIENTIFIC:
+ aStr = "S";
+ break;
+ case SvNumFormatType::PERCENT:
+ aStr = "P";
+ break;
+ default:
+ {
+ bAppendPrec = false;
+ switch (GetIndexTableOffset( nFormat ))
+ {
+ case NF_DATE_SYSTEM_SHORT:
+ case NF_DATE_SYS_DMMMYY:
+ case NF_DATE_SYS_DDMMYY:
+ case NF_DATE_SYS_DDMMYYYY:
+ case NF_DATE_SYS_DMMMYYYY:
+ case NF_DATE_DIN_DMMMYYYY:
+ case NF_DATE_SYS_DMMMMYYYY:
+ case NF_DATE_DIN_DMMMMYYYY: aStr = "D1"; break;
+ case NF_DATE_SYS_DDMMM: aStr = "D2"; break;
+ case NF_DATE_SYS_MMYY: aStr = "D3"; break;
+ case NF_DATETIME_SYSTEM_SHORT_HHMM:
+ case NF_DATETIME_SYS_DDMMYYYY_HHMM:
+ case NF_DATETIME_SYS_DDMMYYYY_HHMMSS:
+ aStr = "D4"; break;
+ case NF_DATE_DIN_MMDD: aStr = "D5"; break;
+ case NF_TIME_HHMMSSAMPM: aStr = "D6"; break;
+ case NF_TIME_HHMMAMPM: aStr = "D7"; break;
+ case NF_TIME_HHMMSS: aStr = "D8"; break;
+ case NF_TIME_HHMM: aStr = "D9"; break;
+ default: aStr = "G";
+ }
+ }
+ }
+
+ if (bAppendPrec)
+ aStr += OUString::number(nPrec);
+
+ if (pFormat->GetColor( 1 ))
+ aStr += "-"; // negative color
+
+ /* FIXME: this probably should not match on literal strings and only be
+ * performed on number or currency formats, but it is what Calc originally
+ * implemented. */
+ if (pFormat->GetFormatstring().indexOf('(') != -1)
+ aStr += "()";
+
+ return aStr;
+}
+
+sal_Int32 SvNumberFormatter::ImpGetFormatCodeIndex(
+ css::uno::Sequence< css::i18n::NumberFormatCode >& rSeq,
+ const NfIndexTableOffset nTabOff )
+{
+ auto pSeq = std::find_if(std::cbegin(rSeq), std::cend(rSeq),
+ [nTabOff](const css::i18n::NumberFormatCode& rCode) { return rCode.Index == nTabOff; });
+ if (pSeq != std::cend(rSeq))
+ return static_cast<sal_Int32>(std::distance(std::cbegin(rSeq), pSeq));
+ if (LocaleDataWrapper::areChecksEnabled() && (nTabOff < NF_CURRENCY_START
+ || NF_CURRENCY_END < nTabOff || nTabOff == NF_CURRENCY_1000INT
+ || nTabOff == NF_CURRENCY_1000INT_RED
+ || nTabOff == NF_CURRENCY_1000DEC2_CCC))
+ { // currency entries with decimals might not exist, e.g. Italian Lira
+ OUString aMsg = "SvNumberFormatter::ImpGetFormatCodeIndex: not found: "
+ + OUString::number( nTabOff );
+ LocaleDataWrapper::outputCheckMessage( xLocaleData->appendLocaleInfo(aMsg));
+ }
+ if ( rSeq.hasElements() )
+ {
+ // look for a preset default
+ pSeq = std::find_if(std::cbegin(rSeq), std::cend(rSeq),
+ [](const css::i18n::NumberFormatCode& rCode) { return rCode.Default; });
+ if (pSeq != std::cend(rSeq))
+ return static_cast<sal_Int32>(std::distance(std::cbegin(rSeq), pSeq));
+ // currencies are special, not all format codes must exist, but all
+ // builtin number format key index positions must have a format assigned
+ if ( NF_CURRENCY_START <= nTabOff && nTabOff <= NF_CURRENCY_END )
+ {
+ // look for a format with decimals
+ pSeq = std::find_if(std::cbegin(rSeq), std::cend(rSeq),
+ [](const css::i18n::NumberFormatCode& rCode) { return rCode.Index == NF_CURRENCY_1000DEC2; });
+ if (pSeq != std::cend(rSeq))
+ return static_cast<sal_Int32>(std::distance(std::cbegin(rSeq), pSeq));
+ // last resort: look for a format without decimals
+ pSeq = std::find_if(std::cbegin(rSeq), std::cend(rSeq),
+ [](const css::i18n::NumberFormatCode& rCode) { return rCode.Index == NF_CURRENCY_1000INT; });
+ if (pSeq != std::cend(rSeq))
+ return static_cast<sal_Int32>(std::distance(std::cbegin(rSeq), pSeq));
+ }
+ }
+ else
+ { // we need at least _some_ format
+ rSeq = { css::i18n::NumberFormatCode() };
+ rSeq.getArray()[0].Code = "0" + GetNumDecimalSep() + "############";
+ }
+ return 0;
+}
+
+
+void SvNumberFormatter::ImpAdjustFormatCodeDefault(
+ css::i18n::NumberFormatCode * pFormatArr,
+ sal_Int32 nCnt )
+{
+ if ( !nCnt )
+ return;
+ if (LocaleDataWrapper::areChecksEnabled())
+ {
+ // check the locale data for correctness
+ OUStringBuffer aMsg;
+ sal_Int32 nElem, nShort, nMedium, nLong, nShortDef, nMediumDef, nLongDef;
+ nShort = nMedium = nLong = nShortDef = nMediumDef = nLongDef = -1;
+ for ( nElem = 0; nElem < nCnt; nElem++ )
+ {
+ switch ( pFormatArr[nElem].Type )
+ {
+ case i18n::KNumberFormatType::SHORT :
+ nShort = nElem;
+ break;
+ case i18n::KNumberFormatType::MEDIUM :
+ nMedium = nElem;
+ break;
+ case i18n::KNumberFormatType::LONG :
+ nLong = nElem;
+ break;
+ default:
+ aMsg.append("unknown type");
+ }
+ if ( pFormatArr[nElem].Default )
+ {
+ switch ( pFormatArr[nElem].Type )
+ {
+ case i18n::KNumberFormatType::SHORT :
+ if ( nShortDef != -1 )
+ aMsg.append("dupe short type default");
+ nShortDef = nElem;
+ break;
+ case i18n::KNumberFormatType::MEDIUM :
+ if ( nMediumDef != -1 )
+ aMsg.append("dupe medium type default");
+ nMediumDef = nElem;
+ break;
+ case i18n::KNumberFormatType::LONG :
+ if ( nLongDef != -1 )
+ aMsg.append("dupe long type default");
+ nLongDef = nElem;
+ break;
+ }
+ }
+ if (!aMsg.isEmpty())
+ {
+ aMsg.insert(0, "SvNumberFormatter::ImpAdjustFormatCodeDefault: ");
+ aMsg.append("\nXML locale data FormatElement formatindex: "
+ + OUString::number(static_cast<sal_Int32>(pFormatArr[nElem].Index)));
+ LocaleDataWrapper::outputCheckMessage(xLocaleData->appendLocaleInfo(aMsg));
+ aMsg.setLength(0);
+ }
+ }
+ if ( nShort != -1 && nShortDef == -1 )
+ aMsg.append("no short type default ");
+ if ( nMedium != -1 && nMediumDef == -1 )
+ aMsg.append("no medium type default ");
+ if ( nLong != -1 && nLongDef == -1 )
+ aMsg.append("no long type default ");
+ if (!aMsg.isEmpty())
+ {
+ aMsg.insert(0, "SvNumberFormatter::ImpAdjustFormatCodeDefault: ");
+ aMsg.append("\nXML locale data FormatElement group of: ");
+ LocaleDataWrapper::outputCheckMessage(
+ xLocaleData->appendLocaleInfo(Concat2View(aMsg + pFormatArr[0].NameID)));
+ aMsg.setLength(0);
+ }
+ }
+ // find the default (medium preferred, then long) and reset all other defaults
+ sal_Int32 nElem, nDef, nMedium;
+ nDef = nMedium = -1;
+ for ( nElem = 0; nElem < nCnt; nElem++ )
+ {
+ if ( pFormatArr[nElem].Default )
+ {
+ switch ( pFormatArr[nElem].Type )
+ {
+ case i18n::KNumberFormatType::MEDIUM :
+ nDef = nMedium = nElem;
+ break;
+ case i18n::KNumberFormatType::LONG :
+ if ( nMedium == -1 )
+ nDef = nElem;
+ [[fallthrough]];
+ default:
+ if ( nDef == -1 )
+ nDef = nElem;
+ pFormatArr[nElem].Default = false;
+ }
+ }
+ }
+ if ( nDef == -1 )
+ nDef = 0;
+ pFormatArr[nDef].Default = true;
+}
+
+SvNumberformat* SvNumberFormatter::GetFormatEntry( sal_uInt32 nKey )
+{
+ auto it = aFTable.find( nKey);
+ if (it != aFTable.end())
+ return it->second.get();
+ return nullptr;
+}
+
+const SvNumberformat* SvNumberFormatter::GetFormatEntry( sal_uInt32 nKey ) const
+{
+ return GetEntry( nKey);
+}
+
+const SvNumberformat* SvNumberFormatter::GetEntry( sal_uInt32 nKey ) const
+{
+ ::osl::MutexGuard aGuard( GetInstanceMutex() );
+ auto it = aFTable.find( nKey);
+ if (it != aFTable.end())
+ return it->second.get();
+ return nullptr;
+}
+
+const SvNumberformat* SvNumberFormatter::GetSubstitutedEntry( sal_uInt32 nKey, sal_uInt32 & o_rNewKey ) const
+{
+ ::osl::MutexGuard aGuard( GetInstanceMutex() );
+ // A tad ugly, but GetStandardFormat() and GetFormatIndex() in
+ // ImpSubstituteEntry() may have to add the LANGUAGE_SYSTEM formats if not
+ // already present (which in practice most times they are).
+ SvNumberFormatter* pThis = const_cast<SvNumberFormatter*>(this);
+ return pThis->ImpSubstituteEntry( pThis->GetFormatEntry( nKey), &o_rNewKey);
+}
+
+SvNumberformat* SvNumberFormatter::ImpSubstituteEntry( SvNumberformat* pFormat, sal_uInt32 * o_pRealKey )
+{
+ if (!pFormat || !pFormat->IsSubstituted())
+ return pFormat;
+
+ // XXX NOTE: substitution can not be done in GetFormatEntry() as otherwise
+ // to be substituted formats would "vanish", i.e. from the number formatter
+ // dialog or when exporting to Excel.
+
+ sal_uInt32 nKey;
+ if (pFormat->IsSystemTimeFormat())
+ /* TODO: should we have NF_TIME_SYSTEM for consistency? */
+ nKey = GetStandardFormat( SvNumFormatType::TIME, LANGUAGE_SYSTEM);
+ else if (pFormat->IsSystemLongDateFormat())
+ /* TODO: either that above, or have a long option for GetStandardFormat() */
+ nKey = GetFormatIndex( NF_DATE_SYSTEM_LONG, LANGUAGE_SYSTEM);
+ else
+ return pFormat;
+
+ if (o_pRealKey)
+ *o_pRealKey = nKey;
+ auto it = aFTable.find( nKey);
+ return it == aFTable.end() ? nullptr : it->second.get();
+}
+
+void SvNumberFormatter::ImpGenerateFormats( sal_uInt32 CLOffset, bool bNoAdditionalFormats )
+{
+ bool bOldConvertMode = pFormatScanner->GetConvertMode();
+ if (bOldConvertMode)
+ {
+ pFormatScanner->SetConvertMode(false); // switch off for this function
+ }
+
+ css::lang::Locale aLocale = GetLanguageTag().getLocale();
+ css::uno::Reference< css::i18n::XNumberFormatCode > xNFC = i18n::NumberFormatMapper::create( m_xContext );
+ sal_Int32 nIdx;
+
+ // Number
+ uno::Sequence< i18n::NumberFormatCode > aFormatSeq = xNFC->getAllFormatCode( i18n::KNumberFormatUsage::FIXED_NUMBER, aLocale );
+ ImpAdjustFormatCodeDefault( aFormatSeq.getArray(), aFormatSeq.getLength() );
+
+ // General
+ nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_NUMBER_STANDARD );
+ SvNumberformat* pStdFormat = ImpInsertFormat( aFormatSeq[nIdx],
+ CLOffset + ZF_STANDARD /* NF_NUMBER_STANDARD */ );
+ if (pStdFormat)
+ {
+ // This is _the_ standard format.
+ if (LocaleDataWrapper::areChecksEnabled() && pStdFormat->GetType() != SvNumFormatType::NUMBER)
+ {
+ LocaleDataWrapper::outputCheckMessage( xLocaleData->
+ appendLocaleInfo( u"SvNumberFormatter::ImpGenerateFormats: General format not NUMBER"));
+ }
+ pStdFormat->SetType( SvNumFormatType::NUMBER );
+ pStdFormat->SetStandard();
+ pStdFormat->SetLastInsertKey( SV_MAX_COUNT_STANDARD_FORMATS, SvNumberformat::FormatterPrivateAccess() );
+ }
+ else
+ {
+ if (LocaleDataWrapper::areChecksEnabled())
+ {
+ LocaleDataWrapper::outputCheckMessage( xLocaleData->
+ appendLocaleInfo( u"SvNumberFormatter::ImpGenerateFormats: General format not insertable, nothing will work"));
+ }
+ }
+
+ {
+ // Boolean
+ OUString aFormatCode = pFormatScanner->GetBooleanString();
+ sal_Int32 nCheckPos = 0;
+
+ std::unique_ptr<SvNumberformat> pNewFormat(new SvNumberformat( aFormatCode, pFormatScanner.get(),
+ pStringScanner.get(), nCheckPos, ActLnge ));
+ pNewFormat->SetType(SvNumFormatType::LOGICAL);
+ pNewFormat->SetStandard();
+ if ( !aFTable.emplace(CLOffset + ZF_STANDARD_LOGICAL /* NF_BOOLEAN */,
+ std::move(pNewFormat)).second )
+ {
+ SAL_WARN( "svl.numbers", "SvNumberFormatter::ImpGenerateFormats: dup position Boolean");
+ }
+
+ // Text
+ aFormatCode = "@";
+ pNewFormat.reset(new SvNumberformat( aFormatCode, pFormatScanner.get(),
+ pStringScanner.get(), nCheckPos, ActLnge ));
+ pNewFormat->SetType(SvNumFormatType::TEXT);
+ pNewFormat->SetStandard();
+ if ( !aFTable.emplace( CLOffset + ZF_STANDARD_TEXT /* NF_TEXT */,
+ std::move(pNewFormat)).second )
+ {
+ SAL_WARN( "svl.numbers", "SvNumberFormatter::ImpGenerateFormats: dup position Text");
+ }
+ }
+
+ // 0
+ nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_NUMBER_INT );
+ ImpInsertFormat( aFormatSeq[nIdx],
+ CLOffset + ZF_STANDARD+1 /* NF_NUMBER_INT */ );
+
+ // 0.00
+ nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_NUMBER_DEC2 );
+ ImpInsertFormat( aFormatSeq[nIdx],
+ CLOffset + ZF_STANDARD+2 /* NF_NUMBER_DEC2 */ );
+
+ // #,##0
+ nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_NUMBER_1000INT );
+ ImpInsertFormat( aFormatSeq[nIdx],
+ CLOffset + ZF_STANDARD+3 /* NF_NUMBER_1000INT */ );
+
+ // #,##0.00
+ nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_NUMBER_1000DEC2 );
+ ImpInsertFormat( aFormatSeq[nIdx],
+ CLOffset + ZF_STANDARD+4 /* NF_NUMBER_1000DEC2 */ );
+
+ // #.##0,00 System country/language dependent
+ nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_NUMBER_SYSTEM );
+ ImpInsertFormat( aFormatSeq[nIdx],
+ CLOffset + ZF_STANDARD+5 /* NF_NUMBER_SYSTEM */ );
+
+
+ // Percent number
+ aFormatSeq = xNFC->getAllFormatCode( i18n::KNumberFormatUsage::PERCENT_NUMBER, aLocale );
+ ImpAdjustFormatCodeDefault( aFormatSeq.getArray(), aFormatSeq.getLength() );
+
+ // 0%
+ nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_PERCENT_INT );
+ ImpInsertFormat( aFormatSeq[nIdx],
+ CLOffset + ZF_STANDARD_PERCENT /* NF_PERCENT_INT */ );
+
+ // 0.00%
+ nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_PERCENT_DEC2 );
+ ImpInsertFormat( aFormatSeq[nIdx],
+ CLOffset + ZF_STANDARD_PERCENT+1 /* NF_PERCENT_DEC2 */ );
+
+
+ // Currency. NO default standard option! Default is determined of locale
+ // data default currency and format is generated if needed.
+ aFormatSeq = xNFC->getAllFormatCode( i18n::KNumberFormatUsage::CURRENCY, aLocale );
+ if (LocaleDataWrapper::areChecksEnabled())
+ {
+ // though no default desired here, test for correctness of locale data
+ ImpAdjustFormatCodeDefault( aFormatSeq.getArray(), aFormatSeq.getLength() );
+ }
+
+ // #,##0
+ nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_CURRENCY_1000INT );
+ // Just copy the format, to avoid COW on sequence after each possible reallocation
+ auto aFormat = aFormatSeq[nIdx];
+ aFormat.Default = false;
+ ImpInsertFormat( aFormat,
+ CLOffset + ZF_STANDARD_CURRENCY /* NF_CURRENCY_1000INT */ );
+
+ // #,##0.00
+ nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_CURRENCY_1000DEC2 );
+ aFormat = aFormatSeq[nIdx];
+ aFormat.Default = false;
+ ImpInsertFormat( aFormat,
+ CLOffset + ZF_STANDARD_CURRENCY+1 /* NF_CURRENCY_1000DEC2 */ );
+
+ // #,##0 negative red
+ nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_CURRENCY_1000INT_RED );
+ aFormat = aFormatSeq[nIdx];
+ aFormat.Default = false;
+ ImpInsertFormat(aFormat,
+ CLOffset + ZF_STANDARD_CURRENCY+2 /* NF_CURRENCY_1000INT_RED */ );
+
+ // #,##0.00 negative red
+ nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_CURRENCY_1000DEC2_RED );
+ aFormat = aFormatSeq[nIdx];
+ aFormat.Default = false;
+ ImpInsertFormat(aFormat,
+ CLOffset + ZF_STANDARD_CURRENCY+3 /* NF_CURRENCY_1000DEC2_RED */ );
+
+ // #,##0.00 USD
+ nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_CURRENCY_1000DEC2_CCC );
+ aFormat = aFormatSeq[nIdx];
+ aFormat.Default = false;
+ ImpInsertFormat( aFormat,
+ CLOffset + ZF_STANDARD_CURRENCY+4 /* NF_CURRENCY_1000DEC2_CCC */ );
+
+ // #.##0,--
+ nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_CURRENCY_1000DEC2_DASHED );
+ aFormat = aFormatSeq[nIdx];
+ aFormat.Default = false;
+ ImpInsertFormat(aFormat,
+ CLOffset + ZF_STANDARD_CURRENCY+5 /* NF_CURRENCY_1000DEC2_DASHED */ );
+
+
+ // Date
+ aFormatSeq = xNFC->getAllFormatCode( i18n::KNumberFormatUsage::DATE, aLocale );
+ ImpAdjustFormatCodeDefault( aFormatSeq.getArray(), aFormatSeq.getLength() );
+
+ // DD.MM.YY System
+ nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_DATE_SYSTEM_SHORT );
+ ImpInsertFormat( aFormatSeq[nIdx],
+ CLOffset + ZF_STANDARD_DATE /* NF_DATE_SYSTEM_SHORT */ );
+
+ // NN DD.MMM YY
+ nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_DATE_DEF_NNDDMMMYY );
+ ImpInsertFormat( aFormatSeq[nIdx],
+ CLOffset + ZF_STANDARD_DATE+1 /* NF_DATE_DEF_NNDDMMMYY */ );
+
+ // DD.MM.YY def/System
+ nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_DATE_SYS_MMYY );
+ ImpInsertFormat( aFormatSeq[nIdx],
+ CLOffset + ZF_STANDARD_DATE+2 /* NF_DATE_SYS_MMYY */ );
+
+ // DD MMM
+ nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_DATE_SYS_DDMMM );
+ ImpInsertFormat( aFormatSeq[nIdx],
+ CLOffset + ZF_STANDARD_DATE+3 /* NF_DATE_SYS_DDMMM */ );
+
+ // MMMM
+ nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_DATE_MMMM );
+ ImpInsertFormat( aFormatSeq[nIdx],
+ CLOffset + ZF_STANDARD_DATE+4 /* NF_DATE_MMMM */ );
+
+ // QQ YY
+ nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_DATE_QQJJ );
+ ImpInsertFormat( aFormatSeq[nIdx],
+ CLOffset + ZF_STANDARD_DATE+5 /* NF_DATE_QQJJ */ );
+
+ // DD.MM.YYYY was DD.MM.[YY]YY
+ nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_DATE_SYS_DDMMYYYY );
+ ImpInsertFormat( aFormatSeq[nIdx],
+ CLOffset + ZF_STANDARD_DATE+6 /* NF_DATE_SYS_DDMMYYYY */ );
+
+ // DD.MM.YY def/System
+ nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_DATE_SYS_DDMMYY );
+ ImpInsertFormat( aFormatSeq[nIdx],
+ CLOffset + ZF_STANDARD_DATE+7 /* NF_DATE_SYS_DDMMYY */ );
+
+ // NNN, D. MMMM YYYY System
+ // Long day of week: "NNNN" instead of "NNN," because of compatibility
+ nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_DATE_SYSTEM_LONG );
+ ImpInsertFormat( aFormatSeq[nIdx],
+ CLOffset + ZF_STANDARD_DATE+8 /* NF_DATE_SYSTEM_LONG */ );
+
+ // Hard coded but system (regional settings) delimiters dependent long date formats
+
+ // D. MMM YY def/System
+ nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_DATE_SYS_DMMMYY );
+ ImpInsertFormat( aFormatSeq[nIdx],
+ CLOffset + ZF_STANDARD_DATE+9 /* NF_DATE_SYS_DMMMYY */ );
+
+ // D. MMM YYYY def/System
+ nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_DATE_SYS_DMMMYYYY );
+ ImpInsertFormat( aFormatSeq[nIdx],
+ CLOffset + ZF_STANDARD_DATE_SYS_DMMMYYYY /* NF_DATE_SYS_DMMMYYYY */ );
+
+ // D. MMMM YYYY def/System
+ nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_DATE_SYS_DMMMMYYYY );
+ ImpInsertFormat( aFormatSeq[nIdx],
+ CLOffset + ZF_STANDARD_DATE_SYS_DMMMMYYYY /* NF_DATE_SYS_DMMMMYYYY */ );
+
+ // NN, D. MMM YY def/System
+ nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_DATE_SYS_NNDMMMYY );
+ ImpInsertFormat( aFormatSeq[nIdx],
+ CLOffset + ZF_STANDARD_DATE_SYS_NNDMMMYY /* NF_DATE_SYS_NNDMMMYY */ );
+
+ // NN, D. MMMM YYYY def/System
+ nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_DATE_SYS_NNDMMMMYYYY );
+ ImpInsertFormat( aFormatSeq[nIdx],
+ CLOffset + ZF_STANDARD_DATE_SYS_NNDMMMMYYYY /* NF_DATE_SYS_NNDMMMMYYYY */ );
+
+ // NNN, D. MMMM YYYY def/System
+ nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_DATE_SYS_NNNNDMMMMYYYY );
+ ImpInsertFormat( aFormatSeq[nIdx],
+ CLOffset + ZF_STANDARD_DATE_SYS_NNNNDMMMMYYYY /* NF_DATE_SYS_NNNNDMMMMYYYY */ );
+
+ // Hard coded DIN (Deutsche Industrie Norm) and EN (European Norm) date formats
+
+ // D. MMM. YYYY DIN/EN
+ nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_DATE_DIN_DMMMYYYY );
+ ImpInsertFormat( aFormatSeq[nIdx],
+ CLOffset + ZF_STANDARD_DATE_DIN_DMMMYYYY /* NF_DATE_DIN_DMMMYYYY */ );
+
+ // D. MMMM YYYY DIN/EN
+ nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_DATE_DIN_DMMMMYYYY );
+ ImpInsertFormat( aFormatSeq[nIdx],
+ CLOffset + ZF_STANDARD_DATE_DIN_DMMMMYYYY /* NF_DATE_DIN_DMMMMYYYY */ );
+
+ // MM-DD DIN/EN
+ nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_DATE_DIN_MMDD );
+ ImpInsertFormat( aFormatSeq[nIdx],
+ CLOffset + ZF_STANDARD_DATE_DIN_MMDD /* NF_DATE_DIN_MMDD */ );
+
+ // YY-MM-DD DIN/EN
+ nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_DATE_DIN_YYMMDD );
+ ImpInsertFormat( aFormatSeq[nIdx],
+ CLOffset + ZF_STANDARD_DATE_DIN_YYMMDD /* NF_DATE_DIN_YYMMDD */ );
+
+ // YYYY-MM-DD DIN/EN/ISO
+ nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_DATE_DIN_YYYYMMDD );
+ ImpInsertFormat( aFormatSeq[nIdx],
+ CLOffset + ZF_STANDARD_DATE_DIN_YYYYMMDD /* NF_DATE_DIN_YYYYMMDD */ );
+
+
+ // Time
+ aFormatSeq = xNFC->getAllFormatCode( i18n::KNumberFormatUsage::TIME, aLocale );
+ ImpAdjustFormatCodeDefault( aFormatSeq.getArray(), aFormatSeq.getLength() );
+
+ // HH:MM
+ nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_TIME_HHMM );
+ ImpInsertFormat( aFormatSeq[nIdx],
+ CLOffset + ZF_STANDARD_TIME /* NF_TIME_HHMM */ );
+
+ // HH:MM:SS
+ nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_TIME_HHMMSS );
+ ImpInsertFormat( aFormatSeq[nIdx],
+ CLOffset + ZF_STANDARD_TIME+1 /* NF_TIME_HHMMSS */ );
+
+ // HH:MM AM/PM
+ nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_TIME_HHMMAMPM );
+ ImpInsertFormat( aFormatSeq[nIdx],
+ CLOffset + ZF_STANDARD_TIME+2 /* NF_TIME_HHMMAMPM */ );
+
+ // HH:MM:SS AM/PM
+ nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_TIME_HHMMSSAMPM );
+ ImpInsertFormat( aFormatSeq[nIdx],
+ CLOffset + ZF_STANDARD_TIME+3 /* NF_TIME_HHMMSSAMPM */ );
+
+ // [HH]:MM:SS
+ nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_TIME_HH_MMSS );
+ ImpInsertFormat( aFormatSeq[nIdx],
+ CLOffset + ZF_STANDARD_TIME+4 /* NF_TIME_HH_MMSS */ );
+
+ // MM:SS,00
+ nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_TIME_MMSS00 );
+ ImpInsertFormat( aFormatSeq[nIdx],
+ CLOffset + ZF_STANDARD_TIME+5 /* NF_TIME_MMSS00 */ );
+
+ // [HH]:MM:SS,00
+ nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_TIME_HH_MMSS00 );
+ ImpInsertFormat( aFormatSeq[nIdx],
+ CLOffset + ZF_STANDARD_TIME+6 /* NF_TIME_HH_MMSS00 */ );
+
+
+ // DateTime
+ aFormatSeq = xNFC->getAllFormatCode( i18n::KNumberFormatUsage::DATE_TIME, aLocale );
+ ImpAdjustFormatCodeDefault( aFormatSeq.getArray(), aFormatSeq.getLength() );
+
+ // DD.MM.YY HH:MM System
+ nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_DATETIME_SYSTEM_SHORT_HHMM );
+ ImpInsertFormat( aFormatSeq[nIdx],
+ CLOffset + ZF_STANDARD_DATETIME /* NF_DATETIME_SYSTEM_SHORT_HHMM */ );
+
+ // DD.MM.YYYY HH:MM:SS System
+ nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_DATETIME_SYS_DDMMYYYY_HHMMSS );
+ ImpInsertFormat( aFormatSeq[nIdx],
+ CLOffset + ZF_STANDARD_DATETIME+1 /* NF_DATETIME_SYS_DDMMYYYY_HHMMSS */ );
+
+ // DD.MM.YYYY HH:MM System
+ nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_DATETIME_SYS_DDMMYYYY_HHMM );
+ ImpInsertFormat( aFormatSeq[nIdx],
+ CLOffset + ZF_STANDARD_DATETIME+2 /* NF_DATETIME_SYS_DDMMYYYY_HHMM */ );
+
+ const NfKeywordTable & rKeyword = pFormatScanner->GetKeywords();
+ i18n::NumberFormatCode aSingleFormatCode;
+ aSingleFormatCode.Usage = i18n::KNumberFormatUsage::DATE_TIME;
+
+ // YYYY-MM-DD HH:MM:SS ISO (with blank instead of 'T')
+ aSingleFormatCode.Code =
+ rKeyword[NF_KEY_YYYY] + "-" +
+ rKeyword[NF_KEY_MM] + "-" +
+ rKeyword[NF_KEY_DD] + " " +
+ rKeyword[NF_KEY_HH] + ":" +
+ rKeyword[NF_KEY_MMI] + ":" +
+ rKeyword[NF_KEY_SS];
+ SvNumberformat* pFormat = ImpInsertFormat( aSingleFormatCode,
+ CLOffset + ZF_STANDARD_DATETIME+3 /* NF_DATETIME_ISO_YYYYMMDD_HHMMSS */ );
+ assert(pFormat);
+
+ // YYYY-MM-DD HH:MM:SS,000 ISO (with blank instead of 'T') and
+ // milliseconds and locale's time decimal separator
+ aSingleFormatCode.Code =
+ rKeyword[NF_KEY_YYYY] + "-" +
+ rKeyword[NF_KEY_MM] + "-" +
+ rKeyword[NF_KEY_DD] + " " +
+ rKeyword[NF_KEY_HH] + ":" +
+ rKeyword[NF_KEY_MMI] + ":" +
+ rKeyword[NF_KEY_SS] + GetLocaleData()->getTime100SecSep() +
+ "000";
+ pFormat = ImpInsertFormat( aSingleFormatCode,
+ CLOffset + ZF_STANDARD_DATETIME+4 /* NF_DATETIME_ISO_YYYYMMDD_HHMMSS000 */ );
+ assert(pFormat);
+
+ // YYYY-MM-DD"T"HH:MM:SS ISO
+ aSingleFormatCode.Code =
+ rKeyword[NF_KEY_YYYY] + "-" +
+ rKeyword[NF_KEY_MM] + "-" +
+ rKeyword[NF_KEY_DD] + "\"T\"" +
+ rKeyword[NF_KEY_HH] + ":" +
+ rKeyword[NF_KEY_MMI] + ":" +
+ rKeyword[NF_KEY_SS];
+ pFormat = ImpInsertFormat( aSingleFormatCode,
+ CLOffset + ZF_STANDARD_DATETIME+5 /* NF_DATETIME_ISO_YYYYMMDDTHHMMSS */ );
+ assert(pFormat);
+ pFormat->SetComment("ISO 8601"); // not to be localized
+
+ // YYYY-MM-DD"T"HH:MM:SS,000 ISO with milliseconds and ',' or '.' decimal separator
+ aSingleFormatCode.Code =
+ rKeyword[NF_KEY_YYYY] + "-" +
+ rKeyword[NF_KEY_MM] + "-" +
+ rKeyword[NF_KEY_DD] + "\"T\"" +
+ rKeyword[NF_KEY_HH] + ":" +
+ rKeyword[NF_KEY_MMI] + ":" +
+ rKeyword[NF_KEY_SS] + (GetLocaleData()->getTime100SecSep() == "." ? "." : ",") +
+ "000";
+ pFormat = ImpInsertFormat( aSingleFormatCode,
+ CLOffset + ZF_STANDARD_DATETIME+6 /* NF_DATETIME_ISO_YYYYMMDDTHHMMSS000 */ );
+ assert(pFormat);
+ pFormat->SetComment("ISO 8601"); // not to be localized
+
+
+ // Scientific number
+ aFormatSeq = xNFC->getAllFormatCode( i18n::KNumberFormatUsage::SCIENTIFIC_NUMBER, aLocale );
+ ImpAdjustFormatCodeDefault( aFormatSeq.getArray(), aFormatSeq.getLength() );
+
+ // 0.00E+000
+ nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_SCIENTIFIC_000E000 );
+ ImpInsertFormat( aFormatSeq[nIdx],
+ CLOffset + ZF_STANDARD_SCIENTIFIC /* NF_SCIENTIFIC_000E000 */ );
+
+ // 0.00E+00
+ nIdx = ImpGetFormatCodeIndex( aFormatSeq, NF_SCIENTIFIC_000E00 );
+ ImpInsertFormat( aFormatSeq[nIdx],
+ CLOffset + ZF_STANDARD_SCIENTIFIC+1 /* NF_SCIENTIFIC_000E00 */ );
+
+
+ // Fraction number (no default option)
+ aSingleFormatCode.Usage = i18n::KNumberFormatUsage::FRACTION_NUMBER;
+
+ // # ?/?
+ aSingleFormatCode.Code = "# ?/?";
+ ImpInsertFormat( aSingleFormatCode,
+ CLOffset + ZF_STANDARD_FRACTION /* NF_FRACTION_1D */ );
+
+ // # ??/??
+ //! "??/" would be interpreted by the compiler as a trigraph for '\'
+ aSingleFormatCode.Code = "# ?\?/?\?";
+ ImpInsertFormat( aSingleFormatCode,
+ CLOffset + ZF_STANDARD_FRACTION+1 /* NF_FRACTION_2D */ );
+
+ // # ???/???
+ //! "??/" would be interpreted by the compiler as a trigraph for '\'
+ aSingleFormatCode.Code = "# ?\?\?/?\?\?";
+ ImpInsertFormat( aSingleFormatCode,
+ CLOffset + ZF_STANDARD_FRACTION+2 /* NF_FRACTION_3D */ );
+
+ // # ?/2
+ aSingleFormatCode.Code = "# ?/2";
+ ImpInsertFormat( aSingleFormatCode,
+ CLOffset + ZF_STANDARD_FRACTION+3 /* NF_FRACTION_2 */ );
+
+ // # ?/4
+ aSingleFormatCode.Code = "# ?/4";
+ ImpInsertFormat( aSingleFormatCode,
+ CLOffset + ZF_STANDARD_FRACTION+4 /* NF_FRACTION_4 */ );
+
+ // # ?/8
+ aSingleFormatCode.Code = "# ?/8";
+ ImpInsertFormat( aSingleFormatCode,
+ CLOffset + ZF_STANDARD_FRACTION+5 /* NF_FRACTION_8 */ );
+
+ // # ??/16
+ aSingleFormatCode.Code = "# ?\?/16";
+ ImpInsertFormat( aSingleFormatCode,
+ CLOffset + ZF_STANDARD_FRACTION+6 /* NF_FRACTION_16 */ );
+
+ // # ??/10
+ aSingleFormatCode.Code = "# ?\?/10";
+ ImpInsertFormat( aSingleFormatCode,
+ CLOffset + ZF_STANDARD_FRACTION+7 /* NF_FRACTION_10 */ );
+
+ // # ??/100
+ aSingleFormatCode.Code = "# ?\?/100";
+ ImpInsertFormat( aSingleFormatCode,
+ CLOffset + ZF_STANDARD_FRACTION+8 /* NF_FRACTION_100 */ );
+
+
+ // Week of year
+ aSingleFormatCode.Code = rKeyword[NF_KEY_WW];
+ ImpInsertFormat( aSingleFormatCode,
+ CLOffset + ZF_STANDARD_DATE_WW /* NF_DATE_WW */ );
+
+ // Now all additional format codes provided by I18N, but only if not
+ // changing SystemCL, then they are appended last after user defined.
+ if ( !bNoAdditionalFormats )
+ {
+ ImpGenerateAdditionalFormats( CLOffset, xNFC, false );
+ }
+ if (bOldConvertMode)
+ {
+ pFormatScanner->SetConvertMode(true);
+ }
+}
+
+
+void SvNumberFormatter::ImpGenerateAdditionalFormats( sal_uInt32 CLOffset,
+ css::uno::Reference< css::i18n::XNumberFormatCode > const & rNumberFormatCode,
+ bool bAfterChangingSystemCL )
+{
+ SvNumberformat* pStdFormat = GetFormatEntry( CLOffset + ZF_STANDARD );
+ if ( !pStdFormat )
+ {
+ SAL_WARN( "svl.numbers", "ImpGenerateAdditionalFormats: no GENERAL format" );
+ return ;
+ }
+ sal_uInt32 nPos = CLOffset + pStdFormat->GetLastInsertKey( SvNumberformat::FormatterPrivateAccess() );
+ css::lang::Locale aLocale = GetLanguageTag().getLocale();
+
+ // All currencies, this time with [$...] which was stripped in
+ // ImpGenerateFormats for old "automatic" currency formats.
+ uno::Sequence< i18n::NumberFormatCode > aFormatSeq = rNumberFormatCode->getAllFormatCode( i18n::KNumberFormatUsage::CURRENCY, aLocale );
+ sal_Int32 nCodes = aFormatSeq.getLength();
+ auto aNonConstRange = asNonConstRange(aFormatSeq);
+ ImpAdjustFormatCodeDefault( aNonConstRange.begin(), nCodes);
+ for ( i18n::NumberFormatCode& rFormat : aNonConstRange )
+ {
+ if ( nPos - CLOffset >= SV_COUNTRY_LANGUAGE_OFFSET )
+ {
+ SAL_WARN( "svl.numbers", "ImpGenerateAdditionalFormats: too many formats" );
+ break; // for
+ }
+ if ( rFormat.Index < NF_INDEX_TABLE_RESERVED_START &&
+ rFormat.Index != NF_CURRENCY_1000DEC2_CCC )
+ { // Insert only if not already inserted, but internal index must be
+ // above so ImpInsertFormat can distinguish it.
+ sal_Int16 nOrgIndex = rFormat.Index;
+ rFormat.Index = sal::static_int_cast< sal_Int16 >(
+ rFormat.Index + nCodes + NF_INDEX_TABLE_ENTRIES);
+ //! no default on currency
+ bool bDefault = rFormat.Default;
+ rFormat.Default = false;
+ if ( SvNumberformat* pNewFormat = ImpInsertFormat( rFormat, nPos+1,
+ bAfterChangingSystemCL, nOrgIndex ) )
+ {
+ pNewFormat->SetAdditionalBuiltin();
+ nPos++;
+ }
+ rFormat.Index = nOrgIndex;
+ rFormat.Default = bDefault;
+ }
+ }
+
+ // All additional format codes provided by I18N that are not old standard
+ // index. Additional formats may define defaults, currently there is no
+ // check if more than one default of a usage/type combination is provided,
+ // like it is done for usage groups with ImpAdjustFormatCodeDefault().
+ // There is no harm though, on first invocation ImpGetDefaultFormat() will
+ // use the first default encountered.
+ aFormatSeq = rNumberFormatCode->getAllFormatCodes( aLocale );
+ for ( const auto& rFormat : std::as_const(aFormatSeq) )
+ {
+ if ( nPos - CLOffset >= SV_COUNTRY_LANGUAGE_OFFSET )
+ {
+ SAL_WARN( "svl.numbers", "ImpGenerateAdditionalFormats: too many formats" );
+ break; // for
+ }
+ if ( rFormat.Index >= NF_INDEX_TABLE_RESERVED_START )
+ {
+ if ( SvNumberformat* pNewFormat = ImpInsertFormat( rFormat, nPos+1,
+ bAfterChangingSystemCL ) )
+ {
+ pNewFormat->SetAdditionalBuiltin();
+ nPos++;
+ }
+ }
+ }
+
+ pStdFormat->SetLastInsertKey( static_cast<sal_uInt16>(nPos - CLOffset), SvNumberformat::FormatterPrivateAccess() );
+}
+
+
+sal_Int32 SvNumberFormatter::ImpPosToken ( const OUStringBuffer & sFormat, sal_Unicode token, sal_Int32 nStartPos /* = 0*/ ) const
+{
+ sal_Int32 nLength = sFormat.getLength();
+ for ( sal_Int32 i=nStartPos; i<nLength && i>=0 ; i++ )
+ {
+ switch(sFormat[i])
+ {
+ case '\"' : // skip text
+ i = sFormat.indexOf('\"',i+1);
+ break;
+ case '[' : // skip condition
+ i = sFormat.indexOf(']',i+1);
+ break;
+ case '\\' : // skip escaped character
+ i++;
+ break;
+ case ';' :
+ if (token == ';')
+ return i;
+ break;
+ case 'e' :
+ case 'E' :
+ if (token == 'E')
+ return i; // if 'E' is outside "" and [] it must be the 'E' exponent
+ break;
+ default : break;
+ }
+ if ( i<0 )
+ i--;
+ }
+ return -2;
+}
+
+OUString SvNumberFormatter::GenerateFormat(sal_uInt32 nIndex,
+ LanguageType eLnge,
+ bool bThousand,
+ bool IsRed,
+ sal_uInt16 nPrecision,
+ sal_uInt16 nLeadingZeros)
+{
+ ::osl::MutexGuard aGuard( GetInstanceMutex() );
+ if (eLnge == LANGUAGE_DONTKNOW)
+ {
+ eLnge = IniLnge;
+ }
+
+ const SvNumberformat* pFormat = GetFormatEntry( nIndex );
+ const SvNumFormatType eType = (pFormat ? pFormat->GetMaskedType() : SvNumFormatType::UNDEFINED);
+
+ ImpGenerateCL(eLnge); // create new standard formats if necessary
+
+ utl::DigitGroupingIterator aGrouping( xLocaleData->getDigitGrouping());
+ // always group of 3 for Engineering notation
+ const sal_Int32 nDigitsInFirstGroup = ( bThousand && (eType == SvNumFormatType::SCIENTIFIC) ) ? 3 : aGrouping.get();
+ const OUString& rThSep = GetNumThousandSep();
+
+ OUStringBuffer sString;
+ using comphelper::string::padToLength;
+
+ if (eType & SvNumFormatType::TIME)
+ {
+ assert(pFormat && "with !pFormat eType can only be SvNumFormatType::UNDEFINED");
+ sString = pFormat->GetFormatStringForTimePrecision( nPrecision );
+ }
+ else if (nLeadingZeros == 0)
+ {
+ if (!bThousand)
+ sString.append('#');
+ else
+ {
+ if (eType == SvNumFormatType::SCIENTIFIC)
+ { // for scientific, bThousand is used for Engineering notation
+ sString.append("###");
+ }
+ else
+ {
+ sString.append("#" + rThSep);
+ padToLength(sString, sString.getLength() + nDigitsInFirstGroup, '#');
+ }
+ }
+ }
+ else
+ {
+ for (sal_uInt16 i = 0; i < nLeadingZeros; i++)
+ {
+ if (bThousand && i > 0 && i == aGrouping.getPos())
+ {
+ sString.insert(0, rThSep);
+ aGrouping.advance();
+ }
+ sString.insert(0, '0');
+ }
+ if ( bThousand )
+ {
+ sal_Int32 nDigits = (eType == SvNumFormatType::SCIENTIFIC) ? 3*((nLeadingZeros-1)/3 + 1) : nDigitsInFirstGroup + 1;
+ for (sal_Int32 i = nLeadingZeros; i < nDigits; i++)
+ {
+ if ( i % nDigitsInFirstGroup == 0 )
+ sString.insert(0, rThSep);
+ sString.insert(0, '#');
+ }
+ }
+ }
+ if (nPrecision > 0 && eType != SvNumFormatType::FRACTION && !( eType & SvNumFormatType::TIME ) )
+ {
+ sString.append(GetNumDecimalSep());
+ padToLength(sString, sString.getLength() + nPrecision, '0');
+ }
+
+ // Native Number
+ const OUString sPosNatNumModifier = pFormat ? pFormat->GetNatNumModifierString( 0 ) : "";
+ const OUString sNegNatNumModifier = pFormat ?
+ // if a negative format already exists, use its NatNum modifier
+ // else use NatNum modifier of positive format
+ ( pFormat->GetNumForString( 1, 0 ) ? pFormat->GetNatNumModifierString( 1 ) : sPosNatNumModifier )
+ : "";
+
+ if (eType == SvNumFormatType::PERCENT)
+ {
+ sString.append( pFormat->GetPercentString() );
+ }
+ else if (eType == SvNumFormatType::SCIENTIFIC)
+ {
+ OUStringBuffer sOldFormatString(pFormat->GetFormatstring());
+ sal_Int32 nIndexE = ImpPosToken( sOldFormatString, 'E' );
+ if (nIndexE > -1)
+ {
+ sal_Int32 nIndexSep = ImpPosToken( sOldFormatString, ';', nIndexE );
+ if (nIndexSep > nIndexE)
+ sString.append( sOldFormatString.subView(nIndexE, nIndexSep - nIndexE) );
+ else
+ sString.append( sOldFormatString.subView(nIndexE) );
+ }
+ }
+ else if (eType == SvNumFormatType::CURRENCY)
+ {
+ OUStringBuffer sNegStr(sString);
+ OUString aCurr;
+ const NfCurrencyEntry* pEntry;
+ bool bBank;
+ bool isPosNatNum12 = sPosNatNumModifier.startsWith( "[NatNum12" );
+ bool isNegNatNum12 = sNegNatNumModifier.startsWith( "[NatNum12" );
+ if ( !isPosNatNum12 || !isNegNatNum12 )
+ {
+ if ( GetNewCurrencySymbolString( nIndex, aCurr, &pEntry, &bBank ) )
+ {
+ if ( pEntry )
+ {
+ sal_uInt16 nPosiForm = NfCurrencyEntry::GetEffectivePositiveFormat(
+ xLocaleData->getCurrPositiveFormat(),
+ pEntry->GetPositiveFormat(), bBank );
+ sal_uInt16 nNegaForm = NfCurrencyEntry::GetEffectiveNegativeFormat(
+ xLocaleData->getCurrNegativeFormat(),
+ pEntry->GetNegativeFormat(), bBank );
+ if ( !isPosNatNum12 )
+ pEntry->CompletePositiveFormatString( sString, bBank, nPosiForm );
+ if ( !isNegNatNum12 )
+ pEntry->CompleteNegativeFormatString( sNegStr, bBank, nNegaForm );
+ }
+ else
+ { // assume currency abbreviation (AKA banking symbol), not symbol
+ sal_uInt16 nPosiForm = NfCurrencyEntry::GetEffectivePositiveFormat(
+ xLocaleData->getCurrPositiveFormat(),
+ xLocaleData->getCurrPositiveFormat(), true );
+ sal_uInt16 nNegaForm = NfCurrencyEntry::GetEffectiveNegativeFormat(
+ xLocaleData->getCurrNegativeFormat(),
+ xLocaleData->getCurrNegativeFormat(), true );
+ if ( !isPosNatNum12 )
+ NfCurrencyEntry::CompletePositiveFormatString( sString, aCurr, nPosiForm );
+ if ( !isNegNatNum12 )
+ NfCurrencyEntry::CompleteNegativeFormatString( sNegStr, aCurr, nNegaForm );
+ }
+ }
+ else
+ { // "automatic" old style
+ OUString aSymbol, aAbbrev;
+ GetCompatibilityCurrency( aSymbol, aAbbrev );
+ if ( !isPosNatNum12 )
+ NfCurrencyEntry::CompletePositiveFormatString( sString,
+ aSymbol, xLocaleData->getCurrPositiveFormat() );
+ if ( !isNegNatNum12 )
+ NfCurrencyEntry::CompleteNegativeFormatString( sNegStr,
+ aSymbol, xLocaleData->getCurrNegativeFormat() );
+ }
+ }
+ sString.append( ';' );
+ if (IsRed)
+ {
+ sString.append("[" +pFormatScanner->GetRedString() + "]");
+ }
+ sString.append( sNegNatNumModifier );
+ if ( isNegNatNum12 )
+ {
+ sString.append( '-' );
+ }
+ sString.append(sNegStr);
+ }
+ else if (eType == SvNumFormatType::FRACTION)
+ {
+ OUString aIntegerFractionDelimiterString = pFormat->GetIntegerFractionDelimiterString( 0 );
+ if ( aIntegerFractionDelimiterString == " " )
+ sString.append( aIntegerFractionDelimiterString );
+ else
+ {
+ sString.append( "\"" + aIntegerFractionDelimiterString + "\"" );
+ }
+ sString.append( pFormat->GetNumeratorString( 0 )
+ + "/" );
+ if ( nPrecision > 0 )
+ padToLength(sString, sString.getLength() + nPrecision, '?');
+ else
+ sString.append( '#' );
+ }
+ if (eType != SvNumFormatType::CURRENCY)
+ {
+ bool insertBrackets = false;
+ if ( eType != SvNumFormatType::UNDEFINED)
+ {
+ insertBrackets = pFormat->IsNegativeInBracket();
+ }
+ if (IsRed || insertBrackets)
+ {
+ OUStringBuffer sTmpStr(sString);
+
+ if (pFormat && pFormat->HasPositiveBracketPlaceholder())
+ {
+ sTmpStr.append("_)");
+ }
+ sTmpStr.append(';');
+
+ if (IsRed)
+ {
+ sTmpStr.append("[" + pFormatScanner->GetRedString() + "]");
+ }
+ sTmpStr.append( sNegNatNumModifier );
+
+ if (insertBrackets)
+ {
+ sTmpStr.append("(" + sString + ")");
+ }
+ else
+ {
+ sTmpStr.append("-" + sString);
+ }
+ sString = sTmpStr;
+ }
+ }
+ sString.insert( 0, sPosNatNumModifier );
+ return sString.makeStringAndClear();
+}
+
+bool SvNumberFormatter::IsUserDefined(sal_uInt32 F_Index) const
+{
+ ::osl::MutexGuard aGuard( GetInstanceMutex() );
+ const SvNumberformat* pFormat = GetFormatEntry(F_Index);
+
+ return pFormat && (pFormat->GetType() & SvNumFormatType::DEFINED);
+}
+
+bool SvNumberFormatter::IsUserDefined(std::u16string_view sStr,
+ LanguageType eLnge)
+{
+ ::osl::MutexGuard aGuard( GetInstanceMutex() );
+ if (eLnge == LANGUAGE_DONTKNOW)
+ {
+ eLnge = IniLnge;
+ }
+ sal_uInt32 CLOffset = ImpGenerateCL(eLnge); // create new standard formats if necessary
+ eLnge = ActLnge;
+
+ sal_uInt32 nKey = ImpIsEntry(sStr, CLOffset, eLnge);
+ if (nKey == NUMBERFORMAT_ENTRY_NOT_FOUND)
+ {
+ return true;
+ }
+ SvNumberformat* pEntry = GetFormatEntry( nKey );
+ return pEntry && (pEntry->GetType() & SvNumFormatType::DEFINED);
+}
+
+sal_uInt32 SvNumberFormatter::GetEntryKey(std::u16string_view sStr,
+ LanguageType eLnge)
+{
+ ::osl::MutexGuard aGuard( GetInstanceMutex() );
+ if (eLnge == LANGUAGE_DONTKNOW)
+ {
+ eLnge = IniLnge;
+ }
+ sal_uInt32 CLOffset = ImpGenerateCL(eLnge); // create new standard formats if necessary
+ return ImpIsEntry(sStr, CLOffset, eLnge);
+}
+
+sal_uInt32 SvNumberFormatter::GetStandardIndex(LanguageType eLnge)
+{
+ ::osl::MutexGuard aGuard( GetInstanceMutex() );
+ if (eLnge == LANGUAGE_DONTKNOW)
+ {
+ eLnge = IniLnge;
+ }
+ return GetStandardFormat(SvNumFormatType::NUMBER, eLnge);
+}
+
+SvNumFormatType SvNumberFormatter::GetType(sal_uInt32 nFIndex) const
+{
+ ::osl::MutexGuard aGuard( GetInstanceMutex() );
+ SvNumFormatType eType;
+ const SvNumberformat* pFormat = GetFormatEntry( nFIndex );
+ if (!pFormat)
+ {
+ eType = SvNumFormatType::UNDEFINED;
+ }
+ else
+ {
+ eType = pFormat->GetMaskedType();
+ if (eType == SvNumFormatType::ALL)
+ {
+ eType = SvNumFormatType::DEFINED;
+ }
+ }
+ return eType;
+}
+
+void SvNumberFormatter::ClearMergeTable()
+{
+ ::osl::MutexGuard aGuard( GetInstanceMutex() );
+ if ( pMergeTable )
+ {
+ pMergeTable->clear();
+ }
+}
+
+SvNumberFormatterIndexTable* SvNumberFormatter::MergeFormatter(SvNumberFormatter& rTable)
+{
+ ::osl::MutexGuard aGuard( GetInstanceMutex() );
+ if ( pMergeTable )
+ {
+ ClearMergeTable();
+ }
+ else
+ {
+ pMergeTable.reset( new SvNumberFormatterIndexTable );
+ }
+
+ sal_uInt32 nCLOffset = 0;
+ sal_uInt32 nOldKey, nOffset, nNewKey;
+
+ for (const auto& rEntry : rTable.aFTable)
+ {
+ SvNumberformat* pFormat = rEntry.second.get();
+ nOldKey = rEntry.first;
+ nOffset = nOldKey % SV_COUNTRY_LANGUAGE_OFFSET; // relative index
+ if (nOffset == 0) // 1st format of CL
+ {
+ nCLOffset = ImpGenerateCL(pFormat->GetLanguage());
+ }
+ if (nOffset <= SV_MAX_COUNT_STANDARD_FORMATS) // Std.form.
+ {
+ nNewKey = nCLOffset + nOffset;
+ if (aFTable.find( nNewKey) == aFTable.end()) // not already present
+ {
+ std::unique_ptr<SvNumberformat> pNewEntry(new SvNumberformat( *pFormat, *pFormatScanner ));
+ if (!aFTable.emplace( nNewKey, std::move(pNewEntry)).second)
+ {
+ SAL_WARN( "svl.numbers", "SvNumberFormatter::MergeFormatter: dup position");
+ }
+ }
+ if (nNewKey != nOldKey) // new index
+ {
+ (*pMergeTable)[nOldKey] = nNewKey;
+ }
+ }
+ else // user defined
+ {
+ std::unique_ptr<SvNumberformat> pNewEntry(new SvNumberformat( *pFormat, *pFormatScanner ));
+ nNewKey = ImpIsEntry(pNewEntry->GetFormatstring(),
+ nCLOffset,
+ pFormat->GetLanguage());
+ if (nNewKey == NUMBERFORMAT_ENTRY_NOT_FOUND) // only if not present yet
+ {
+ SvNumberformat* pStdFormat = GetFormatEntry(nCLOffset + ZF_STANDARD);
+ sal_uInt32 nPos = nCLOffset + pStdFormat->GetLastInsertKey( SvNumberformat::FormatterPrivateAccess() );
+ nNewKey = nPos+1;
+ if (nNewKey - nCLOffset >= SV_COUNTRY_LANGUAGE_OFFSET)
+ {
+ SAL_WARN( "svl.numbers", "SvNumberFormatter::MergeFormatter: too many formats for CL");
+ }
+ else if (!aFTable.emplace( nNewKey, std::move(pNewEntry)).second)
+ {
+ SAL_WARN( "svl.numbers", "SvNumberFormatter::MergeFormatter: dup position");
+ }
+ else
+ {
+ pStdFormat->SetLastInsertKey(static_cast<sal_uInt16>(nNewKey - nCLOffset),
+ SvNumberformat::FormatterPrivateAccess());
+ }
+ }
+ if (nNewKey != nOldKey) // new index
+ {
+ (*pMergeTable)[nOldKey] = nNewKey;
+ }
+ }
+ }
+ return pMergeTable.get();
+}
+
+
+SvNumberFormatterMergeMap SvNumberFormatter::ConvertMergeTableToMap()
+{
+ ::osl::MutexGuard aGuard( GetInstanceMutex() );
+ if (!HasMergeFormatTable())
+ {
+ return SvNumberFormatterMergeMap();
+ }
+ SvNumberFormatterMergeMap aMap;
+ for (const auto& rEntry : *pMergeTable)
+ {
+ sal_uInt32 nOldKey = rEntry.first;
+ aMap[ nOldKey ] = rEntry.second;
+ }
+ ClearMergeTable();
+ return aMap;
+}
+
+
+sal_uInt32 SvNumberFormatter::GetFormatForLanguageIfBuiltIn( sal_uInt32 nFormat,
+ LanguageType eLnge )
+{
+ ::osl::MutexGuard aGuard( GetInstanceMutex() );
+ if ( eLnge == LANGUAGE_DONTKNOW )
+ {
+ eLnge = IniLnge;
+ }
+ if ( nFormat < SV_COUNTRY_LANGUAGE_OFFSET && eLnge == IniLnge )
+ {
+ return nFormat; // it stays as it is
+ }
+ sal_uInt32 nOffset = nFormat % SV_COUNTRY_LANGUAGE_OFFSET; // relative index
+ if ( nOffset > SV_MAX_COUNT_STANDARD_FORMATS )
+ {
+ return nFormat; // not a built-in format
+ }
+ sal_uInt32 nCLOffset = ImpGenerateCL(eLnge); // create new standard formats if necessary
+ return nCLOffset + nOffset;
+}
+
+
+sal_uInt32 SvNumberFormatter::GetFormatIndex( NfIndexTableOffset nTabOff,
+ LanguageType eLnge )
+{
+ ::osl::MutexGuard aGuard( GetInstanceMutex() );
+ if (nTabOff >= NF_INDEX_TABLE_ENTRIES)
+ return NUMBERFORMAT_ENTRY_NOT_FOUND;
+
+ if (eLnge == LANGUAGE_DONTKNOW)
+ eLnge = IniLnge;
+
+ if (indexTable[nTabOff] == NUMBERFORMAT_ENTRY_NOT_FOUND)
+ return NUMBERFORMAT_ENTRY_NOT_FOUND;
+
+ sal_uInt32 nCLOffset = ImpGenerateCL(eLnge); // create new standard formats if necessary
+
+ return nCLOffset + indexTable[nTabOff];
+}
+
+
+NfIndexTableOffset SvNumberFormatter::GetIndexTableOffset( sal_uInt32 nFormat ) const
+{
+ sal_uInt32 nOffset = nFormat % SV_COUNTRY_LANGUAGE_OFFSET; // relative index
+ if ( nOffset > SV_MAX_COUNT_STANDARD_FORMATS )
+ {
+ return NF_INDEX_TABLE_ENTRIES; // not a built-in format
+ }
+
+ for ( sal_uInt16 j = 0; j < NF_INDEX_TABLE_ENTRIES; j++ )
+ {
+ if (indexTable[j] == nOffset)
+ return static_cast<NfIndexTableOffset>(j);
+ }
+ return NF_INDEX_TABLE_ENTRIES; // bad luck
+}
+
+void SvNumberFormatter::SetEvalDateFormat( NfEvalDateFormat eEDF )
+{
+ ::osl::MutexGuard aGuard( GetInstanceMutex() );
+ eEvalDateFormat = eEDF;
+}
+
+NfEvalDateFormat SvNumberFormatter::GetEvalDateFormat() const
+{
+ ::osl::MutexGuard aGuard( GetInstanceMutex() );
+ return eEvalDateFormat;
+}
+
+void SvNumberFormatter::SetYear2000( sal_uInt16 nVal )
+{
+ ::osl::MutexGuard aGuard( GetInstanceMutex() );
+ pStringScanner->SetYear2000( nVal );
+}
+
+
+sal_uInt16 SvNumberFormatter::GetYear2000() const
+{
+ ::osl::MutexGuard aGuard( GetInstanceMutex() );
+ return pStringScanner->GetYear2000();
+}
+
+
+sal_uInt16 SvNumberFormatter::ExpandTwoDigitYear( sal_uInt16 nYear ) const
+{
+ if ( nYear < 100 )
+ return SvNumberFormatter::ExpandTwoDigitYear( nYear,
+ pStringScanner->GetYear2000() );
+ return nYear;
+}
+
+
+// static
+sal_uInt16 SvNumberFormatter::GetYear2000Default()
+{
+ if (!utl::ConfigManager::IsFuzzing())
+ return officecfg::Office::Common::DateFormat::TwoDigitYear::get();
+ return 1930;
+}
+
+// static
+void SvNumberFormatter::resetTheCurrencyTable()
+{
+ SAL_INFO("svl", "Resetting the currency table.");
+
+ nSystemCurrencyPosition = 0;
+ bCurrencyTableInitialized = false;
+
+ GetFormatterRegistry().ConfigurationChanged(nullptr, ConfigurationHints::Locale | ConfigurationHints::Currency | ConfigurationHints::DatePatterns);
+}
+
+// static
+const NfCurrencyTable& SvNumberFormatter::GetTheCurrencyTable()
+{
+ while ( !bCurrencyTableInitialized )
+ ImpInitCurrencyTable();
+ return theCurrencyTable();
+}
+
+
+// static
+const NfCurrencyEntry* SvNumberFormatter::MatchSystemCurrency()
+{
+ // MUST call GetTheCurrencyTable() before accessing nSystemCurrencyPosition
+ const NfCurrencyTable& rTable = GetTheCurrencyTable();
+ return nSystemCurrencyPosition ? &rTable[nSystemCurrencyPosition] : nullptr;
+}
+
+
+// static
+const NfCurrencyEntry& SvNumberFormatter::GetCurrencyEntry( LanguageType eLang )
+{
+ if ( eLang == LANGUAGE_SYSTEM )
+ {
+ const NfCurrencyEntry* pCurr = MatchSystemCurrency();
+ return pCurr ? *pCurr : GetTheCurrencyTable()[0];
+ }
+ else
+ {
+ eLang = MsLangId::getRealLanguage( eLang );
+ const NfCurrencyTable& rTable = GetTheCurrencyTable();
+ sal_uInt16 nCount = rTable.size();
+ for ( sal_uInt16 j = 0; j < nCount; j++ )
+ {
+ if ( rTable[j].GetLanguage() == eLang )
+ return rTable[j];
+ }
+ return rTable[0];
+ }
+}
+
+
+// static
+const NfCurrencyEntry* SvNumberFormatter::GetCurrencyEntry(std::u16string_view rAbbrev, LanguageType eLang )
+{
+ eLang = MsLangId::getRealLanguage( eLang );
+ const NfCurrencyTable& rTable = GetTheCurrencyTable();
+ sal_uInt16 nCount = rTable.size();
+ for ( sal_uInt16 j = 0; j < nCount; j++ )
+ {
+ if ( rTable[j].GetLanguage() == eLang &&
+ rTable[j].GetBankSymbol() == rAbbrev )
+ {
+ return &rTable[j];
+ }
+ }
+ return nullptr;
+}
+
+
+// static
+const NfCurrencyEntry* SvNumberFormatter::GetLegacyOnlyCurrencyEntry( std::u16string_view rSymbol,
+ std::u16string_view rAbbrev )
+{
+ GetTheCurrencyTable(); // just for initialization
+ const NfCurrencyTable& rTable = theLegacyOnlyCurrencyTable();
+ sal_uInt16 nCount = rTable.size();
+ for ( sal_uInt16 j = 0; j < nCount; j++ )
+ {
+ if ( rTable[j].GetSymbol() == rSymbol &&
+ rTable[j].GetBankSymbol() == rAbbrev )
+ {
+ return &rTable[j];
+ }
+ }
+ return nullptr;
+}
+
+
+// static
+IMPL_STATIC_LINK_NOARG( SvNumberFormatter, CurrencyChangeLink, LinkParamNone*, void )
+{
+ OUString aAbbrev;
+ LanguageType eLang = LANGUAGE_SYSTEM;
+ SvtSysLocaleOptions().GetCurrencyAbbrevAndLanguage( aAbbrev, eLang );
+ SetDefaultSystemCurrency( aAbbrev, eLang );
+}
+
+
+// static
+void SvNumberFormatter::SetDefaultSystemCurrency( std::u16string_view rAbbrev, LanguageType eLang )
+{
+ ::osl::MutexGuard aGuard( GetGlobalMutex() );
+ if ( eLang == LANGUAGE_SYSTEM )
+ {
+ eLang = SvtSysLocale().GetLanguageTag().getLanguageType();
+ }
+ const NfCurrencyTable& rTable = GetTheCurrencyTable();
+ sal_uInt16 nCount = rTable.size();
+ if ( !rAbbrev.empty() )
+ {
+ for ( sal_uInt16 j = 0; j < nCount; j++ )
+ {
+ if ( rTable[j].GetLanguage() == eLang && rTable[j].GetBankSymbol() == rAbbrev )
+ {
+ nSystemCurrencyPosition = j;
+ return ;
+ }
+ }
+ }
+ else
+ {
+ for ( sal_uInt16 j = 0; j < nCount; j++ )
+ {
+ if ( rTable[j].GetLanguage() == eLang )
+ {
+ nSystemCurrencyPosition = j;
+ return ;
+ }
+ }
+ }
+ nSystemCurrencyPosition = 0; // not found => simple SYSTEM
+}
+
+
+void SvNumberFormatter::ResetDefaultSystemCurrency()
+{
+ nDefaultSystemCurrencyFormat = NUMBERFORMAT_ENTRY_NOT_FOUND;
+}
+
+
+void SvNumberFormatter::InvalidateDateAcceptancePatterns()
+{
+ pStringScanner->InvalidateDateAcceptancePatterns();
+}
+
+
+sal_uInt32 SvNumberFormatter::ImpGetDefaultSystemCurrencyFormat()
+{
+ if ( nDefaultSystemCurrencyFormat == NUMBERFORMAT_ENTRY_NOT_FOUND )
+ {
+ sal_Int32 nCheck;
+ SvNumFormatType nType;
+ NfWSStringsDtor aCurrList;
+ sal_uInt16 nDefault = GetCurrencyFormatStrings( aCurrList,
+ GetCurrencyEntry( LANGUAGE_SYSTEM ), false );
+ DBG_ASSERT( aCurrList.size(), "where is the NewCurrency System standard format?!?" );
+ // if already loaded or user defined nDefaultSystemCurrencyFormat
+ // will be set to the right value
+ PutEntry( aCurrList[ nDefault ], nCheck, nType,
+ nDefaultSystemCurrencyFormat, LANGUAGE_SYSTEM );
+ DBG_ASSERT( nCheck == 0, "NewCurrency CheckError" );
+ DBG_ASSERT( nDefaultSystemCurrencyFormat != NUMBERFORMAT_ENTRY_NOT_FOUND,
+ "nDefaultSystemCurrencyFormat == NUMBERFORMAT_ENTRY_NOT_FOUND" );
+ }
+ return nDefaultSystemCurrencyFormat;
+}
+
+
+sal_uInt32 SvNumberFormatter::ImpGetDefaultCurrencyFormat()
+{
+ sal_uInt32 CLOffset = ImpGetCLOffset( ActLnge );
+ DefaultFormatKeysMap::const_iterator it = aDefaultFormatKeys.find( CLOffset + ZF_STANDARD_CURRENCY );
+ sal_uInt32 nDefaultCurrencyFormat = (it != aDefaultFormatKeys.end() ?
+ it->second : NUMBERFORMAT_ENTRY_NOT_FOUND);
+ if ( nDefaultCurrencyFormat == NUMBERFORMAT_ENTRY_NOT_FOUND )
+ {
+ // look for a defined standard
+ sal_uInt32 nStopKey = CLOffset + SV_COUNTRY_LANGUAGE_OFFSET;
+ sal_uInt32 nKey(0);
+ auto it2 = aFTable.lower_bound( CLOffset );
+ while ( it2 != aFTable.end() && (nKey = it2->first) >= CLOffset && nKey < nStopKey )
+ {
+ const SvNumberformat* pEntry = it2->second.get();
+ if ( pEntry->IsStandard() && (pEntry->GetType() & SvNumFormatType::CURRENCY) )
+ {
+ nDefaultCurrencyFormat = nKey;
+ break; // while
+ }
+ ++it2;
+ }
+
+ if ( nDefaultCurrencyFormat == NUMBERFORMAT_ENTRY_NOT_FOUND )
+ { // none found, create one
+ sal_Int32 nCheck;
+ NfWSStringsDtor aCurrList;
+ sal_uInt16 nDefault = GetCurrencyFormatStrings( aCurrList,
+ GetCurrencyEntry( ActLnge ), false );
+ DBG_ASSERT( aCurrList.size(), "where is the NewCurrency standard format?" );
+ if ( !aCurrList.empty() )
+ {
+ // if already loaded or user defined nDefaultSystemCurrencyFormat
+ // will be set to the right value
+ SvNumFormatType nType;
+ PutEntry( aCurrList[ nDefault ], nCheck, nType,
+ nDefaultCurrencyFormat, ActLnge );
+ DBG_ASSERT( nCheck == 0, "NewCurrency CheckError" );
+ DBG_ASSERT( nDefaultCurrencyFormat != NUMBERFORMAT_ENTRY_NOT_FOUND,
+ "nDefaultCurrencyFormat == NUMBERFORMAT_ENTRY_NOT_FOUND" );
+ }
+ // old automatic currency format as a last resort
+ if ( nDefaultCurrencyFormat == NUMBERFORMAT_ENTRY_NOT_FOUND )
+ nDefaultCurrencyFormat = CLOffset + ZF_STANDARD_CURRENCY+3;
+ else
+ { // mark as standard so that it is found next time
+ SvNumberformat* pEntry = GetFormatEntry( nDefaultCurrencyFormat );
+ if ( pEntry )
+ pEntry->SetStandard();
+ }
+ }
+ aDefaultFormatKeys[ CLOffset + ZF_STANDARD_CURRENCY ] = nDefaultCurrencyFormat;
+ }
+ return nDefaultCurrencyFormat;
+}
+
+
+// static
+// true: continue; false: break loop, if pFoundEntry==NULL dupe found
+bool SvNumberFormatter::ImpLookupCurrencyEntryLoopBody(
+ const NfCurrencyEntry*& pFoundEntry, bool& bFoundBank, const NfCurrencyEntry* pData,
+ sal_uInt16 nPos, std::u16string_view rSymbol )
+{
+ bool bFound;
+ if ( pData->GetSymbol() == rSymbol )
+ {
+ bFound = true;
+ bFoundBank = false;
+ }
+ else if ( pData->GetBankSymbol() == rSymbol )
+ {
+ bFound = true;
+ bFoundBank = true;
+ }
+ else
+ bFound = false;
+ if ( bFound )
+ {
+ if ( pFoundEntry && pFoundEntry != pData )
+ {
+ pFoundEntry = nullptr;
+ return false; // break loop, not unique
+ }
+ if ( nPos == 0 )
+ { // first entry is SYSTEM
+ pFoundEntry = MatchSystemCurrency();
+ if ( pFoundEntry )
+ {
+ return false; // break loop
+ // even if there are more matching entries
+ // this one is probably the one we are looking for
+ }
+ else
+ {
+ pFoundEntry = pData;
+ }
+ }
+ else
+ {
+ pFoundEntry = pData;
+ }
+ }
+ return true;
+}
+
+
+bool SvNumberFormatter::GetNewCurrencySymbolString( sal_uInt32 nFormat, OUString& rStr,
+ const NfCurrencyEntry** ppEntry /* = NULL */,
+ bool* pBank /* = NULL */ ) const
+{
+ ::osl::MutexGuard aGuard( GetInstanceMutex() );
+ if ( ppEntry )
+ *ppEntry = nullptr;
+ if ( pBank )
+ *pBank = false;
+
+ const SvNumberformat* pFormat = GetFormatEntry(nFormat);
+ if ( pFormat )
+ {
+ OUString aSymbol, aExtension;
+ if ( pFormat->GetNewCurrencySymbol( aSymbol, aExtension ) )
+ {
+ OUStringBuffer sBuff(128); // guess-estimate of a value that will pretty much guarantee no re-alloc
+ if ( ppEntry )
+ {
+ bool bFoundBank = false;
+ // we definitely need an entry matching the format code string
+ const NfCurrencyEntry* pFoundEntry = GetCurrencyEntry(
+ bFoundBank, aSymbol, aExtension, pFormat->GetLanguage(),
+ true );
+ if ( pFoundEntry )
+ {
+ *ppEntry = pFoundEntry;
+ if ( pBank )
+ *pBank = bFoundBank;
+ rStr = pFoundEntry->BuildSymbolString(bFoundBank);
+ }
+ }
+ if ( rStr.isEmpty() )
+ { // analog to BuildSymbolString
+ sBuff.append("[$");
+ if ( aSymbol.indexOf( '-' ) != -1 ||
+ aSymbol.indexOf( ']' ) != -1 )
+ {
+ sBuff.append("\"" + aSymbol + "\"");
+ }
+ else
+ {
+ sBuff.append(aSymbol);
+ }
+ if ( !aExtension.isEmpty() )
+ {
+ sBuff.append(aExtension);
+ }
+ sBuff.append(']');
+ }
+ rStr = sBuff.makeStringAndClear();
+ return true;
+ }
+ }
+ rStr.clear();
+ return false;
+}
+
+
+// static
+const NfCurrencyEntry* SvNumberFormatter::GetCurrencyEntry( bool & bFoundBank,
+ std::u16string_view rSymbol,
+ std::u16string_view rExtension,
+ LanguageType eFormatLanguage,
+ bool bOnlyStringLanguage )
+{
+ sal_Int32 nExtLen = rExtension.size();
+ LanguageType eExtLang;
+ if ( nExtLen )
+ {
+ // rExtension should be a 16-bit hex value max FFFF which may contain a
+ // leading "-" separator (that is not a minus sign, but toInt32 can be
+ // used to parse it, with post-processing as necessary):
+ sal_Int32 nExtLang = o3tl::toInt32(rExtension, 16);
+ if ( !nExtLang )
+ {
+ eExtLang = LANGUAGE_DONTKNOW;
+ }
+ else
+ {
+ if (nExtLang < 0)
+ nExtLang = -nExtLang;
+ SAL_WARN_IF(nExtLang > 0xFFFF, "svl.numbers", "Out of range Lang Id: " << nExtLang << " from input string: " << OUString(rExtension));
+ eExtLang = LanguageType(nExtLang & 0xFFFF);
+ }
+ }
+ else
+ {
+ eExtLang = LANGUAGE_DONTKNOW;
+ }
+ const NfCurrencyEntry* pFoundEntry = nullptr;
+ const NfCurrencyTable& rTable = GetTheCurrencyTable();
+ sal_uInt16 nCount = rTable.size();
+ bool bCont = true;
+
+ // first try with given extension language/country
+ if ( nExtLen )
+ {
+ for ( sal_uInt16 j = 0; j < nCount && bCont; j++ )
+ {
+ LanguageType eLang = rTable[j].GetLanguage();
+ if ( eLang == eExtLang ||
+ ((eExtLang == LANGUAGE_DONTKNOW) &&
+ (eLang == LANGUAGE_SYSTEM)))
+ {
+ bCont = ImpLookupCurrencyEntryLoopBody( pFoundEntry, bFoundBank,
+ &rTable[j], j, rSymbol );
+ }
+ }
+ }
+
+ // ok?
+ if ( pFoundEntry || !bCont || (bOnlyStringLanguage && nExtLen) )
+ {
+ return pFoundEntry;
+ }
+ if ( !bOnlyStringLanguage )
+ {
+ // now try the language/country of the number format
+ for ( sal_uInt16 j = 0; j < nCount && bCont; j++ )
+ {
+ LanguageType eLang = rTable[j].GetLanguage();
+ if ( eLang == eFormatLanguage ||
+ ((eFormatLanguage == LANGUAGE_DONTKNOW) &&
+ (eLang == LANGUAGE_SYSTEM)))
+ {
+ bCont = ImpLookupCurrencyEntryLoopBody( pFoundEntry, bFoundBank,
+ &rTable[j], j, rSymbol );
+ }
+ }
+
+ // ok?
+ if ( pFoundEntry || !bCont )
+ {
+ return pFoundEntry;
+ }
+ }
+
+ // then try without language/country if no extension specified
+ if ( !nExtLen )
+ {
+ for ( sal_uInt16 j = 0; j < nCount && bCont; j++ )
+ {
+ bCont = ImpLookupCurrencyEntryLoopBody( pFoundEntry, bFoundBank,
+ &rTable[j], j, rSymbol );
+ }
+ }
+
+ return pFoundEntry;
+}
+
+
+void SvNumberFormatter::GetCompatibilityCurrency( OUString& rSymbol, OUString& rAbbrev ) const
+{
+ ::osl::MutexGuard aGuard( GetInstanceMutex() );
+ const css::uno::Sequence< css::i18n::Currency2 >
+ xCurrencies( xLocaleData->getAllCurrencies() );
+
+ auto pCurrency = std::find_if(xCurrencies.begin(), xCurrencies.end(),
+ [](const css::i18n::Currency2& rCurrency) { return rCurrency.UsedInCompatibleFormatCodes; });
+ if (pCurrency != xCurrencies.end())
+ {
+ rSymbol = pCurrency->Symbol;
+ rAbbrev = pCurrency->BankSymbol;
+ }
+ else
+ {
+ if (LocaleDataWrapper::areChecksEnabled())
+ {
+ LocaleDataWrapper::outputCheckMessage( xLocaleData->
+ appendLocaleInfo( u"GetCompatibilityCurrency: none?"));
+ }
+ rSymbol = xLocaleData->getCurrSymbol();
+ rAbbrev = xLocaleData->getCurrBankSymbol();
+ }
+}
+
+
+static void lcl_CheckCurrencySymbolPosition( const NfCurrencyEntry& rCurr )
+{
+ switch ( rCurr.GetPositiveFormat() )
+ {
+ case 0: // $1
+ case 1: // 1$
+ case 2: // $ 1
+ case 3: // 1 $
+ break;
+ default:
+ LocaleDataWrapper::outputCheckMessage( "lcl_CheckCurrencySymbolPosition: unknown PositiveFormat");
+ break;
+ }
+ switch ( rCurr.GetNegativeFormat() )
+ {
+ case 0: // ($1)
+ case 1: // -$1
+ case 2: // $-1
+ case 3: // $1-
+ case 4: // (1$)
+ case 5: // -1$
+ case 6: // 1-$
+ case 7: // 1$-
+ case 8: // -1 $
+ case 9: // -$ 1
+ case 10: // 1 $-
+ case 11: // $ -1
+ case 12 : // $ 1-
+ case 13 : // 1- $
+ case 14 : // ($ 1)
+ case 15 : // (1 $)
+ break;
+ default:
+ LocaleDataWrapper::outputCheckMessage( "lcl_CheckCurrencySymbolPosition: unknown NegativeFormat");
+ break;
+ }
+}
+
+// static
+bool SvNumberFormatter::IsLocaleInstalled( LanguageType eLang )
+{
+ // The set is initialized as a side effect of the currency table
+ // created, make sure that exists, which usually is the case unless a
+ // SvNumberFormatter was never instantiated.
+ GetTheCurrencyTable();
+ return theInstalledLocales.find( eLang) != theInstalledLocales.end();
+}
+
+// static
+void SvNumberFormatter::ImpInitCurrencyTable()
+{
+ // Race condition possible:
+ // ::osl::MutexGuard aGuard( GetMutex() );
+ // while ( !bCurrencyTableInitialized )
+ // ImpInitCurrencyTable();
+ static bool bInitializing = false;
+ if ( bCurrencyTableInitialized || bInitializing )
+ {
+ return ;
+ }
+ bInitializing = true;
+
+ LanguageType eSysLang = SvtSysLocale().GetLanguageTag().getLanguageType();
+ std::optional<LocaleDataWrapper> pLocaleData(std::in_place,
+ ::comphelper::getProcessComponentContext(),
+ SvtSysLocale().GetLanguageTag() );
+ // get user configured currency
+ OUString aConfiguredCurrencyAbbrev;
+ LanguageType eConfiguredCurrencyLanguage = LANGUAGE_SYSTEM;
+ SvtSysLocaleOptions().GetCurrencyAbbrevAndLanguage(
+ aConfiguredCurrencyAbbrev, eConfiguredCurrencyLanguage );
+ sal_uInt16 nSecondarySystemCurrencyPosition = 0;
+ sal_uInt16 nMatchingSystemCurrencyPosition = 0;
+
+ // First entry is SYSTEM:
+ auto& rCurrencyTable = theCurrencyTable();
+ rCurrencyTable.insert(
+ rCurrencyTable.begin(),
+ NfCurrencyEntry(*pLocaleData, LANGUAGE_SYSTEM));
+ sal_uInt16 nCurrencyPos = 1;
+
+ const css::uno::Sequence< css::lang::Locale > xLoc = LocaleDataWrapper::getInstalledLocaleNames();
+ sal_Int32 nLocaleCount = xLoc.getLength();
+ SAL_INFO( "svl.numbers", "number of locales: \"" << nLocaleCount << "\"" );
+ NfCurrencyTable &rLegacyOnlyCurrencyTable = theLegacyOnlyCurrencyTable();
+ sal_uInt16 nLegacyOnlyCurrencyPos = 0;
+ for ( css::lang::Locale const & rLocale : xLoc )
+ {
+ LanguageType eLang = LanguageTag::convertToLanguageType( rLocale, false);
+ theInstalledLocales.insert( eLang);
+ pLocaleData.emplace(
+ ::comphelper::getProcessComponentContext(),
+ LanguageTag(rLocale) );
+ Sequence< Currency2 > aCurrSeq = pLocaleData->getAllCurrencies();
+ sal_Int32 nCurrencyCount = aCurrSeq.getLength();
+ Currency2 const * const pCurrencies = aCurrSeq.getConstArray();
+
+ // one default currency for each locale, insert first so it is found first
+ sal_Int32 nDefault;
+ for ( nDefault = 0; nDefault < nCurrencyCount; nDefault++ )
+ {
+ if ( pCurrencies[nDefault].Default )
+ break;
+ }
+ std::optional<NfCurrencyEntry> pEntry;
+ if ( nDefault < nCurrencyCount )
+ {
+ pEntry.emplace(pCurrencies[nDefault], *pLocaleData, eLang);
+ }
+ else
+ { // first or ShellsAndPebbles
+ pEntry.emplace(*pLocaleData, eLang);
+ }
+ if (LocaleDataWrapper::areChecksEnabled())
+ {
+ lcl_CheckCurrencySymbolPosition( *pEntry );
+ }
+ if ( !nSystemCurrencyPosition && !aConfiguredCurrencyAbbrev.isEmpty() &&
+ pEntry->GetBankSymbol() == aConfiguredCurrencyAbbrev &&
+ pEntry->GetLanguage() == eConfiguredCurrencyLanguage )
+ {
+ nSystemCurrencyPosition = nCurrencyPos;
+ }
+ if ( !nMatchingSystemCurrencyPosition &&
+ pEntry->GetLanguage() == eSysLang )
+ {
+ nMatchingSystemCurrencyPosition = nCurrencyPos;
+ }
+ rCurrencyTable.insert(
+ rCurrencyTable.begin() + nCurrencyPos++, std::move(*pEntry));
+ // all remaining currencies for each locale
+ if ( nCurrencyCount > 1 )
+ {
+ sal_Int32 nCurrency;
+ for ( nCurrency = 0; nCurrency < nCurrencyCount; nCurrency++ )
+ {
+ if (pCurrencies[nCurrency].LegacyOnly)
+ {
+ rLegacyOnlyCurrencyTable.insert(
+ rLegacyOnlyCurrencyTable.begin() + nLegacyOnlyCurrencyPos++,
+ NfCurrencyEntry(
+ pCurrencies[nCurrency], *pLocaleData, eLang));
+ }
+ else if ( nCurrency != nDefault )
+ {
+ pEntry.emplace(pCurrencies[nCurrency], *pLocaleData, eLang);
+ // no dupes
+ bool bInsert = true;
+ sal_uInt16 n = rCurrencyTable.size();
+ sal_uInt16 aCurrencyIndex = 1; // skip first SYSTEM entry
+ for ( sal_uInt16 j=1; j<n; j++ )
+ {
+ if ( rCurrencyTable[aCurrencyIndex++] == *pEntry )
+ {
+ bInsert = false;
+ break; // for
+ }
+ }
+ if ( !bInsert )
+ {
+ pEntry.reset();
+ }
+ else
+ {
+ if ( !nSecondarySystemCurrencyPosition &&
+ (!aConfiguredCurrencyAbbrev.isEmpty() ?
+ pEntry->GetBankSymbol() == aConfiguredCurrencyAbbrev :
+ pEntry->GetLanguage() == eConfiguredCurrencyLanguage) )
+ {
+ nSecondarySystemCurrencyPosition = nCurrencyPos;
+ }
+ if ( !nMatchingSystemCurrencyPosition &&
+ pEntry->GetLanguage() == eSysLang )
+ {
+ nMatchingSystemCurrencyPosition = nCurrencyPos;
+ }
+ rCurrencyTable.insert(
+ rCurrencyTable.begin() + nCurrencyPos++, std::move(*pEntry));
+ }
+ }
+ }
+ }
+ }
+ if ( !nSystemCurrencyPosition )
+ {
+ nSystemCurrencyPosition = nSecondarySystemCurrencyPosition;
+ }
+ if ((!aConfiguredCurrencyAbbrev.isEmpty() && !nSystemCurrencyPosition) &&
+ LocaleDataWrapper::areChecksEnabled())
+ {
+ LocaleDataWrapper::outputCheckMessage(
+ "SvNumberFormatter::ImpInitCurrencyTable: configured currency not in I18N locale data.");
+ }
+ // match SYSTEM if no configured currency found
+ if ( !nSystemCurrencyPosition )
+ {
+ nSystemCurrencyPosition = nMatchingSystemCurrencyPosition;
+ }
+ if ((aConfiguredCurrencyAbbrev.isEmpty() && !nSystemCurrencyPosition) &&
+ LocaleDataWrapper::areChecksEnabled())
+ {
+ LocaleDataWrapper::outputCheckMessage(
+ "SvNumberFormatter::ImpInitCurrencyTable: system currency not in I18N locale data.");
+ }
+ pLocaleData.reset();
+ SvtSysLocaleOptions::SetCurrencyChangeLink( LINK( nullptr, SvNumberFormatter, CurrencyChangeLink ) );
+ bInitializing = false;
+ bCurrencyTableInitialized = true;
+}
+
+
+static std::ptrdiff_t addToCurrencyFormatsList( NfWSStringsDtor& rStrArr, const OUString& rFormat )
+{
+ // Prevent duplicates even over subsequent calls of
+ // GetCurrencyFormatStrings() with the same vector.
+ NfWSStringsDtor::const_iterator it( std::find( rStrArr.begin(), rStrArr.end(), rFormat));
+ if (it != rStrArr.end())
+ return it - rStrArr.begin();
+
+ rStrArr.push_back( rFormat);
+ return rStrArr.size() - 1;
+}
+
+
+sal_uInt16 SvNumberFormatter::GetCurrencyFormatStrings( NfWSStringsDtor& rStrArr,
+ const NfCurrencyEntry& rCurr,
+ bool bBank ) const
+{
+ ::osl::MutexGuard aGuard( GetInstanceMutex() );
+ OUString aRed = "["
+ + pFormatScanner->GetRedString()
+ + "]";
+
+ sal_uInt16 nDefault = 0;
+ if ( bBank )
+ {
+ // Only bank symbols.
+ OUString aPositiveBank = rCurr.BuildPositiveFormatString(true, *xLocaleData);
+ OUString aNegativeBank = rCurr.BuildNegativeFormatString(true, *xLocaleData );
+
+ OUString format1 = aPositiveBank
+ + ";"
+ + aNegativeBank;
+ addToCurrencyFormatsList( rStrArr, format1);
+
+ OUString format2 = aPositiveBank
+ + ";"
+ + aRed
+ + aNegativeBank;
+ nDefault = addToCurrencyFormatsList( rStrArr, format2);
+ }
+ else
+ {
+ // Mixed formats like in SvNumberFormatter::ImpGenerateFormats() but no
+ // duplicates if no decimals in currency.
+ OUString aPositive = rCurr.BuildPositiveFormatString(false, *xLocaleData );
+ OUString aNegative = rCurr.BuildNegativeFormatString(false, *xLocaleData );
+ OUString format1;
+ OUString format2;
+ OUString format3;
+ OUString format4;
+ OUString format5;
+ if (rCurr.GetDigits())
+ {
+ OUString aPositiveNoDec = rCurr.BuildPositiveFormatString(false, *xLocaleData, 0);
+ OUString aNegativeNoDec = rCurr.BuildNegativeFormatString(false, *xLocaleData, 0 );
+ OUString aPositiveDashed = rCurr.BuildPositiveFormatString(false, *xLocaleData, 2);
+ OUString aNegativeDashed = rCurr.BuildNegativeFormatString(false, *xLocaleData, 2);
+
+ format1 = aPositiveNoDec
+ + ";"
+ + aNegativeNoDec;
+
+ format3 = aPositiveNoDec
+ + ";"
+ + aRed
+ + aNegativeNoDec;
+
+ format5 = aPositiveDashed
+ + ";"
+ + aRed
+ + aNegativeDashed;
+ }
+
+ format2 = aPositive
+ + ";"
+ + aNegative;
+
+ format4 = aPositive
+ + ";"
+ + aRed
+ + aNegative;
+
+ if (rCurr.GetDigits())
+ {
+ addToCurrencyFormatsList( rStrArr, format1);
+ }
+ addToCurrencyFormatsList( rStrArr, format2);
+ if (rCurr.GetDigits())
+ {
+ addToCurrencyFormatsList( rStrArr, format3);
+ }
+ nDefault = addToCurrencyFormatsList( rStrArr, format4);
+ if (rCurr.GetDigits())
+ {
+ addToCurrencyFormatsList( rStrArr, format5);
+ }
+ }
+ return nDefault;
+}
+
+sal_uInt32 SvNumberFormatter::GetMergeFormatIndex( sal_uInt32 nOldFmt ) const
+{
+ ::osl::MutexGuard aGuard( GetInstanceMutex() );
+ if (pMergeTable)
+ {
+ SvNumberFormatterIndexTable::const_iterator it = pMergeTable->find(nOldFmt);
+ if (it != pMergeTable->end())
+ {
+ return it->second;
+ }
+ }
+ return nOldFmt;
+}
+
+bool SvNumberFormatter::HasMergeFormatTable() const
+{
+ ::osl::MutexGuard aGuard( GetInstanceMutex() );
+ return pMergeTable && !pMergeTable->empty();
+}
+
+// static
+sal_uInt16 SvNumberFormatter::ExpandTwoDigitYear( sal_uInt16 nYear, sal_uInt16 nTwoDigitYearStart )
+{
+ if ( nYear < 100 )
+ {
+ if ( nYear < (nTwoDigitYearStart % 100) )
+ {
+ return nYear + (((nTwoDigitYearStart / 100) + 1) * 100);
+ }
+ else
+ {
+ return nYear + ((nTwoDigitYearStart / 100) * 100);
+ }
+ }
+ return nYear;
+}
+
+NfCurrencyEntry::NfCurrencyEntry( const LocaleDataWrapper& rLocaleData, LanguageType eLang )
+{
+ aSymbol = rLocaleData.getCurrSymbol();
+ aBankSymbol = rLocaleData.getCurrBankSymbol();
+ eLanguage = eLang;
+ nPositiveFormat = rLocaleData.getCurrPositiveFormat();
+ nNegativeFormat = rLocaleData.getCurrNegativeFormat();
+ nDigits = rLocaleData.getCurrDigits();
+ cZeroChar = rLocaleData.getCurrZeroChar();
+}
+
+
+NfCurrencyEntry::NfCurrencyEntry( const css::i18n::Currency & rCurr,
+ const LocaleDataWrapper& rLocaleData, LanguageType eLang )
+{
+ aSymbol = rCurr.Symbol;
+ aBankSymbol = rCurr.BankSymbol;
+ eLanguage = eLang;
+ nPositiveFormat = rLocaleData.getCurrPositiveFormat();
+ nNegativeFormat = rLocaleData.getCurrNegativeFormat();
+ nDigits = rCurr.DecimalPlaces;
+ cZeroChar = rLocaleData.getCurrZeroChar();
+}
+
+bool NfCurrencyEntry::operator==( const NfCurrencyEntry& r ) const
+{
+ return aSymbol == r.aSymbol
+ && aBankSymbol == r.aBankSymbol
+ && eLanguage == r.eLanguage
+ ;
+}
+
+OUString NfCurrencyEntry::BuildSymbolString(bool bBank,
+ bool bWithoutExtension) const
+{
+ OUStringBuffer aBuf("[$");
+ if (bBank)
+ {
+ aBuf.append(aBankSymbol);
+ }
+ else
+ {
+ if ( aSymbol.indexOf( '-' ) >= 0 ||
+ aSymbol.indexOf( ']' ) >= 0)
+ {
+ aBuf.append("\"" + aSymbol + "\"");
+ }
+ else
+ {
+ aBuf.append(aSymbol);
+ }
+ if ( !bWithoutExtension && eLanguage != LANGUAGE_DONTKNOW && eLanguage != LANGUAGE_SYSTEM )
+ {
+ sal_Int32 nLang = static_cast<sal_uInt16>(eLanguage);
+ aBuf.append("-" + OUString::number(nLang, 16).toAsciiUpperCase());
+ }
+ }
+ aBuf.append(']');
+ return aBuf.makeStringAndClear();
+}
+
+OUString NfCurrencyEntry::Impl_BuildFormatStringNumChars( const LocaleDataWrapper& rLoc,
+ sal_uInt16 nDecimalFormat) const
+{
+ OUStringBuffer aBuf("#" + rLoc.getNumThousandSep() + "##0");
+ if (nDecimalFormat && nDigits)
+ {
+ aBuf.append(rLoc.getNumDecimalSep());
+ sal_Unicode cDecimalChar = nDecimalFormat == 2 ? '-' : cZeroChar;
+ for (sal_uInt16 i = 0; i < nDigits; ++i)
+ {
+ aBuf.append(cDecimalChar);
+ }
+ }
+ return aBuf.makeStringAndClear();
+}
+
+
+OUString NfCurrencyEntry::BuildPositiveFormatString(bool bBank, const LocaleDataWrapper& rLoc,
+ sal_uInt16 nDecimalFormat) const
+{
+ OUStringBuffer sBuf(Impl_BuildFormatStringNumChars(rLoc, nDecimalFormat));
+ sal_uInt16 nPosiForm = NfCurrencyEntry::GetEffectivePositiveFormat( rLoc.getCurrPositiveFormat(),
+ nPositiveFormat, bBank );
+ CompletePositiveFormatString(sBuf, bBank, nPosiForm);
+ return sBuf.makeStringAndClear();
+}
+
+
+OUString NfCurrencyEntry::BuildNegativeFormatString(bool bBank,
+ const LocaleDataWrapper& rLoc, sal_uInt16 nDecimalFormat ) const
+{
+ OUStringBuffer sBuf(Impl_BuildFormatStringNumChars(rLoc, nDecimalFormat));
+ sal_uInt16 nNegaForm = NfCurrencyEntry::GetEffectiveNegativeFormat( rLoc.getCurrNegativeFormat(),
+ nNegativeFormat, bBank );
+ CompleteNegativeFormatString(sBuf, bBank, nNegaForm);
+ return sBuf.makeStringAndClear();
+}
+
+
+void NfCurrencyEntry::CompletePositiveFormatString(OUStringBuffer& rStr, bool bBank,
+ sal_uInt16 nPosiForm) const
+{
+ OUString aSymStr = BuildSymbolString(bBank);
+ NfCurrencyEntry::CompletePositiveFormatString( rStr, aSymStr, nPosiForm );
+}
+
+
+void NfCurrencyEntry::CompleteNegativeFormatString(OUStringBuffer& rStr, bool bBank,
+ sal_uInt16 nNegaForm) const
+{
+ OUString aSymStr = BuildSymbolString(bBank);
+ NfCurrencyEntry::CompleteNegativeFormatString( rStr, aSymStr, nNegaForm );
+}
+
+
+// static
+void NfCurrencyEntry::CompletePositiveFormatString(OUStringBuffer& rStr, std::u16string_view rSymStr,
+ sal_uInt16 nPositiveFormat)
+{
+ switch( nPositiveFormat )
+ {
+ case 0: // $1
+ rStr.insert(0, rSymStr);
+ break;
+ case 1: // 1$
+ rStr.append(rSymStr);
+ break;
+ case 2: // $ 1
+ {
+ rStr.insert(0, OUString::Concat(rSymStr) + " ");
+ }
+ break;
+ case 3: // 1 $
+ {
+ rStr.append(' ');
+ rStr.append(rSymStr);
+ }
+ break;
+ default:
+ SAL_WARN( "svl.numbers", "NfCurrencyEntry::CompletePositiveFormatString: unknown option");
+ break;
+ }
+}
+
+
+// static
+void NfCurrencyEntry::CompleteNegativeFormatString(OUStringBuffer& rStr,
+ std::u16string_view rSymStr,
+ sal_uInt16 nNegativeFormat)
+{
+ switch( nNegativeFormat )
+ {
+ case 0: // ($1)
+ {
+ rStr.insert(0, OUString::Concat("(") + rSymStr);
+ rStr.append(')');
+ }
+ break;
+ case 1: // -$1
+ {
+ rStr.insert(0, OUString::Concat("-") + rSymStr);
+ }
+ break;
+ case 2: // $-1
+ {
+ rStr.insert(0, OUString::Concat(rSymStr) + "-");
+ }
+ break;
+ case 3: // $1-
+ {
+ rStr.insert(0, rSymStr);
+ rStr.append('-');
+ }
+ break;
+ case 4: // (1$)
+ {
+ rStr.insert(0, '(');
+ rStr.append(rSymStr);
+ rStr.append(')');
+ }
+ break;
+ case 5: // -1$
+ {
+ rStr.append(rSymStr);
+ rStr.insert(0, '-');
+ }
+ break;
+ case 6: // 1-$
+ {
+ rStr.append('-');
+ rStr.append(rSymStr);
+ }
+ break;
+ case 7: // 1$-
+ {
+ rStr.append(rSymStr);
+ rStr.append('-');
+ }
+ break;
+ case 8: // -1 $
+ {
+ rStr.append(' ');
+ rStr.append(rSymStr);
+ rStr.insert(0, '-');
+ }
+ break;
+ case 9: // -$ 1
+ {
+ rStr.insert(0, OUString::Concat("-") + rSymStr + " ");
+ }
+ break;
+ case 10: // 1 $-
+ {
+ rStr.append(' ');
+ rStr.append(rSymStr);
+ rStr.append('-');
+ }
+ break;
+ case 11: // $ -1
+ {
+ rStr.insert(0, OUString::Concat(rSymStr) + " -");
+ }
+ break;
+ case 12 : // $ 1-
+ {
+ rStr.insert(0, OUString::Concat(rSymStr) + " ");
+ rStr.append('-');
+ }
+ break;
+ case 13 : // 1- $
+ {
+ rStr.append('-');
+ rStr.append(' ');
+ rStr.append(rSymStr);
+ }
+ break;
+ case 14 : // ($ 1)
+ {
+ rStr.insert(0, OUString::Concat("(") + rSymStr + " ");
+ rStr.append(')');
+ }
+ break;
+ case 15 : // (1 $)
+ {
+ rStr.insert(0, '(');
+ rStr.append(' ');
+ rStr.append(rSymStr);
+ rStr.append(')');
+ }
+ break;
+ default:
+ SAL_WARN( "svl.numbers", "NfCurrencyEntry::CompleteNegativeFormatString: unknown option");
+ break;
+ }
+}
+
+
+// static
+sal_uInt16 NfCurrencyEntry::GetEffectivePositiveFormat( sal_uInt16 nIntlFormat,
+ sal_uInt16 nCurrFormat, bool bBank )
+{
+ if ( bBank )
+ {
+#if NF_BANKSYMBOL_FIX_POSITION
+ (void) nIntlFormat; // avoid warnings
+ return 3;
+#else
+ switch ( nIntlFormat )
+ {
+ case 0: // $1
+ nIntlFormat = 2; // $ 1
+ break;
+ case 1: // 1$
+ nIntlFormat = 3; // 1 $
+ break;
+ case 2: // $ 1
+ break;
+ case 3: // 1 $
+ break;
+ default:
+ SAL_WARN( "svl.numbers", "NfCurrencyEntry::GetEffectivePositiveFormat: unknown option");
+ break;
+ }
+ return nIntlFormat;
+#endif
+ }
+ else
+ return nCurrFormat;
+}
+
+
+//! Call this only if nCurrFormat is really with parentheses!
+static sal_uInt16 lcl_MergeNegativeParenthesisFormat( sal_uInt16 nIntlFormat, sal_uInt16 nCurrFormat )
+{
+ short nSign = 0; // -1:=bracket 0:=left, 1:=middle, 2:=right
+ switch ( nIntlFormat )
+ {
+ case 0: // ($1)
+ case 4: // (1$)
+ case 14 : // ($ 1)
+ case 15 : // (1 $)
+ return nCurrFormat;
+ case 1: // -$1
+ case 5: // -1$
+ case 8: // -1 $
+ case 9: // -$ 1
+ nSign = 0;
+ break;
+ case 2: // $-1
+ case 6: // 1-$
+ case 11 : // $ -1
+ case 13 : // 1- $
+ nSign = 1;
+ break;
+ case 3: // $1-
+ case 7: // 1$-
+ case 10: // 1 $-
+ case 12 : // $ 1-
+ nSign = 2;
+ break;
+ default:
+ SAL_WARN( "svl.numbers", "lcl_MergeNegativeParenthesisFormat: unknown option");
+ break;
+ }
+
+ switch ( nCurrFormat )
+ {
+ case 0: // ($1)
+ switch ( nSign )
+ {
+ case 0:
+ return 1; // -$1
+ case 1:
+ return 2; // $-1
+ case 2:
+ return 3; // $1-
+ }
+ break;
+ case 4: // (1$)
+ switch ( nSign )
+ {
+ case 0:
+ return 5; // -1$
+ case 1:
+ return 6; // 1-$
+ case 2:
+ return 7; // 1$-
+ }
+ break;
+ case 14 : // ($ 1)
+ switch ( nSign )
+ {
+ case 0:
+ return 9; // -$ 1
+ case 1:
+ return 11; // $ -1
+ case 2:
+ return 12; // $ 1-
+ }
+ break;
+ case 15 : // (1 $)
+ switch ( nSign )
+ {
+ case 0:
+ return 8; // -1 $
+ case 1:
+ return 13; // 1- $
+ case 2:
+ return 10; // 1 $-
+ }
+ break;
+ }
+ return nCurrFormat;
+}
+
+
+// static
+sal_uInt16 NfCurrencyEntry::GetEffectiveNegativeFormat( sal_uInt16 nIntlFormat,
+ sal_uInt16 nCurrFormat, bool bBank )
+{
+ if ( bBank )
+ {
+#if NF_BANKSYMBOL_FIX_POSITION
+ return 8;
+#else
+ switch ( nIntlFormat )
+ {
+ case 0: // ($1)
+// nIntlFormat = 14; // ($ 1)
+ nIntlFormat = 9; // -$ 1
+ break;
+ case 1: // -$1
+ nIntlFormat = 9; // -$ 1
+ break;
+ case 2: // $-1
+ nIntlFormat = 11; // $ -1
+ break;
+ case 3: // $1-
+ nIntlFormat = 12; // $ 1-
+ break;
+ case 4: // (1$)
+// nIntlFormat = 15; // (1 $)
+ nIntlFormat = 8; // -1 $
+ break;
+ case 5: // -1$
+ nIntlFormat = 8; // -1 $
+ break;
+ case 6: // 1-$
+ nIntlFormat = 13; // 1- $
+ break;
+ case 7: // 1$-
+ nIntlFormat = 10; // 1 $-
+ break;
+ case 8: // -1 $
+ break;
+ case 9: // -$ 1
+ break;
+ case 10: // 1 $-
+ break;
+ case 11: // $ -1
+ break;
+ case 12 : // $ 1-
+ break;
+ case 13 : // 1- $
+ break;
+ case 14 : // ($ 1)
+// nIntlFormat = 14; // ($ 1)
+ nIntlFormat = 9; // -$ 1
+ break;
+ case 15 : // (1 $)
+// nIntlFormat = 15; // (1 $)
+ nIntlFormat = 8; // -1 $
+ break;
+ default:
+ SAL_WARN( "svl.numbers", "NfCurrencyEntry::GetEffectiveNegativeFormat: unknown option");
+ break;
+ }
+#endif
+ }
+ else if ( nIntlFormat != nCurrFormat )
+ {
+ switch ( nCurrFormat )
+ {
+ case 0: // ($1)
+ nIntlFormat = lcl_MergeNegativeParenthesisFormat(
+ nIntlFormat, nCurrFormat );
+ break;
+ case 1: // -$1
+ nIntlFormat = nCurrFormat;
+ break;
+ case 2: // $-1
+ nIntlFormat = nCurrFormat;
+ break;
+ case 3: // $1-
+ nIntlFormat = nCurrFormat;
+ break;
+ case 4: // (1$)
+ nIntlFormat = lcl_MergeNegativeParenthesisFormat(
+ nIntlFormat, nCurrFormat );
+ break;
+ case 5: // -1$
+ nIntlFormat = nCurrFormat;
+ break;
+ case 6: // 1-$
+ nIntlFormat = nCurrFormat;
+ break;
+ case 7: // 1$-
+ nIntlFormat = nCurrFormat;
+ break;
+ case 8: // -1 $
+ nIntlFormat = nCurrFormat;
+ break;
+ case 9: // -$ 1
+ nIntlFormat = nCurrFormat;
+ break;
+ case 10: // 1 $-
+ nIntlFormat = nCurrFormat;
+ break;
+ case 11: // $ -1
+ nIntlFormat = nCurrFormat;
+ break;
+ case 12 : // $ 1-
+ nIntlFormat = nCurrFormat;
+ break;
+ case 13 : // 1- $
+ nIntlFormat = nCurrFormat;
+ break;
+ case 14 : // ($ 1)
+ nIntlFormat = lcl_MergeNegativeParenthesisFormat(
+ nIntlFormat, nCurrFormat );
+ break;
+ case 15 : // (1 $)
+ nIntlFormat = lcl_MergeNegativeParenthesisFormat(
+ nIntlFormat, nCurrFormat );
+ break;
+ default:
+ SAL_WARN( "svl.numbers", "NfCurrencyEntry::GetEffectiveNegativeFormat: unknown option");
+ break;
+ }
+ }
+ return nIntlFormat;
+}
+
+const NfKeywordTable & SvNumberFormatter::GetKeywords( sal_uInt32 nKey )
+{
+ osl::MutexGuard aGuard( GetInstanceMutex() );
+ const SvNumberformat* pFormat = GetFormatEntry( nKey);
+ if (pFormat)
+ ChangeIntl( pFormat->GetLanguage());
+ else
+ ChangeIntl( IniLnge);
+ return pFormatScanner->GetKeywords();
+}
+
+const NfKeywordTable & SvNumberFormatter::GetEnglishKeywords() const
+{
+ return ImpSvNumberformatScan::GetEnglishKeywords();
+}
+
+const std::vector<Color> & SvNumberFormatter::GetStandardColors() const
+{
+ return ImpSvNumberformatScan::GetStandardColors();
+}
+
+size_t SvNumberFormatter::GetMaxDefaultColors() const
+{
+ return ImpSvNumberformatScan::GetMaxDefaultColors();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/numbers/zformat.cxx b/svl/source/numbers/zformat.cxx
new file mode 100644
index 0000000000..b5c8757ef2
--- /dev/null
+++ b/svl/source/numbers/zformat.cxx
@@ -0,0 +1,6083 @@
+/* -*- 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 <string_view>
+
+#include <o3tl/sprintf.hxx>
+#include <o3tl/string_view.hxx>
+#include <comphelper/string.hxx>
+#include <sal/log.hxx>
+#include <tools/debug.hxx>
+#include <tools/long.hxx>
+#include <i18nlangtag/mslangid.hxx>
+#include <rtl/math.hxx>
+#include <unotools/charclass.hxx>
+#include <unotools/calendarwrapper.hxx>
+#include <unotools/nativenumberwrapper.hxx>
+#include <com/sun/star/i18n/CalendarFieldIndex.hpp>
+#include <com/sun/star/i18n/CalendarDisplayIndex.hpp>
+#include <com/sun/star/i18n/CalendarDisplayCode.hpp>
+#include <com/sun/star/i18n/AmPmValue.hpp>
+#include <com/sun/star/i18n/NativeNumberMode.hpp>
+#include <com/sun/star/i18n/NativeNumberXmlAttributes2.hpp>
+
+#include <svl/zformat.hxx>
+#include "zforscan.hxx"
+
+#include "zforfind.hxx"
+#include <svl/zforlist.hxx>
+#include <unotools/digitgroupingiterator.hxx>
+#include <svl/nfsymbol.hxx>
+
+#include <cmath>
+#include <array>
+
+using namespace svt;
+
+namespace {
+
+constexpr OUString GREGORIAN = u"gregorian"_ustr;
+
+const sal_uInt16 UPPER_PRECISION = 300; // entirely arbitrary...
+const double EXP_LOWER_BOUND = 1.0E-4; // prefer scientific notation below this value.
+const double EXP_ABS_UPPER_BOUND = 1.0E15; // use exponential notation above that absolute value.
+ // Back in time was E16 that lead
+ // to display rounding errors, see
+ // also sal/rtl/math.cxx
+ // doubleToString()
+
+constexpr sal_Int32 kTimeSignificantRound = 7; // Round (date+)time at 7 decimals
+ // (+5 of 86400 == 12 significant digits).
+} // namespace
+
+const double D_MAX_U_INT32 = double(0xffffffff); // 4294967295.0
+constexpr double D_MAX_INTEGER = (sal_uInt64(1) << 53) - 1;
+
+const double D_MAX_D_BY_100 = 1.7E306;
+const double D_MIN_M_BY_1000 = 2.3E-305;
+
+const sal_uInt8 cCharWidths[ 128-32 ] = {
+ 1,1,1,2,2,3,2,1,1,1,1,2,1,1,1,1,
+ 2,2,2,2,2,2,2,2,2,2,1,1,2,2,2,2,
+ 3,2,2,2,2,2,2,3,2,1,2,2,2,3,3,3,
+ 2,3,2,2,2,2,2,3,2,2,2,1,1,1,2,2,
+ 1,2,2,2,2,2,1,2,2,1,1,2,1,3,2,2,
+ 2,2,1,2,1,2,2,2,2,2,2,1,1,1,2,1
+};
+
+// static
+sal_Int32 SvNumberformat::InsertBlanks( OUStringBuffer& r, sal_Int32 nPos, sal_Unicode c )
+{
+ if( c >= 32 )
+ {
+ int n = 2; // Default for chars > 128 (HACK!)
+ if( c <= 127 )
+ {
+ n = static_cast<int>(cCharWidths[ c - 32 ]);
+ }
+ while( n-- )
+ {
+ r.insert( nPos++, ' ');
+ }
+ }
+ return nPos;
+}
+
+static tools::Long GetPrecExp( double fAbsVal )
+{
+ DBG_ASSERT( fAbsVal > 0.0, "GetPrecExp: fAbsVal <= 0.0" );
+ if ( fAbsVal < 1e-7 || fAbsVal > 1e7 )
+ {
+ // Shear: whether it's faster or not, falls in between 1e6 and 1e7
+ return static_cast<tools::Long>(floor( log10( fAbsVal ) )) + 1;
+ }
+ else
+ {
+ tools::Long nPrecExp = 1;
+ while( fAbsVal < 1 )
+ {
+ fAbsVal *= 10;
+ nPrecExp--;
+ }
+ while( fAbsVal >= 10 )
+ {
+ fAbsVal /= 10;
+ nPrecExp++;
+ }
+ return nPrecExp;
+ }
+}
+
+/**
+ * SvNumberformatInfo
+ * */
+
+void ImpSvNumberformatInfo::Copy( const ImpSvNumberformatInfo& rNumFor, sal_uInt16 nCnt )
+{
+ for (sal_uInt16 i = 0; i < nCnt; ++i)
+ {
+ sStrArray[i] = rNumFor.sStrArray[i];
+ nTypeArray[i] = rNumFor.nTypeArray[i];
+ }
+ eScannedType = rNumFor.eScannedType;
+ bThousand = rNumFor.bThousand;
+ nThousand = rNumFor.nThousand;
+ nCntPre = rNumFor.nCntPre;
+ nCntPost = rNumFor.nCntPost;
+ nCntExp = rNumFor.nCntExp;
+}
+
+const std::map<LanguageType, std::array<sal_uInt8, 4>> tblDBNumToNatNum
+ = { { primary(LANGUAGE_CHINESE), { 4, 5, 3, 0 } },
+ { primary(LANGUAGE_JAPANESE), { 4, 5, 3, 0 } },
+ { primary(LANGUAGE_KOREAN), { 4, 5, 6, 10 } } };
+
+// static
+sal_uInt8 SvNumberNatNum::MapDBNumToNatNum( sal_uInt8 nDBNum, LanguageType eLang, bool bDate )
+{
+ sal_uInt8 nNatNum = 0;
+ eLang = MsLangId::getRealLanguage( eLang ); // resolve SYSTEM etc.
+ eLang = primary(eLang); // 10 bit primary language
+ if ( bDate )
+ {
+ if ( nDBNum == 4 && eLang == primary(LANGUAGE_KOREAN) )
+ {
+ nNatNum = 10;
+ }
+ else if ( nDBNum <= 3 )
+ {
+ nNatNum = nDBNum; // known to be good for: zh,ja,ko / 1,2,3
+ }
+ }
+ else
+ {
+ if (1 <= nDBNum && nDBNum <= 4)
+ {
+ auto const it = tblDBNumToNatNum.find(eLang);
+ if (it != tblDBNumToNatNum.end())
+ nNatNum = it->second[nDBNum - 1];
+
+ }
+ }
+ return nNatNum;
+}
+
+const std::map<LanguageType, std::array<sal_uInt8, 9>> tblNatNumToDBNum
+ = { { primary(LANGUAGE_CHINESE), { 1, 0, 0, 1, 2, 3, 0, 0, 0 } },
+ { primary(LANGUAGE_JAPANESE), { 1, 2, 3, 1, 2, 3, 1, 2, 0 } },
+ { primary(LANGUAGE_KOREAN), { 1, 2, 3, 1, 2, 3, 1, 2, 4 } } };
+
+// static
+sal_uInt8 SvNumberNatNum::MapNatNumToDBNum( sal_uInt8 nNatNum, LanguageType eLang, bool bDate )
+{
+ sal_uInt8 nDBNum = 0;
+ eLang = MsLangId::getRealLanguage( eLang ); // resolve SYSTEM etc.
+ eLang = primary(eLang); // 10 bit primary language
+ if ( bDate )
+ {
+ if ( nNatNum == 10 && eLang == primary(LANGUAGE_KOREAN) )
+ {
+ nDBNum = 4;
+ }
+ else if ( nNatNum <= 3 )
+ {
+ nDBNum = nNatNum; // known to be good for: zh,ja,ko / 1,2,3
+ }
+ }
+ else
+ {
+ if (1 <= nNatNum && nNatNum <= 9)
+ {
+ auto const it = tblNatNumToDBNum.find(eLang);
+ if (it != tblNatNumToDBNum.end())
+ nDBNum = it->second[nNatNum - 1];
+ }
+ }
+ return nDBNum;
+}
+
+/**
+ * SvNumFor
+ */
+
+ImpSvNumFor::ImpSvNumFor()
+{
+ nStringsCnt = 0;
+ aI.eScannedType = SvNumFormatType::UNDEFINED;
+ aI.bThousand = false;
+ aI.nThousand = 0;
+ aI.nCntPre = 0;
+ aI.nCntPost = 0;
+ aI.nCntExp = 0;
+ pColor = nullptr;
+}
+
+ImpSvNumFor::~ImpSvNumFor()
+{
+}
+
+void ImpSvNumFor::Enlarge(sal_uInt16 nCnt)
+{
+ if ( nStringsCnt != nCnt )
+ {
+ nStringsCnt = nCnt;
+ aI.nTypeArray.resize(nCnt);
+ aI.sStrArray.resize(nCnt);
+ }
+}
+
+void ImpSvNumFor::Copy( const ImpSvNumFor& rNumFor, const ImpSvNumberformatScan* pSc )
+{
+ Enlarge( rNumFor.nStringsCnt );
+ aI.Copy( rNumFor.aI, nStringsCnt );
+ sColorName = rNumFor.sColorName;
+ if ( pSc )
+ {
+ pColor = pSc->GetColor( sColorName ); // #121103# don't copy pointer between documents
+ }
+ else
+ {
+ pColor = rNumFor.pColor;
+ }
+ aNatNum = rNumFor.aNatNum;
+}
+
+bool ImpSvNumFor::HasNewCurrency() const
+{
+ for ( sal_uInt16 j=0; j<nStringsCnt; j++ )
+ {
+ if ( aI.nTypeArray[j] == NF_SYMBOLTYPE_CURRENCY )
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool ImpSvNumFor::GetNewCurrencySymbol( OUString& rSymbol,
+ OUString& rExtension ) const
+{
+ for ( sal_uInt16 j=0; j<nStringsCnt; j++ )
+ {
+ if ( aI.nTypeArray[j] == NF_SYMBOLTYPE_CURRENCY )
+ {
+ rSymbol = aI.sStrArray[j];
+ if ( j < nStringsCnt-1 && aI.nTypeArray[j+1] == NF_SYMBOLTYPE_CURREXT )
+ {
+ rExtension = aI.sStrArray[j+1];
+ }
+ else
+ {
+ rExtension.clear();
+ }
+ return true;
+ }
+ }
+ //! No Erase at rSymbol, rExtension
+ return false;
+}
+
+/**
+ * SvNumberformat
+ */
+
+namespace {
+
+enum BracketFormatSymbolType
+{
+ BRACKET_SYMBOLTYPE_FORMAT = -1, // subformat string
+ BRACKET_SYMBOLTYPE_COLOR = -2, // color
+ BRACKET_SYMBOLTYPE_ERROR = -3, // error
+ BRACKET_SYMBOLTYPE_DBNUM1 = -4, // DoubleByteNumber, represent numbers
+ BRACKET_SYMBOLTYPE_DBNUM2 = -5, // using CJK characters, Excel compatible
+ BRACKET_SYMBOLTYPE_DBNUM3 = -6,
+ BRACKET_SYMBOLTYPE_DBNUM4 = -7,
+ BRACKET_SYMBOLTYPE_DBNUM5 = -8,
+ BRACKET_SYMBOLTYPE_DBNUM6 = -9,
+ BRACKET_SYMBOLTYPE_DBNUM7 = -10,
+ BRACKET_SYMBOLTYPE_DBNUM8 = -11,
+ BRACKET_SYMBOLTYPE_DBNUM9 = -12,
+ BRACKET_SYMBOLTYPE_LOCALE = -13,
+ BRACKET_SYMBOLTYPE_NATNUM0 = -14, // Our NativeNumber support, ASCII
+ BRACKET_SYMBOLTYPE_NATNUM1 = -15, // Our NativeNumber support, represent
+ BRACKET_SYMBOLTYPE_NATNUM2 = -16, // numbers using CJK, CTL, ...
+ BRACKET_SYMBOLTYPE_NATNUM3 = -17,
+ BRACKET_SYMBOLTYPE_NATNUM4 = -18,
+ BRACKET_SYMBOLTYPE_NATNUM5 = -19,
+ BRACKET_SYMBOLTYPE_NATNUM6 = -20,
+ BRACKET_SYMBOLTYPE_NATNUM7 = -21,
+ BRACKET_SYMBOLTYPE_NATNUM8 = -22,
+ BRACKET_SYMBOLTYPE_NATNUM9 = -23,
+ BRACKET_SYMBOLTYPE_NATNUM10 = -24,
+ BRACKET_SYMBOLTYPE_NATNUM11 = -25,
+ BRACKET_SYMBOLTYPE_NATNUM12 = -26,
+ BRACKET_SYMBOLTYPE_NATNUM13 = -27,
+ BRACKET_SYMBOLTYPE_NATNUM14 = -28,
+ BRACKET_SYMBOLTYPE_NATNUM15 = -29,
+ BRACKET_SYMBOLTYPE_NATNUM16 = -30,
+ BRACKET_SYMBOLTYPE_NATNUM17 = -31,
+ BRACKET_SYMBOLTYPE_NATNUM18 = -32,
+ BRACKET_SYMBOLTYPE_NATNUM19 = -33
+};
+
+}
+
+void SvNumberformat::ImpCopyNumberformat( const SvNumberformat& rFormat )
+{
+ sFormatstring = rFormat.sFormatstring;
+ eType = rFormat.eType;
+ maLocale = rFormat.maLocale;
+ fLimit1 = rFormat.fLimit1;
+ fLimit2 = rFormat.fLimit2;
+ eOp1 = rFormat.eOp1;
+ eOp2 = rFormat.eOp2;
+ bStandard = rFormat.bStandard;
+ bIsUsed = rFormat.bIsUsed;
+ sComment = rFormat.sComment;
+ bAdditionalBuiltin = rFormat.bAdditionalBuiltin;
+
+ // #121103# when copying between documents, get color pointers from own scanner
+ ImpSvNumberformatScan* pColorSc = ( &rScan != &rFormat.rScan ) ? &rScan : nullptr;
+
+ for (sal_uInt16 i = 0; i < 4; i++)
+ {
+ NumFor[i].Copy(rFormat.NumFor[i], pColorSc);
+ }
+}
+
+SvNumberformat::SvNumberformat( SvNumberformat const & rFormat )
+ : rScan(rFormat.rScan), bStarFlag( rFormat.bStarFlag )
+{
+ ImpCopyNumberformat( rFormat );
+}
+
+SvNumberformat::SvNumberformat( SvNumberformat const & rFormat, ImpSvNumberformatScan& rSc )
+ : rScan(rSc)
+ , bStarFlag( rFormat.bStarFlag )
+{
+ ImpCopyNumberformat( rFormat );
+}
+
+static bool lcl_SvNumberformat_IsBracketedPrefix( short nSymbolType )
+{
+ if ( nSymbolType > 0 )
+ {
+ return true; // conditions
+ }
+ switch ( nSymbolType )
+ {
+ case BRACKET_SYMBOLTYPE_COLOR :
+ case BRACKET_SYMBOLTYPE_DBNUM1 :
+ case BRACKET_SYMBOLTYPE_DBNUM2 :
+ case BRACKET_SYMBOLTYPE_DBNUM3 :
+ case BRACKET_SYMBOLTYPE_DBNUM4 :
+ case BRACKET_SYMBOLTYPE_DBNUM5 :
+ case BRACKET_SYMBOLTYPE_DBNUM6 :
+ case BRACKET_SYMBOLTYPE_DBNUM7 :
+ case BRACKET_SYMBOLTYPE_DBNUM8 :
+ case BRACKET_SYMBOLTYPE_DBNUM9 :
+ case BRACKET_SYMBOLTYPE_LOCALE :
+ case BRACKET_SYMBOLTYPE_NATNUM0 :
+ case BRACKET_SYMBOLTYPE_NATNUM1 :
+ case BRACKET_SYMBOLTYPE_NATNUM2 :
+ case BRACKET_SYMBOLTYPE_NATNUM3 :
+ case BRACKET_SYMBOLTYPE_NATNUM4 :
+ case BRACKET_SYMBOLTYPE_NATNUM5 :
+ case BRACKET_SYMBOLTYPE_NATNUM6 :
+ case BRACKET_SYMBOLTYPE_NATNUM7 :
+ case BRACKET_SYMBOLTYPE_NATNUM8 :
+ case BRACKET_SYMBOLTYPE_NATNUM9 :
+ case BRACKET_SYMBOLTYPE_NATNUM10 :
+ case BRACKET_SYMBOLTYPE_NATNUM11 :
+ case BRACKET_SYMBOLTYPE_NATNUM12 :
+ case BRACKET_SYMBOLTYPE_NATNUM13 :
+ case BRACKET_SYMBOLTYPE_NATNUM14 :
+ case BRACKET_SYMBOLTYPE_NATNUM15 :
+ case BRACKET_SYMBOLTYPE_NATNUM16 :
+ case BRACKET_SYMBOLTYPE_NATNUM17 :
+ case BRACKET_SYMBOLTYPE_NATNUM18 :
+ case BRACKET_SYMBOLTYPE_NATNUM19 :
+ return true;
+ }
+ return false;
+}
+
+/** Import extended LCID from Excel
+ */
+OUString SvNumberformat::ImpObtainCalendarAndNumerals( OUStringBuffer& rString, sal_Int32 nPos,
+ LanguageType& nLang, const LocaleType& aTmpLocale )
+{
+ OUString sCalendar;
+ sal_uInt16 nNatNum = 0;
+ LanguageType nLocaleLang = MsLangId::getRealLanguage( maLocale.meLanguage );
+ LanguageType nTmpLocaleLang = MsLangId::getRealLanguage( aTmpLocale.meLanguage );
+ /* NOTE: enhancement to allow other possible locale dependent
+ * calendars and numerals. BUT only if our locale data allows it! For LCID
+ * numerals and calendars see
+ * http://office.microsoft.com/en-us/excel/HA010346351033.aspx
+ * Calendar is inserted after
+ * all prefixes have been consumed as it is actually a format modifier
+ * and not a prefix.
+ * Currently calendars are tied to the locale of the entire number
+ * format, e.g. [~buddhist] in en_US doesn't work.
+ * => Having different locales in sub formats does not work!
+ * */
+ /* TODO: calendars could be tied to a sub format's NatNum info
+ * instead, or even better be available for any locale. Needs a
+ * different implementation of GetCal() and locale data calendars.
+ * */
+ switch ( aTmpLocale.mnCalendarType & 0x7F )
+ {
+ case 0x03 : // Gengou calendar
+ // Only Japanese language support Gengou calendar.
+ // It is an implicit "other" calendar where E, EE, R and RR
+ // automatically switch to and YY and YYYY switch to Gregorian. Do
+ // not add the "[~gengou]" modifier.
+ if ( nLocaleLang != LANGUAGE_JAPANESE )
+ {
+ nLang = maLocale.meLanguage = LANGUAGE_JAPANESE;
+ }
+ break;
+ case 0x05 : // Korean Dangi calendar
+ sCalendar = "[~dangi]";
+ // Only Korean language support dangi calendar
+ if ( nLocaleLang != LANGUAGE_KOREAN )
+ {
+ nLang = maLocale.meLanguage = LANGUAGE_KOREAN;
+ }
+ break;
+ case 0x06 : // Hijri calendar
+ case 0x17 : // same?
+ sCalendar = "[~hijri]";
+ // Only Arabic or Farsi languages support Hijri calendar
+ if ( ( primary( nLocaleLang ) != LANGUAGE_ARABIC_PRIMARY_ONLY )
+ && nLocaleLang != LANGUAGE_FARSI )
+ {
+ if ( ( primary( nTmpLocaleLang ) == LANGUAGE_ARABIC_PRIMARY_ONLY )
+ || nTmpLocaleLang == LANGUAGE_FARSI )
+ {
+ nLang = maLocale.meLanguage = aTmpLocale.meLanguage;
+ }
+ else
+ {
+ nLang = maLocale.meLanguage = LANGUAGE_ARABIC_SAUDI_ARABIA;
+ }
+ }
+ break;
+ case 0x07 : // Buddhist calendar
+ sCalendar="[~buddhist]";
+ // Only Thai or Lao languages support Buddhist calendar
+ if ( nLocaleLang != LANGUAGE_THAI && nLocaleLang != LANGUAGE_LAO )
+ {
+ if ( nTmpLocaleLang == LANGUAGE_THAI || nTmpLocaleLang == LANGUAGE_LAO )
+ {
+ nLang = maLocale.meLanguage = aTmpLocale.meLanguage;
+ }
+ else
+ {
+ nLang = maLocale.meLanguage = LANGUAGE_THAI;
+ }
+ }
+ break;
+ case 0x08 : // Hebrew calendar
+ sCalendar = "[~jewish]";
+ // Many languages (but not all) support Jewish calendar
+ // Unable to find any logic => keep same language
+ break;
+ case 0x0E : // unknown calendar
+ case 0x0F : // unknown calendar
+ case 0x10 : // Indian calendar (unsupported)
+ case 0x11 : // unknown calendar
+ case 0x12 : // unknown calendar
+ case 0x13 : // unknown calendar
+ default : // other calendars (see tdf#36038) are not handle by LibO
+ break;
+ }
+ /** Reference language for each numeral ID */
+ static const LanguageType aNumeralIDtoLanguage []=
+ {
+ LANGUAGE_DONTKNOW, // 0x00
+ LANGUAGE_ENGLISH_US, // 0x01
+ LANGUAGE_ARABIC_SAUDI_ARABIA, // 0x02 + all Arabic
+ LANGUAGE_FARSI, // 0x03
+ LANGUAGE_HINDI, // 0x04 + Devanagari
+ LANGUAGE_BENGALI, // 0x05
+ LANGUAGE_PUNJABI, // 0x06
+ LANGUAGE_GUJARATI, // 0x07
+ LANGUAGE_ODIA, // 0x08
+ LANGUAGE_TAMIL, // 0x09
+ LANGUAGE_TELUGU, // 0x0A
+ LANGUAGE_KANNADA, // 0x0B
+ LANGUAGE_MALAYALAM, // 0x0C
+ LANGUAGE_THAI, // 0x0D
+ LANGUAGE_LAO, // 0x0E
+ LANGUAGE_TIBETAN, // 0x0F
+ LANGUAGE_BURMESE, // 0x10
+ LANGUAGE_TIGRIGNA_ETHIOPIA, // 0x11
+ LANGUAGE_KHMER, // 0x12
+ LANGUAGE_MONGOLIAN_MONGOLIAN_MONGOLIA, // 0x13
+ LANGUAGE_DONTKNOW, // 0x14
+ LANGUAGE_DONTKNOW, // 0x15
+ LANGUAGE_DONTKNOW, // 0x16
+ LANGUAGE_DONTKNOW, // 0x17
+ LANGUAGE_DONTKNOW, // 0x18
+ LANGUAGE_DONTKNOW, // 0x19
+ LANGUAGE_DONTKNOW, // 0x1A
+ LANGUAGE_JAPANESE, // 0x1B
+ LANGUAGE_JAPANESE, // 0x1C
+ LANGUAGE_JAPANESE, // 0x1D
+ LANGUAGE_CHINESE_SIMPLIFIED, // 0x1E
+ LANGUAGE_CHINESE_SIMPLIFIED, // 0x1F
+ LANGUAGE_CHINESE_SIMPLIFIED, // 0x20
+ LANGUAGE_CHINESE_TRADITIONAL, // 0x21
+ LANGUAGE_CHINESE_TRADITIONAL, // 0x22
+ LANGUAGE_CHINESE_TRADITIONAL, // 0x23
+ LANGUAGE_KOREAN, // 0x24
+ LANGUAGE_KOREAN, // 0x25
+ LANGUAGE_KOREAN, // 0x26
+ LANGUAGE_KOREAN // 0x27
+ };
+
+ sal_uInt16 nNumeralID = aTmpLocale.mnNumeralShape & 0x7F;
+ LanguageType nReferenceLanguage = nNumeralID <= 0x27 ? aNumeralIDtoLanguage[nNumeralID] : LANGUAGE_DONTKNOW;
+
+ switch ( nNumeralID )
+ {
+ // Regular cases: all languages with same primary mask use same numerals
+ case 0x03 : // Perso-Arabic (Farsi) numerals
+ case 0x05 : // Bengali numerals
+ case 0x06 : // Punjabi numerals
+ case 0x07 : // Gujarati numerals
+ case 0x08 : // Odia (Orya) numerals
+ case 0x09 : // Tamil numerals
+ case 0x0A : // Telugu numerals
+ case 0x0B : // Kannada numerals
+ case 0x0C : // Malayalam numerals
+ case 0x0D : // Thai numerals
+ case 0x0E : // Lao numerals
+ case 0x0F : // Tibetan numerals
+ case 0x10 : // Burmese (Myanmar) numerals
+ case 0x11 : // Tigrigna (Ethiopia) numerals
+ case 0x12 : // Khmer numerals
+ if ( primary( nLocaleLang ) != primary( nReferenceLanguage ) )
+ {
+ if ( primary( nTmpLocaleLang ) == primary( nReferenceLanguage ) )
+ {
+ nLang = maLocale.meLanguage = aTmpLocale.meLanguage;
+ }
+ else
+ {
+ nLang = maLocale.meLanguage = nReferenceLanguage;
+ }
+ }
+ break;
+ // Special cases
+ case 0x04 : // Devanagari (Hindi) numerals
+ // same numerals (Devanagari) for languages with different primary masks
+ if ( nLocaleLang != LANGUAGE_HINDI && nLocaleLang != LANGUAGE_MARATHI
+ && primary( nLocaleLang ) != primary( LANGUAGE_NEPALI ) )
+ {
+ if ( nTmpLocaleLang == LANGUAGE_HINDI || nTmpLocaleLang == LANGUAGE_MARATHI
+ || primary( nTmpLocaleLang ) == primary( LANGUAGE_NEPALI ) )
+ {
+ nLang = maLocale.meLanguage = aTmpLocale.meLanguage;
+ }
+ else
+ {
+ nLang = maLocale.meLanguage = LANGUAGE_HINDI;
+ }
+ }
+ break;
+ case 0x13 : // Mongolian numerals
+ // not all Mongolian languages use Mongolian numerals
+ if ( nLocaleLang != LANGUAGE_MONGOLIAN_MONGOLIAN_MONGOLIA
+ && nLocaleLang != LANGUAGE_MONGOLIAN_MONGOLIAN_CHINA
+ && nLocaleLang != LANGUAGE_MONGOLIAN_MONGOLIAN_LSO )
+ {
+ if ( nTmpLocaleLang == LANGUAGE_MONGOLIAN_MONGOLIAN_MONGOLIA
+ || nTmpLocaleLang == LANGUAGE_MONGOLIAN_MONGOLIAN_CHINA
+ || nTmpLocaleLang == LANGUAGE_MONGOLIAN_MONGOLIAN_LSO )
+ {
+ nLang = maLocale.meLanguage = aTmpLocale.meLanguage;
+ }
+ else
+ {
+ nLang = maLocale.meLanguage = LANGUAGE_MONGOLIAN_MONGOLIAN_MONGOLIA;
+ }
+ }
+ break;
+ case 0x02 : // Eastern-Arabic numerals
+ // all arabic primary mask + LANGUAGE_PUNJABI_ARABIC_LSO
+ if ( primary( nLocaleLang ) != LANGUAGE_ARABIC_PRIMARY_ONLY
+ && nLocaleLang != LANGUAGE_PUNJABI_ARABIC_LSO )
+ {
+ if ( primary( nTmpLocaleLang ) == LANGUAGE_ARABIC_PRIMARY_ONLY
+ || nTmpLocaleLang != LANGUAGE_PUNJABI_ARABIC_LSO )
+ {
+ nLang = maLocale.meLanguage = aTmpLocale.meLanguage;
+ }
+ else
+ {
+ nLang = maLocale.meLanguage = nReferenceLanguage;
+ }
+ }
+ break;
+ // CJK numerals
+ case 0x1B : // simple Asian numerals, Japanese
+ case 0x1C : // financial Asian numerals, Japanese
+ case 0x1D : // Arabic fullwidth numerals, Japanese
+ case 0x24 : // simple Asian numerals, Korean
+ case 0x25 : // financial Asian numerals, Korean
+ case 0x26 : // Arabic fullwidth numerals, Korean
+ case 0x27 : // Korean Hangul numerals
+ // Japanese and Korean are regular
+ if ( primary( nLocaleLang ) != primary( nReferenceLanguage ) )
+ {
+ if ( primary( nTmpLocaleLang ) == primary( nReferenceLanguage ) )
+ {
+ nLang = maLocale.meLanguage = aTmpLocale.meLanguage;
+ }
+ else
+ {
+ nLang = maLocale.meLanguage = nReferenceLanguage;
+ }
+ }
+ [[fallthrough]];
+ case 0x1E : // simple Asian numerals, Chinese-PRC
+ case 0x1F : // financial Asian numerals, Chinese-PRC
+ case 0x20 : // Arabic fullwidth numerals, Chinese-PRC
+ case 0x21 : // simple Asian numerals, Chinese-Taiwan
+ case 0x22 : // financial Asian numerals, Chinese-Taiwan
+ case 0x23 : // Arabic fullwidth numerals, Chinese-Taiwan
+ nNatNum = nNumeralID == 0x27 ? 9 : ( ( nNumeralID - 0x1B ) % 3 ) + 1;
+ // [NatNum1] simple numerals
+ // [natNum2] financial numerals
+ // [NatNum3] Arabic fullwidth numerals
+ // Chinese simplified and Chinese traditional have same primary mask
+ // Chinese-PRC
+ if ( nReferenceLanguage == LANGUAGE_CHINESE_SIMPLIFIED
+ && nLocaleLang != LANGUAGE_CHINESE_SIMPLIFIED
+ && nLocaleLang != LANGUAGE_CHINESE_SINGAPORE
+ && nLocaleLang != LANGUAGE_CHINESE_LSO )
+ {
+ if ( nTmpLocaleLang == LANGUAGE_CHINESE_SIMPLIFIED
+ || nTmpLocaleLang == LANGUAGE_CHINESE_SINGAPORE
+ || nTmpLocaleLang == LANGUAGE_CHINESE_LSO )
+ {
+ nLang = maLocale.meLanguage = aTmpLocale.meLanguage;
+ }
+ else
+ {
+ nLang = maLocale.meLanguage = LANGUAGE_CHINESE_SIMPLIFIED;
+ }
+ }
+ // Chinese-Taiwan
+ else if ( nReferenceLanguage == LANGUAGE_CHINESE_TRADITIONAL
+ && nLocaleLang != LANGUAGE_CHINESE_TRADITIONAL
+ && nLocaleLang != LANGUAGE_CHINESE_HONGKONG
+ && nLocaleLang != LANGUAGE_CHINESE_MACAU )
+ {
+ if ( nTmpLocaleLang == LANGUAGE_CHINESE_TRADITIONAL
+ || nTmpLocaleLang == LANGUAGE_CHINESE_HONGKONG
+ || nTmpLocaleLang == LANGUAGE_CHINESE_MACAU )
+ {
+ nLang = maLocale.meLanguage = aTmpLocale.meLanguage;
+ }
+ else
+ {
+ nLang = maLocale.meLanguage = LANGUAGE_CHINESE_TRADITIONAL;
+ }
+ }
+ break;
+ }
+ if ( nNumeralID >= 0x02 && nNumeralID <= 0x13 )
+ nNatNum = 1;
+ if ( nNatNum )
+ rString.insert(nPos, "[NatNum" + OUString::number(nNatNum) + "]");
+ return sCalendar;
+}
+
+namespace
+{
+bool NatNumTakesParameters(sal_Int16 nNum)
+{
+ return (nNum == css::i18n::NativeNumberMode::NATNUM12);
+}
+}
+
+// is there a 3-letter bank code in NatNum12 param (but not
+// followed by an equal mark, like in the date code "NNN=")?
+static bool lcl_isNatNum12Currency( const OUString& sParam )
+{
+ sal_Int32 nUpper = 0;
+ sal_Int32 nLen = sParam.getLength();
+ for (sal_Int32 n = 0; n < nLen; ++n)
+ {
+ sal_Unicode c = sParam[n];
+ if ( 'A' <= c && c <= 'Z' )
+ {
+ ++nUpper;
+ }
+ else if ( c == ' ' && nUpper == 3 && (n == 3 || sParam[n - 4] == ' ') )
+ {
+ return true;
+ }
+ else
+ {
+ nUpper = 0;
+ }
+ }
+
+ return nUpper == 3 && (nLen == 3 || sParam[nLen - 4] == ' ');
+}
+
+SvNumberformat::SvNumberformat(OUString& rString,
+ ImpSvNumberformatScan* pSc,
+ ImpSvNumberInputScan* pISc,
+ sal_Int32& nCheckPos,
+ LanguageType& eLan,
+ bool bReplaceBooleanEquivalent)
+ : rScan(*pSc)
+ , bAdditionalBuiltin( false )
+ , bStarFlag( false )
+{
+ if (bReplaceBooleanEquivalent)
+ rScan.ReplaceBooleanEquivalent( rString);
+
+ OUStringBuffer sBuff(rString);
+
+ // If the group (AKA thousand) separator is a No-Break Space (French)
+ // replace all occurrences by a simple space.
+ // The same for Narrow No-Break Space just in case some locale uses it.
+ // The tokens will be changed to the LocaleData separator again later on.
+ const OUString& rThSep = GetFormatter().GetNumThousandSep();
+ if ( rThSep.getLength() == 1)
+ {
+ const sal_Unicode cNBSp = 0xA0;
+ const sal_Unicode cNNBSp = 0x202F;
+ if (rThSep[0] == cNBSp )
+ sBuff.replace( cNBSp, ' ');
+ else if (rThSep[0] == cNNBSp )
+ sBuff.replace( cNNBSp, ' ');
+ }
+
+ OUString aConvertFromDecSep;
+ OUString aConvertToDecSep;
+ if (rScan.GetConvertMode())
+ {
+ aConvertFromDecSep = GetFormatter().GetNumDecimalSep();
+ maLocale.meLanguage = rScan.GetNewLnge();
+ eLan = maLocale.meLanguage; // Make sure to return switch
+ }
+ else
+ {
+ maLocale.meLanguage = eLan;
+ }
+ bStandard = false;
+ bIsUsed = false;
+ fLimit1 = 0.0;
+ fLimit2 = 0.0;
+ eOp1 = NUMBERFORMAT_OP_NO;
+ eOp2 = NUMBERFORMAT_OP_NO;
+ eType = SvNumFormatType::DEFINED;
+
+ bool bCancel = false;
+ bool bCondition = false;
+ short eSymbolType;
+ sal_Int32 nPos = 0;
+ sal_Int32 nPosOld;
+ nCheckPos = 0;
+
+ // Split into 4 sub formats
+ sal_uInt16 nIndex;
+ for ( nIndex = 0; nIndex < 4 && !bCancel; nIndex++ )
+ {
+ // Original language/country may have to be reestablished
+ if (rScan.GetConvertMode())
+ {
+ rScan.GetNumberformatter()->ChangeIntl(rScan.GetTmpLnge());
+ }
+ OUString sInsertCalendar; // a calendar resulting from parsing LCID
+ OUString sStr;
+ nPosOld = nPos; // Start position of substring
+ // first get bracketed prefixes; e.g. conditions, color
+ do
+ {
+ eSymbolType = ImpNextSymbol(sBuff, nPos, sStr);
+ if (eSymbolType > 0) // condition
+ {
+ if ( nIndex == 0 && !bCondition )
+ {
+ bCondition = true;
+ eOp1 = static_cast<SvNumberformatLimitOps>(eSymbolType);
+ }
+ else if ( nIndex == 1 && bCondition )
+ {
+ eOp2 = static_cast<SvNumberformatLimitOps>(eSymbolType);
+ }
+ else // error
+ {
+ bCancel = true; // break for
+ nCheckPos = nPosOld;
+ }
+ if (!bCancel)
+ {
+ double fNumber;
+ sal_Int32 nCntChars = ImpGetNumber(sBuff, nPos, sStr);
+ if (nCntChars > 0)
+ {
+ sal_Int32 nDecPos;
+ SvNumFormatType F_Type = SvNumFormatType::UNDEFINED;
+ if (!pISc->IsNumberFormat(sStr, F_Type, fNumber, nullptr, SvNumInputOptions::NONE) ||
+ ( F_Type != SvNumFormatType::NUMBER &&
+ F_Type != SvNumFormatType::SCIENTIFIC) )
+ {
+ fNumber = 0.0;
+ nPos = nPos - nCntChars;
+ sBuff.remove(nPos, nCntChars);
+ sBuff.insert(nPos, '0');
+ nPos++;
+ }
+ else if (rScan.GetConvertMode() && ((nDecPos = sStr.indexOf( aConvertFromDecSep)) >= 0))
+ {
+ if (aConvertToDecSep.isEmpty())
+ aConvertToDecSep = GetFormatter().GetLangDecimalSep( rScan.GetNewLnge());
+ if (aConvertToDecSep != aConvertFromDecSep)
+ {
+ const OUString aStr( sStr.replaceAt( nDecPos,
+ aConvertFromDecSep.getLength(), aConvertToDecSep));
+ nPos = nPos - nCntChars;
+ sBuff.remove(nPos, nCntChars);
+ sBuff.insert(nPos, aStr);
+ nPos += aStr.getLength();
+ }
+ }
+ }
+ else
+ {
+ fNumber = 0.0;
+ sBuff.insert(nPos++, '0');
+ }
+ if (nIndex == 0)
+ {
+ fLimit1 = fNumber;
+ }
+ else
+ {
+ fLimit2 = fNumber;
+ }
+ if ( nPos < sBuff.getLength() && sBuff[nPos] == ']' )
+ {
+ nPos++;
+ }
+ else
+ {
+ bCancel = true; // break for
+ nCheckPos = nPos;
+ }
+ }
+ nPosOld = nPos; // position before string
+ }
+ else if ( lcl_SvNumberformat_IsBracketedPrefix( eSymbolType ) )
+ {
+ OUString sSymbol( sStr);
+ switch ( eSymbolType )
+ {
+ case BRACKET_SYMBOLTYPE_COLOR :
+ if ( NumFor[nIndex].GetColor() != nullptr )
+ { // error, more than one color
+ bCancel = true; // break for
+ nCheckPos = nPosOld;
+ }
+ else
+ {
+ const Color* pColor = pSc->GetColor( sStr);
+ NumFor[nIndex].SetColor( pColor, sStr);
+ if (pColor == nullptr)
+ { // error
+ bCancel = true; // break for
+ nCheckPos = nPosOld;
+ }
+ }
+ break;
+ case BRACKET_SYMBOLTYPE_NATNUM0 :
+ case BRACKET_SYMBOLTYPE_NATNUM1 :
+ case BRACKET_SYMBOLTYPE_NATNUM2 :
+ case BRACKET_SYMBOLTYPE_NATNUM3 :
+ case BRACKET_SYMBOLTYPE_NATNUM4 :
+ case BRACKET_SYMBOLTYPE_NATNUM5 :
+ case BRACKET_SYMBOLTYPE_NATNUM6 :
+ case BRACKET_SYMBOLTYPE_NATNUM7 :
+ case BRACKET_SYMBOLTYPE_NATNUM8 :
+ case BRACKET_SYMBOLTYPE_NATNUM9 :
+ case BRACKET_SYMBOLTYPE_NATNUM10 :
+ case BRACKET_SYMBOLTYPE_NATNUM11 :
+ case BRACKET_SYMBOLTYPE_NATNUM12 :
+ case BRACKET_SYMBOLTYPE_NATNUM13 :
+ case BRACKET_SYMBOLTYPE_NATNUM14 :
+ case BRACKET_SYMBOLTYPE_NATNUM15 :
+ case BRACKET_SYMBOLTYPE_NATNUM16 :
+ case BRACKET_SYMBOLTYPE_NATNUM17 :
+ case BRACKET_SYMBOLTYPE_NATNUM18 :
+ case BRACKET_SYMBOLTYPE_NATNUM19 :
+ if ( NumFor[nIndex].GetNatNum().IsSet() )
+ {
+ bCancel = true; // break for
+ nCheckPos = nPosOld;
+ }
+ else
+ {
+ OUString sParams;
+ sal_Int32 nSpacePos = sStr.indexOf(' ');
+ if (nSpacePos >= 0)
+ {
+ sParams = o3tl::trim(sStr.subView(nSpacePos+1));
+ }
+ //! eSymbolType is negative
+ sal_uInt8 nNum = static_cast<sal_uInt8>(0 - (eSymbolType - BRACKET_SYMBOLTYPE_NATNUM0));
+ if (!sParams.isEmpty() && !NatNumTakesParameters(nNum))
+ {
+ bCancel = true; // break for
+ nCheckPos = nPosOld;
+ break;
+ }
+ sStr = "NatNum" + OUString::number(nNum);
+ NumFor[nIndex].SetNatNumNum( nNum, false );
+ // NatNum12 supports arguments
+ if (nNum == 12)
+ {
+ if (sParams.isEmpty())
+ sParams = "cardinal"; // default NatNum12 format is "cardinal"
+ else if (sParams.indexOf("CURRENCY") >= 0)
+ sParams = sParams.replaceAll("CURRENCY",
+ rLoc().getCurrBankSymbol());
+ NumFor[nIndex].SetNatNumParams(sParams);
+ sStr += " " + sParams;
+ }
+ }
+ break;
+ case BRACKET_SYMBOLTYPE_DBNUM1 :
+ case BRACKET_SYMBOLTYPE_DBNUM2 :
+ case BRACKET_SYMBOLTYPE_DBNUM3 :
+ case BRACKET_SYMBOLTYPE_DBNUM4 :
+ case BRACKET_SYMBOLTYPE_DBNUM5 :
+ case BRACKET_SYMBOLTYPE_DBNUM6 :
+ case BRACKET_SYMBOLTYPE_DBNUM7 :
+ case BRACKET_SYMBOLTYPE_DBNUM8 :
+ case BRACKET_SYMBOLTYPE_DBNUM9 :
+ if ( NumFor[nIndex].GetNatNum().IsSet() )
+ {
+ bCancel = true; // break for
+ nCheckPos = nPosOld;
+ }
+ else
+ {
+ //! eSymbolType is negative
+ sal_uInt8 nNum = static_cast<sal_uInt8>(1 - (eSymbolType - BRACKET_SYMBOLTYPE_DBNUM1));
+ sStr = "DBNum" + OUStringChar(sal_Unicode('0' + nNum));
+ NumFor[nIndex].SetNatNumNum( nNum, true );
+ }
+ break;
+ case BRACKET_SYMBOLTYPE_LOCALE :
+ if ( NumFor[nIndex].GetNatNum().GetLang() != LANGUAGE_DONTKNOW ||
+ sBuff[nPos-1] != ']' )
+ // Check also for ']' to avoid pulling in
+ // locale data for the preview string for not
+ // yet completed LCIDs in the dialog.
+ {
+ bCancel = true; // break for
+ nCheckPos = nPosOld;
+ }
+ else
+ {
+ sal_Int32 nTmp = 2;
+ LocaleType aTmpLocale( ImpGetLocaleType( sStr, nTmp));
+ if (aTmpLocale.meLanguage == LANGUAGE_DONTKNOW)
+ {
+ bCancel = true; // break for
+ nCheckPos = nPosOld;
+ }
+ else
+ {
+ // Only the first sub format's locale will be
+ // used as the format's overall locale.
+ // Sorts this also under the corresponding
+ // locale for the dialog.
+ // If we don't support the locale this would
+ // result in an unknown (empty) language
+ // listbox entry and the user would never see
+ // this format.
+ if (nIndex == 0 && (aTmpLocale.meLanguage == LANGUAGE_SYSTEM ||
+ SvNumberFormatter::IsLocaleInstalled( aTmpLocale.meLanguage)))
+ {
+ maLocale = aTmpLocale;
+ eLan = aTmpLocale.meLanguage; // return to caller
+
+ // Set new target locale also at scanner.
+ // We have to do this because switching locale
+ // may make replacing keywords and separators
+ // necessary.
+ // We can do this because it's the first
+ // subformat and we're still at parsing the
+ // modifiers, not keywords.
+ rScan.SetNewLnge( eLan);
+ // We can not force conversion though because
+ // the caller may have explicitly not set it.
+ // In the usual case the target locale is the
+ // originating locale the conversion is not
+ // necessary, when reading alien documents
+ // conversion is enabled anyway.
+
+ /* TODO: fiddle with scanner to make this
+ * known? A change in the locale may affect
+ * separators and keywords. On the other
+ * hand they may have been entered as used
+ * in the originating locale, there's no
+ * way to predict other than analyzing the
+ * format code, we assume here the current
+ * context is used, which is most likely
+ * the case.
+ * */
+
+ // Strip a plain locale identifier if locale
+ // data is available to avoid duplicated
+ // formats with and without LCID for the same
+ // locale. Besides it looks ugly and confusing
+ // and is unnecessary as the format will be
+ // listed for the resulting locale.
+ if (aTmpLocale.isPlainLocale())
+ sStr.clear();
+ else
+ sStr = "$-" + aTmpLocale.generateCode();
+ }
+ else
+ {
+ if (nIndex == 0)
+ // Locale data not available, remember.
+ maLocale.meLanguageWithoutLocaleData = aTmpLocale.meLanguage;
+
+ sStr = "$-" + aTmpLocale.generateCode();
+ }
+ NumFor[nIndex].SetNatNumLang( MsLangId::getRealLanguage( aTmpLocale.meLanguage));
+
+ // "$-NNCCLLLL" Numerals and Calendar
+ if (sSymbol.getLength() > 6)
+ {
+ sInsertCalendar = ImpObtainCalendarAndNumerals( sBuff, nPos, eLan, aTmpLocale);
+ }
+ /* NOTE: there can be only one calendar
+ * inserted so the last one wins, though
+ * our own calendar modifiers support
+ * multiple calendars within one sub format
+ * code if at different positions. */
+ }
+ }
+ break;
+ }
+ if ( !bCancel )
+ {
+ if (sStr == sSymbol)
+ {
+ nPosOld = nPos;
+ }
+ else
+ {
+ sBuff.remove(nPosOld, nPos - nPosOld);
+ if (!sStr.isEmpty())
+ {
+ sBuff.insert(nPosOld, "[" + sStr + "]");
+ nPos = nPosOld + sStr.getLength() + 2;
+ nPosOld = nPos; // position before string
+ }
+ else
+ {
+ nPos = nPosOld; // prefix removed for whatever reason
+ }
+ }
+ }
+ }
+ }
+ while ( !bCancel && lcl_SvNumberformat_IsBracketedPrefix( eSymbolType ) );
+
+ // The remaining format code string
+ if ( !bCancel )
+ {
+ if (eSymbolType == BRACKET_SYMBOLTYPE_FORMAT)
+ {
+ if (nIndex == 1 && eOp1 == NUMBERFORMAT_OP_NO)
+ {
+ eOp1 = NUMBERFORMAT_OP_GT; // undefined condition, default: > 0
+ }
+ else if (nIndex == 2 && eOp2 == NUMBERFORMAT_OP_NO)
+ {
+ eOp2 = NUMBERFORMAT_OP_LT; // undefined condition, default: < 0
+ }
+ if (sStr.isEmpty())
+ {
+ // Empty sub format.
+ NumFor[nIndex].Info().eScannedType = SvNumFormatType::EMPTY;
+ }
+ else
+ {
+ if (!sInsertCalendar.isEmpty())
+ {
+ sStr = sInsertCalendar + sStr;
+ }
+ sal_Int32 nStrPos = pSc->ScanFormat( sStr);
+ sal_uInt16 nCnt = pSc->GetResultStringsCnt();
+ if (nCnt == 0 && nStrPos == 0) // error
+ {
+ nStrPos = 1;
+ }
+ if (nStrPos == 0) // ok
+ {
+ // e.g. Thai T speciality
+ if (pSc->GetNatNumModifier() && !NumFor[nIndex].GetNatNum().IsSet())
+ {
+ sStr = "[NatNum" + OUString::number( pSc->GetNatNumModifier()) + "]" + sStr;
+ NumFor[nIndex].SetNatNumNum( pSc->GetNatNumModifier(), false );
+ }
+ // #i53826# #i42727# For the Thai T speciality we need
+ // to freeze the locale and immunize it against
+ // conversions during exports, just in case we want to
+ // save to Xcl. This disables the feature of being able
+ // to convert a NatNum to another locale. You can't
+ // have both.
+ // FIXME: implement a specialized export conversion
+ // that works on tokens (have to tokenize all first)
+ // and doesn't use the format string and
+ // PutandConvertEntry() to LANGUAGE_ENGLISH_US in
+ // sc/source/filter/excel/xestyle.cxx
+ // XclExpNumFmtBuffer::WriteFormatRecord().
+ LanguageType eLanguage;
+ if (NumFor[nIndex].GetNatNum().GetNatNum() == 1 &&
+ ((eLanguage = MsLangId::getRealLanguage( eLan)) == LANGUAGE_THAI) &&
+ NumFor[nIndex].GetNatNum().GetLang() == LANGUAGE_DONTKNOW)
+ {
+ sStr = "[$-" + OUString::number( sal_uInt16(eLanguage), 16 ).toAsciiUpperCase() + "]" + sStr;
+ NumFor[nIndex].SetNatNumLang( eLanguage);
+ }
+ sBuff.remove(nPosOld, nPos - nPosOld);
+ sBuff.insert(nPosOld, sStr);
+ nPos = nPosOld + sStr.getLength();
+ if (nPos < sBuff.getLength())
+ {
+ sBuff.insert(nPos, ";");
+ nPos++;
+ }
+ else if (nIndex > 0)
+ {
+ // The last subformat. If it is a trailing text
+ // format the omitted subformats act like they were
+ // not specified and "inherited" the first format,
+ // e.g. 0;@ behaves like 0;-0;0;@
+ if (pSc->GetScannedType() == SvNumFormatType::TEXT)
+ {
+ // Reset conditions, reverting any set above.
+ if (nIndex == 1)
+ eOp1 = NUMBERFORMAT_OP_NO;
+ else if (nIndex == 2)
+ eOp2 = NUMBERFORMAT_OP_NO;
+ nIndex = 3;
+ }
+ }
+ NumFor[nIndex].Enlarge(nCnt);
+ pSc->CopyInfo(&(NumFor[nIndex].Info()), nCnt);
+ // type check
+ if (nIndex == 0)
+ {
+ if ( NumFor[nIndex].GetNatNum().GetNatNum() == 12 &&
+ lcl_isNatNum12Currency(NumFor[nIndex].GetNatNum().GetParams()) )
+ eType = SvNumFormatType::CURRENCY;
+ else
+ eType = NumFor[nIndex].Info().eScannedType;
+ }
+ else if (nIndex == 3)
+ { // #77026# Everything recognized IS text
+ NumFor[nIndex].Info().eScannedType = SvNumFormatType::TEXT;
+ }
+ else if ( NumFor[nIndex].Info().eScannedType != eType)
+ {
+ eType = SvNumFormatType::DEFINED;
+ }
+ }
+ else
+ {
+ nCheckPos = nPosOld + nStrPos; // error in string
+ bCancel = true; // break for
+ }
+ }
+ }
+ else if (eSymbolType == BRACKET_SYMBOLTYPE_ERROR) // error
+ {
+ nCheckPos = nPosOld;
+ bCancel = true;
+ }
+ else if ( lcl_SvNumberformat_IsBracketedPrefix( eSymbolType ) )
+ {
+ nCheckPos = nPosOld + 1; // error, prefix in string
+ bCancel = true; // break for
+ }
+ }
+ if ( bCancel && !nCheckPos )
+ {
+ nCheckPos = 1; // nCheckPos is used as an error condition
+ }
+ if ( !bCancel )
+ {
+ if ( NumFor[nIndex].GetNatNum().IsSet() &&
+ NumFor[nIndex].GetNatNum().GetLang() == LANGUAGE_DONTKNOW )
+ {
+ NumFor[nIndex].SetNatNumLang( eLan );
+ }
+ }
+ if (sBuff.getLength() == nPos)
+ {
+ if (nIndex < 3 && rString[rString.getLength()-1] == ';')
+ {
+ // A trailing ';' is significant and specifies the following
+ // subformat to be empty. We don't enter the scanning loop
+ // above again though.
+ // Note that the operators apply to the current last scanned
+ // subformat.
+ if (nIndex == 0 && eOp1 == NUMBERFORMAT_OP_NO)
+ {
+ eOp1 = NUMBERFORMAT_OP_GT; // undefined condition, default: > 0
+ }
+ else if (nIndex == 1 && eOp2 == NUMBERFORMAT_OP_NO)
+ {
+ eOp2 = NUMBERFORMAT_OP_LT; // undefined condition, default: < 0
+ }
+ NumFor[nIndex+1].Info().eScannedType = SvNumFormatType::EMPTY;
+ if (sBuff[nPos-1] != ';')
+ sBuff.insert( nPos++, ';');
+ }
+ if (nIndex == 2 && eSymbolType == BRACKET_SYMBOLTYPE_FORMAT && sBuff[nPos-1] == ';')
+ {
+ // #83510# A 4th subformat explicitly specified to be empty
+ // hides any text. Need the type here for HasTextFormat()
+ NumFor[3].Info().eScannedType = SvNumFormatType::TEXT;
+ }
+ bCancel = true;
+ }
+ if ( NumFor[nIndex].GetNatNum().IsSet() )
+ {
+ NumFor[nIndex].SetNatNumDate( bool(NumFor[nIndex].Info().eScannedType & SvNumFormatType::DATE) );
+ }
+ }
+
+ if (!nCheckPos && IsSubstituted())
+ {
+ // For to be substituted formats the scanned type must match the
+ // substitute type.
+ if (IsSystemTimeFormat())
+ {
+ if ((eType & ~SvNumFormatType::DEFINED) != SvNumFormatType::TIME)
+ nCheckPos = std::max<sal_Int32>( sBuff.indexOf(']') + 1, 1);
+ }
+ else if (IsSystemLongDateFormat())
+ {
+ if ((eType & ~SvNumFormatType::DEFINED) != SvNumFormatType::DATE)
+ nCheckPos = std::max<sal_Int32>( sBuff.indexOf(']') + 1, 1);
+ }
+ else
+ assert(!"unhandled substitute");
+ }
+
+ if ( bCondition && !nCheckPos )
+ {
+ if ( nIndex == 1 && NumFor[0].GetCount() == 0 &&
+ sBuff[sBuff.getLength() - 1] != ';' )
+ {
+ // No format code => GENERAL but not if specified empty
+ OUString aAdd( pSc->GetStandardName() );
+ if ( !pSc->ScanFormat( aAdd ) )
+ {
+ sal_uInt16 nCnt = pSc->GetResultStringsCnt();
+ if ( nCnt )
+ {
+ NumFor[0].Enlarge(nCnt);
+ pSc->CopyInfo( &(NumFor[0].Info()), nCnt );
+ sBuff.append(aAdd);
+ }
+ }
+ }
+ else if ( nIndex == 1 && NumFor[nIndex].GetCount() == 0 &&
+ sBuff[sBuff.getLength() - 1] != ';' &&
+ (NumFor[0].GetCount() > 1 ||
+ (NumFor[0].GetCount() == 1 &&
+ NumFor[0].Info().nTypeArray[0] != NF_KEY_GENERAL)) )
+ {
+ // No trailing second subformat => GENERAL but not if specified empty
+ // and not if first subformat is GENERAL
+ OUString aAdd( pSc->GetStandardName() );
+ if ( !pSc->ScanFormat( aAdd ) )
+ {
+ sal_uInt16 nCnt = pSc->GetResultStringsCnt();
+ if ( nCnt )
+ {
+ NumFor[nIndex].Enlarge(nCnt);
+ pSc->CopyInfo( &(NumFor[nIndex].Info()), nCnt );
+ sBuff.append(";" + aAdd);
+ }
+ }
+ }
+ else if ( nIndex == 2 && NumFor[nIndex].GetCount() == 0 &&
+ sBuff[sBuff.getLength() - 1] != ';' &&
+ eOp2 != NUMBERFORMAT_OP_NO )
+ {
+ // No trailing third subformat => GENERAL but not if specified empty
+ OUString aAdd( pSc->GetStandardName() );
+ if ( !pSc->ScanFormat( aAdd ) )
+ {
+ sal_uInt16 nCnt = pSc->GetResultStringsCnt();
+ if ( nCnt )
+ {
+ NumFor[nIndex].Enlarge(nCnt);
+ pSc->CopyInfo( &(NumFor[nIndex].Info()), nCnt );
+ sBuff.append(";" + aAdd);
+ }
+ }
+ }
+ }
+ rString = sBuff.makeStringAndClear();
+ sFormatstring = rString;
+
+ if (NumFor[2].GetCount() == 0 && // No third partial string
+ eOp1 == NUMBERFORMAT_OP_GT && eOp2 == NUMBERFORMAT_OP_NO &&
+ fLimit1 == 0.0 && fLimit2 == 0.0)
+ {
+ eOp1 = NUMBERFORMAT_OP_GE; // Add 0 to the first format
+ }
+
+}
+
+SvNumberformat::~SvNumberformat()
+{
+}
+
+/**
+ * Next_Symbol
+ *
+ * Splits up the symbols for further processing (by the Turing machine)
+ *
+ * Start state = SsStart, * = Special state
+ * ---------------+-------------------+----------------------------+---------------
+ * Old State | Symbol read | Event | New state
+ * ---------------+-------------------+----------------------------+---------------
+ * SsStart | " | Symbol += Character | SsGetQuoted
+ * | ; | Pos-- | SsGetString
+ * | [ | Symbol += Character | SsGetBracketed
+ * | ] | Error | SsStop
+ * | BLANK | |
+ * | Else | Symbol += Character | SsGetString
+ * ---------------+-------------------+----------------------------+---------------
+ * SsGetString | " | Symbol += Character | SsGetQuoted
+ * | ; | | SsStop
+ * | Else | Symbol += Character |
+ * ---------------+-------------------+----------------------------+---------------
+ * SsGetQuoted | " | Symbol += Character | SsGetString
+ * | Else | Symbol += Character |
+ * ---------------+-------------------+----------------------------+---------------
+ * SsGetBracketed | <, > = | del [ |
+ * | | Symbol += Character | SsGetCon
+ * | BLANK | |
+ * | h, H, m, M, s, S | Symbol += Character | SsGetTime
+ * | Else | del [ |
+ * | | Symbol += Character | SsGetPrefix
+ * ---------------+-------------------+----------------------------+---------------
+ * SsGetTime | ] | Symbol += Character | SsGetString
+ * | h, H, m, M, s, S | Symbol += Character, * | SsGetString
+ * | Else | del [; Symbol += Character | SsGetPrefix
+ * ---------------+-------------------+----------------------------+---------------
+ * SsGetPrefix | ] | | SsStop
+ * | Else | Symbol += Character |
+ * ---------------+-------------------+----------------------------+---------------
+ * SsGetCon | >, = | Symbol += Character |
+ * | ] | | SsStop
+ * | Else | Error | SsStop
+ * ---------------+-------------------+----------------------------+---------------
+ */
+
+namespace {
+
+enum ScanState
+{
+ SsStop,
+ SsStart,
+ SsGetCon, // condition
+ SsGetString, // format string
+ SsGetPrefix, // color or NatNumN
+ SsGetTime, // [HH] for time
+ SsGetBracketed, // any [...] not decided yet
+ SsGetQuoted // quoted text
+};
+
+}
+
+// read a string until ']' and delete spaces in input
+// static
+sal_Int32 SvNumberformat::ImpGetNumber(OUStringBuffer& rString,
+ sal_Int32& nPos,
+ OUString& sSymbol)
+{
+ sal_Int32 nStartPos = nPos;
+ sal_Unicode cToken;
+ sal_Int32 nLen = rString.getLength();
+ OUStringBuffer sBuffSymbol;
+ while ( nPos < nLen )
+ {
+ cToken = rString[nPos];
+ if (cToken == ']')
+ break;
+ if (cToken == ' ')
+ { // delete spaces
+ rString.remove(nPos,1);
+ nLen--;
+ }
+ else
+ {
+ nPos++;
+ sBuffSymbol.append(cToken);
+ }
+ }
+ sSymbol = sBuffSymbol.makeStringAndClear();
+ return nPos - nStartPos;
+}
+
+namespace {
+
+sal_Unicode toUniChar(sal_uInt8 n)
+{
+ char c;
+ if (n < 10)
+ {
+ c = '0' + n;
+ }
+ else
+ {
+ c = 'A' + n - 10;
+ }
+ return sal_Unicode(c);
+}
+
+bool IsCombiningSymbol( OUStringBuffer& rStringBuffer, sal_Int32 nPos )
+{
+ bool bRet = false;
+ while (nPos >= 0)
+ {
+ switch (rStringBuffer[nPos])
+ {
+ case '*':
+ case '\\':
+ case '_':
+ bRet = !bRet;
+ --nPos;
+ break;
+ default:
+ return bRet;
+ }
+ }
+ return bRet;
+}
+
+} // namespace
+
+OUString SvNumberformat::LocaleType::generateCode() const
+{
+ OUStringBuffer aBuf;
+#if 0
+ // TODO: We may re-enable this later. Don't remove it! --Kohei
+ if (mnNumeralShape)
+ {
+ sal_uInt8 nVal = mnNumeralShape;
+ for (sal_uInt8 i = 0; i < 2; ++i)
+ {
+ sal_uInt8 n = (nVal & 0xF0) >> 4;
+ if (n || aBuf.getLength())
+ {
+ aBuf.append(toUniChar(n));
+ }
+ nVal = nVal << 4;
+ }
+ }
+
+ if (mnNumeralShape || mnCalendarType)
+ {
+ sal_uInt8 nVal = mnCalendarType;
+ for (sal_uInt8 i = 0; i < 2; ++i)
+ {
+ sal_uInt8 n = (nVal & 0xF0) >> 4;
+ if (n || aBuf.getLength())
+ {
+ aBuf.append(toUniChar(n));
+ }
+ nVal = nVal << 4;
+ }
+ }
+#endif
+
+ sal_uInt16 n16 = static_cast<sal_uInt16>(
+ (meLanguageWithoutLocaleData == LANGUAGE_DONTKNOW) ? meLanguage :
+ meLanguageWithoutLocaleData);
+ if (meLanguage == LANGUAGE_SYSTEM)
+ {
+ switch (meSubstitute)
+ {
+ case Substitute::NONE:
+ ; // nothing
+ break;
+ case Substitute::TIME:
+ n16 = static_cast<sal_uInt16>(LANGUAGE_NF_SYSTEM_TIME);
+ break;
+ case Substitute::LONGDATE:
+ n16 = static_cast<sal_uInt16>(LANGUAGE_NF_SYSTEM_DATE);
+ break;
+ }
+ }
+ for (sal_uInt8 i = 0; i < 4; ++i)
+ {
+ sal_uInt8 n = static_cast<sal_uInt8>((n16 & 0xF000) >> 12);
+ // Omit leading zeros for consistency.
+ if (n || !aBuf.isEmpty() || i == 3)
+ {
+ aBuf.append(toUniChar(n));
+ }
+ n16 = n16 << 4;
+ }
+
+ return aBuf.makeStringAndClear();
+}
+
+SvNumberformat::LocaleType::LocaleType()
+ : meLanguage(LANGUAGE_DONTKNOW)
+ , meLanguageWithoutLocaleData(LANGUAGE_DONTKNOW)
+ , meSubstitute(Substitute::NONE)
+ , mnNumeralShape(0)
+ , mnCalendarType(0)
+{
+}
+
+SvNumberformat::LocaleType::LocaleType(sal_uInt32 nRawNum)
+ : meLanguage(LANGUAGE_DONTKNOW)
+ , meLanguageWithoutLocaleData(LANGUAGE_DONTKNOW)
+ , meSubstitute(Substitute::NONE)
+ , mnNumeralShape(0)
+ , mnCalendarType(0)
+{
+ meLanguage = static_cast<LanguageType>(nRawNum & 0x0000FFFF);
+ if (meLanguage == LANGUAGE_NF_SYSTEM_TIME)
+ {
+ meSubstitute = Substitute::TIME;
+ meLanguage = LANGUAGE_SYSTEM;
+ }
+ else if (meLanguage == LANGUAGE_NF_SYSTEM_DATE)
+ {
+ meSubstitute = Substitute::LONGDATE;
+ meLanguage = LANGUAGE_SYSTEM;
+ }
+ nRawNum = (nRawNum >> 16);
+ mnCalendarType = static_cast<sal_uInt8>(nRawNum & 0xFF);
+ nRawNum = (nRawNum >> 8);
+ mnNumeralShape = static_cast<sal_uInt8>(nRawNum & 0xFF);
+}
+
+bool SvNumberformat::LocaleType::isPlainLocale() const
+{
+ return meSubstitute == Substitute::NONE && !mnCalendarType && !mnNumeralShape;
+}
+
+// static
+SvNumberformat::LocaleType SvNumberformat::ImpGetLocaleType(std::u16string_view rString, sal_Int32& nPos )
+{
+ sal_uInt32 nNum = 0;
+ sal_Unicode cToken = 0;
+ sal_Int32 nStart = nPos;
+ sal_Int32 nLen = rString.size();
+ while ( nPos < nLen && (nPos - nStart < 8) )
+ {
+ cToken = rString[nPos];
+ if (cToken == ']')
+ break;
+ if ( '0' <= cToken && cToken <= '9' )
+ {
+ nNum *= 16;
+ nNum += cToken - '0';
+ }
+ else if ( 'a' <= cToken && cToken <= 'f' )
+ {
+ nNum *= 16;
+ nNum += cToken - 'a' + 10;
+ }
+ else if ( 'A' <= cToken && cToken <= 'F' )
+ {
+ nNum *= 16;
+ nNum += cToken - 'A' + 10;
+ }
+ else
+ {
+ return LocaleType(); // LANGUAGE_DONTKNOW;
+ }
+ ++nPos;
+ }
+
+ return (cToken == ']' || nPos == nLen) ? LocaleType(nNum) : LocaleType();
+}
+
+static bool lcl_matchKeywordAndGetNumber( std::u16string_view rString, const sal_Int32 nPos,
+ std::u16string_view rKeyword, sal_Int32 & nNumber )
+{
+ if (0 <= nPos && nPos + static_cast<sal_Int32>(rKeyword.size()) < static_cast<sal_Int32>(rString.size()) && o3tl::matchIgnoreAsciiCase( rString, rKeyword, nPos))
+ {
+ nNumber = o3tl::toInt32(rString.substr( nPos + rKeyword.size()));
+ return true;
+ }
+ else
+ {
+ nNumber = 0;
+ return false;
+ }
+}
+
+short SvNumberformat::ImpNextSymbol(OUStringBuffer& rString,
+ sal_Int32& nPos,
+ OUString& sSymbol) const
+{
+ short eSymbolType = BRACKET_SYMBOLTYPE_FORMAT;
+ sal_Unicode cToken;
+ sal_Unicode cLetter = ' '; // Preliminary result
+ sal_Int32 nLen = rString.getLength();
+ ScanState eState = SsStart;
+ OUStringBuffer sBuffSymbol(128);
+
+ const NfKeywordTable & rKeywords = rScan.GetKeywords();
+ while (nPos < nLen && eState != SsStop)
+ {
+ cToken = rString[nPos];
+ nPos++;
+ switch (eState)
+ {
+ case SsStart:
+ if (cToken == '\"')
+ {
+ eState = SsGetQuoted;
+ sBuffSymbol.append(cToken);
+ }
+ else if (cToken == '[')
+ {
+ eState = SsGetBracketed;
+ sBuffSymbol.append(cToken);
+ }
+ else if (cToken == ';')
+ {
+ eState = SsGetString;
+ nPos--;
+ eSymbolType = BRACKET_SYMBOLTYPE_FORMAT;
+ }
+ else if (cToken == ']')
+ {
+ eState = SsStop;
+ eSymbolType = BRACKET_SYMBOLTYPE_ERROR;
+ }
+ else if (cToken == ' ') // Skip Blanks
+ {
+ nPos--;
+ rString.remove(nPos, 1);
+ nLen--;
+ }
+ else
+ {
+ sBuffSymbol.append(cToken);
+ eState = SsGetString;
+ eSymbolType = BRACKET_SYMBOLTYPE_FORMAT;
+ }
+ break;
+ case SsGetBracketed:
+ switch (cToken)
+ {
+ case '<':
+ case '>':
+ case '=':
+ sBuffSymbol.stripStart('[');
+ sBuffSymbol.append(cToken);
+ cLetter = cToken;
+ eState = SsGetCon;
+ switch (cToken)
+ {
+ case '<':
+ eSymbolType = NUMBERFORMAT_OP_LT;
+ break;
+ case '>':
+ eSymbolType = NUMBERFORMAT_OP_GT;
+ break;
+ case '=':
+ eSymbolType = NUMBERFORMAT_OP_EQ;
+ break;
+ }
+ break;
+ case ' ':
+ nPos--;
+ rString.remove(nPos, 1);
+ nLen--;
+ break;
+ case '$' :
+ if ( nPos < nLen && rString[nPos] == '-' )
+ {
+ // [$-xxx] locale
+ sBuffSymbol.stripStart('[');
+ eSymbolType = BRACKET_SYMBOLTYPE_LOCALE;
+ eState = SsGetPrefix;
+ }
+ else
+ { // currency
+ eSymbolType = BRACKET_SYMBOLTYPE_FORMAT;
+ eState = SsGetString;
+ }
+ sBuffSymbol.append(cToken);
+ break;
+ case '~' :
+ // calendarID
+ eSymbolType = BRACKET_SYMBOLTYPE_FORMAT;
+ sBuffSymbol.append(cToken);
+ eState = SsGetString;
+ break;
+ default:
+ {
+ static constexpr OUString aNatNum(u"NATNUM"_ustr);
+ static constexpr OUString aDBNum(u"DBNUM"_ustr);
+ const OUString aBufStr( rString.toString());
+ sal_Int32 nNatNumNum;
+ sal_Int32 nDBNum;
+ if ( lcl_matchKeywordAndGetNumber( aBufStr, nPos-1, aNatNum, nNatNumNum) &&
+ 0 <= nNatNumNum && nNatNumNum <= 19 )
+ {
+ sBuffSymbol.stripStart('[');
+ sBuffSymbol.append( aBufStr.subView(--nPos, aNatNum.getLength()+1) );
+ nPos += aNatNum.getLength()+1;
+ //! SymbolType is negative
+ eSymbolType = static_cast<short>(BRACKET_SYMBOLTYPE_NATNUM0 - nNatNumNum);
+ eState = SsGetPrefix;
+ }
+ else if ( lcl_matchKeywordAndGetNumber( aBufStr, nPos-1, aDBNum, nDBNum) &&
+ 1 <= nDBNum && nDBNum <= 9 )
+ {
+ sBuffSymbol.stripStart('[');
+ sBuffSymbol.append( aBufStr.subView(--nPos, aDBNum.getLength()+1) );
+ nPos += aDBNum.getLength()+1;
+ //! SymbolType is negative
+ eSymbolType = sal::static_int_cast< short >( BRACKET_SYMBOLTYPE_DBNUM1 - (nDBNum - 1) );
+ eState = SsGetPrefix;
+ }
+ else
+ {
+ sal_Unicode cUpper = rChrCls().uppercase( aBufStr, nPos-1, 1)[0];
+ if ( cUpper == rKeywords[NF_KEY_H][0] || // H
+ cUpper == rKeywords[NF_KEY_MI][0] || // M
+ cUpper == rKeywords[NF_KEY_S][0] ) // S
+ {
+ sBuffSymbol.append(cToken);
+ eState = SsGetTime;
+ cLetter = cToken;
+ }
+ else
+ {
+ sBuffSymbol.stripStart('[');
+ sBuffSymbol.append(cToken);
+ eSymbolType = BRACKET_SYMBOLTYPE_COLOR;
+ eState = SsGetPrefix;
+ }
+ }
+ }
+ }
+ break;
+ case SsGetString:
+ if (cToken == '\"')
+ {
+ eState = SsGetQuoted;
+ sBuffSymbol.append(cToken);
+ }
+ else if (cToken == ';' && (nPos < 2 || !IsCombiningSymbol( rString, nPos-2)))
+ {
+ eState = SsStop;
+ }
+ else
+ {
+ sBuffSymbol.append(cToken);
+ }
+ break;
+ case SsGetQuoted:
+ if (cToken == '\"')
+ {
+ eState = SsGetString;
+ sBuffSymbol.append(cToken);
+ }
+ else
+ {
+ sBuffSymbol.append(cToken);
+ }
+ break;
+ case SsGetTime:
+ if (cToken == ']')
+ {
+ sBuffSymbol.append(cToken);
+ eState = SsGetString;
+ eSymbolType = BRACKET_SYMBOLTYPE_FORMAT;
+ }
+ else
+ {
+ sal_Unicode cUpper = rChrCls().uppercase(rString.toString(), nPos-1, 1)[0];
+ if (cUpper == rKeywords[NF_KEY_H][0] || // H
+ cUpper == rKeywords[NF_KEY_MI][0] || // M
+ cUpper == rKeywords[NF_KEY_S][0] ) // S
+ {
+ if (cLetter == cToken)
+ {
+ sBuffSymbol.append(cToken);
+ cLetter = ' ';
+ }
+ else
+ {
+ sBuffSymbol.stripStart('[');
+ sBuffSymbol.append(cToken);
+ eState = SsGetPrefix;
+ }
+ }
+ else
+ {
+ sBuffSymbol.stripStart('[');
+ sBuffSymbol.append(cToken);
+ eSymbolType = BRACKET_SYMBOLTYPE_COLOR;
+ eState = SsGetPrefix;
+ }
+ }
+ break;
+ case SsGetCon:
+ switch (cToken)
+ {
+ case '<':
+ eState = SsStop;
+ eSymbolType = BRACKET_SYMBOLTYPE_ERROR;
+ break;
+ case '>':
+ if (cLetter == '<')
+ {
+ sBuffSymbol.append(cToken);
+ cLetter = ' ';
+ eState = SsStop;
+ eSymbolType = NUMBERFORMAT_OP_NE;
+ }
+ else
+ {
+ eState = SsStop;
+ eSymbolType = BRACKET_SYMBOLTYPE_ERROR;
+ }
+ break;
+ case '=':
+ if (cLetter == '<')
+ {
+ sBuffSymbol.append(cToken);
+ cLetter = ' ';
+ eSymbolType = NUMBERFORMAT_OP_LE;
+ }
+ else if (cLetter == '>')
+ {
+ sBuffSymbol.append(cToken);
+ cLetter = ' ';
+ eSymbolType = NUMBERFORMAT_OP_GE;
+ }
+ else
+ {
+ eState = SsStop;
+ eSymbolType = BRACKET_SYMBOLTYPE_ERROR;
+ }
+ break;
+ case ' ':
+ nPos--;
+ rString.remove(nPos,1);
+ nLen--;
+ break;
+ default:
+ eState = SsStop;
+ nPos--;
+ break;
+ }
+ break;
+ case SsGetPrefix:
+ if (cToken == ']')
+ {
+ eState = SsStop;
+ }
+ else
+ {
+ sBuffSymbol.append(cToken);
+ }
+ break;
+ default:
+ break;
+ } // of switch
+ } // of while
+ sSymbol = sBuffSymbol.makeStringAndClear();
+ return eSymbolType;
+}
+
+void SvNumberformat::ConvertLanguage( SvNumberFormatter& rConverter,
+ LanguageType eConvertFrom,
+ LanguageType eConvertTo )
+{
+ sal_Int32 nCheckPos;
+ sal_uInt32 nKey;
+ SvNumFormatType nType = eType;
+ OUString aFormatString( sFormatstring );
+ rConverter.PutandConvertEntry( aFormatString, nCheckPos, nType,
+ nKey, eConvertFrom, eConvertTo, false);
+ const SvNumberformat* pFormat = rConverter.GetEntry( nKey );
+ DBG_ASSERT( pFormat, "SvNumberformat::ConvertLanguage: Conversion without format" );
+ if ( pFormat )
+ {
+ ImpCopyNumberformat( *pFormat );
+ // Reset values taken over from Formatter/Scanner
+ // pColor still points to table in temporary Formatter/Scanner
+ for (ImpSvNumFor & rFormatter : NumFor)
+ {
+ OUString aColorName = rFormatter.GetColorName();
+ const Color* pColor = rScan.GetColor( aColorName );
+ rFormatter.SetColor( pColor, aColorName );
+ }
+ }
+}
+
+bool SvNumberformat::HasNewCurrency() const
+{
+ for (const auto & j : NumFor)
+ {
+ if ( j.HasNewCurrency() )
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool SvNumberformat::GetNewCurrencySymbol( OUString& rSymbol,
+ OUString& rExtension ) const
+{
+ for (const auto & j : NumFor)
+ {
+ if ( j.GetNewCurrencySymbol( rSymbol, rExtension ) )
+ {
+ return true;
+ }
+ }
+ rSymbol.clear();
+ rExtension.clear();
+ return false;
+}
+
+// static
+OUString SvNumberformat::StripNewCurrencyDelimiters( const OUString& rStr )
+{
+ OUStringBuffer aTmp(rStr.getLength());
+ sal_Int32 nStartPos, nPos, nLen;
+ nLen = rStr.getLength();
+ nStartPos = 0;
+ while ( (nPos = rStr.indexOf( "[$", nStartPos )) >= 0 )
+ {
+ sal_Int32 nEnd;
+ if ( (nEnd = GetQuoteEnd( rStr, nPos )) >= 0 )
+ {
+ aTmp.append(rStr.subView( nStartPos, ++nEnd - nStartPos ));
+ nStartPos = nEnd;
+ }
+ else
+ {
+ aTmp.append(rStr.subView(nStartPos, nPos - nStartPos) );
+ nStartPos = nPos + 2;
+ sal_Int32 nDash;
+ nEnd = nStartPos - 1;
+ do
+ {
+ nDash = rStr.indexOf( '-', ++nEnd );
+ nEnd = GetQuoteEnd( rStr, nDash );
+ }
+ while ( nEnd >= 0 );
+ sal_Int32 nClose;
+ nEnd = nStartPos - 1;
+ do
+ {
+ nClose = rStr.indexOf( ']', ++nEnd );
+ nEnd = GetQuoteEnd( rStr, nClose );
+ }
+ while ( nEnd >= 0 );
+
+ if(nClose < 0)
+ {
+ /* there should always be a closing ]
+ * but the old String class would have hidden
+ * that. so be conservative too
+ */
+ nClose = nLen;
+ }
+
+ nPos = nClose;
+ if(nDash >= 0 && nDash < nClose)
+ {
+ nPos = nDash;
+ }
+ aTmp.append(rStr.subView(nStartPos, nPos - nStartPos) );
+ nStartPos = nClose + 1;
+ }
+ }
+ if ( nLen > nStartPos )
+ {
+ aTmp.append(rStr.subView(nStartPos, nLen - nStartPos) );
+ }
+ return aTmp.makeStringAndClear();
+}
+
+void SvNumberformat::ImpGetOutputStandard(double& fNumber, OUStringBuffer& rOutString) const
+{
+ OUString sTemp;
+ ImpGetOutputStandard(fNumber, sTemp);
+ rOutString = sTemp;
+}
+
+void SvNumberformat::ImpGetOutputStandard(double& fNumber, OUString& rOutString) const
+{
+ sal_uInt16 nStandardPrec = rScan.GetStandardPrec();
+
+ if ( fabs(fNumber) > EXP_ABS_UPPER_BOUND )
+ {
+ nStandardPrec = ::std::min(nStandardPrec, static_cast<sal_uInt16>(14)); // limits to 14 decimals
+ rOutString = ::rtl::math::doubleToUString( fNumber,
+ rtl_math_StringFormat_E2, nStandardPrec /*2*/,
+ GetFormatter().GetNumDecimalSep()[0]);
+ }
+ else
+ {
+ ImpGetOutputStdToPrecision(fNumber, rOutString, nStandardPrec);
+ }
+}
+
+namespace
+{
+
+template<typename T>
+bool checkForAll0s(const T& rString, sal_Int32 nIdx=0)
+{
+ if (nIdx>=rString.getLength())
+ return false;
+
+ do
+ {
+ if (rString[nIdx]!='0')
+ return false;
+ }
+ while (++nIdx<rString.getLength());
+
+ return true;
+}
+
+}
+
+void SvNumberformat::ImpGetOutputStdToPrecision(double& rNumber, OUString& rOutString, sal_uInt16 nPrecision) const
+{
+ // Make sure the precision doesn't go over the maximum allowable precision.
+ nPrecision = ::std::min(UPPER_PRECISION, nPrecision);
+
+ // We decided to strip trailing zeros unconditionally, since binary
+ // double-precision rounding error makes it impossible to determine e.g.
+ // whether 844.10000000000002273737 is what the user has typed, or the
+ // user has typed 844.1 but IEEE 754 represents it that way internally.
+
+ rOutString = ::rtl::math::doubleToUString( rNumber,
+ rtl_math_StringFormat_F, nPrecision /*2*/,
+ GetFormatter().GetNumDecimalSep()[0], true );
+ if (rOutString[0] == '-' && checkForAll0s(rOutString, 1))
+ {
+ rOutString = comphelper::string::stripStart(rOutString, '-'); // not -0
+ }
+ rOutString = impTransliterate(rOutString, NumFor[0].GetNatNum());
+}
+
+void SvNumberformat::ImpGetOutputInputLine(double fNumber, OUString& OutString) const
+{
+ bool bModified = false;
+ if ( (eType & SvNumFormatType::PERCENT) && (fabs(fNumber) < D_MAX_D_BY_100))
+ {
+ if (fNumber == 0.0)
+ {
+ OutString = "0%";
+ return;
+ }
+ fNumber *= 100;
+ bModified = true;
+ }
+
+ if (fNumber == 0.0)
+ {
+ OutString = "0";
+ return;
+ }
+
+ OutString = ::rtl::math::doubleToUString( fNumber,
+ rtl_math_StringFormat_Automatic,
+ rtl_math_DecimalPlaces_Max,
+ GetFormatter().GetNumDecimalSep()[0], true );
+
+ if ( eType & SvNumFormatType::PERCENT && bModified)
+ {
+ OutString += "%";
+ }
+}
+
+short SvNumberformat::ImpCheckCondition(double fNumber,
+ double fLimit,
+ SvNumberformatLimitOps eOp)
+{
+ switch(eOp)
+ {
+ case NUMBERFORMAT_OP_NO:
+ return -1;
+ case NUMBERFORMAT_OP_EQ:
+ return static_cast<short>(fNumber == fLimit);
+ case NUMBERFORMAT_OP_NE:
+ return static_cast<short>(fNumber != fLimit);
+ case NUMBERFORMAT_OP_LT:
+ return static_cast<short>(fNumber < fLimit);
+ case NUMBERFORMAT_OP_LE:
+ return static_cast<short>(fNumber <= fLimit);
+ case NUMBERFORMAT_OP_GT:
+ return static_cast<short>(fNumber > fLimit);
+ case NUMBERFORMAT_OP_GE:
+ return static_cast<short>(fNumber >= fLimit);
+ default:
+ return -1;
+ }
+}
+
+static bool lcl_appendStarFillChar( OUStringBuffer& rBuf, std::u16string_view rStr )
+{
+ // Right during user input the star symbol is the very
+ // last character before the user enters another one.
+ if (rStr.size() > 1)
+ {
+ rBuf.append(u'\x001B');
+ rBuf.append(rStr[1]);
+ return true;
+ }
+ return false;
+}
+
+static bool lcl_insertStarFillChar( OUStringBuffer& rBuf, sal_Int32 nPos, std::u16string_view rStr )
+{
+ if (rStr.size() > 1)
+ {
+ rBuf.insert( nPos, rStr[1]);
+ rBuf.insert( nPos, u'\x001B');
+ return true;
+ }
+ return false;
+}
+
+void SvNumberformat::GetOutputString(std::u16string_view sString,
+ OUString& OutString,
+ const Color** ppColor)
+{
+ OUStringBuffer sOutBuff;
+ sal_uInt16 nIx;
+ if (eType & SvNumFormatType::TEXT)
+ {
+ nIx = 0;
+ }
+ else if (NumFor[3].GetCount() > 0)
+ {
+ nIx = 3;
+ }
+ else
+ {
+ *ppColor = nullptr; // no change of color
+ return;
+ }
+ *ppColor = NumFor[nIx].GetColor();
+ const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info();
+ if (rInfo.eScannedType == SvNumFormatType::TEXT)
+ {
+ const sal_uInt16 nCnt = NumFor[nIx].GetCount();
+ for (sal_uInt16 i = 0; i < nCnt; i++)
+ {
+ switch (rInfo.nTypeArray[i])
+ {
+ case NF_SYMBOLTYPE_STAR:
+ if( bStarFlag )
+ {
+ lcl_appendStarFillChar( sOutBuff, rInfo.sStrArray[i]);
+ }
+ break;
+ case NF_SYMBOLTYPE_BLANK:
+ if (rInfo.sStrArray[i].getLength() >= 2)
+ InsertBlanks( sOutBuff, sOutBuff.getLength(), rInfo.sStrArray[i][1] );
+ break;
+ case NF_KEY_GENERAL : // #77026# "General" is the same as "@"
+ case NF_SYMBOLTYPE_DEL :
+ sOutBuff.append(sString);
+ break;
+ default:
+ sOutBuff.append(rInfo.sStrArray[i]);
+ }
+ }
+ }
+ OutString = sOutBuff.makeStringAndClear();
+}
+
+namespace {
+
+void lcl_GetOutputStringScientific(double fNumber, sal_uInt16 nCharCount,
+ const SvNumberFormatter& rFormatter, OUString& rOutString)
+{
+ bool bSign = std::signbit(fNumber);
+
+ // 1.000E+015 (one digit and the decimal point, and the two chars +
+ // nExpDigit for the exponential part, totalling 6 or 7).
+ double fExp = log10( fabs(fNumber) );
+ if( fExp < 0.0 )
+ fExp = 1.0 - fExp;
+ sal_uInt16 nCharFormat = 6 + (fExp >= 100.0 ? 1 : 0);
+ sal_uInt16 nPrec = nCharCount > nCharFormat ? nCharCount - nCharFormat : 0;
+ if (nPrec && bSign)
+ {
+ // Make room for the negative sign.
+ --nPrec;
+ }
+ nPrec = ::std::min(nPrec, static_cast<sal_uInt16>(14)); // limit to 14 decimals.
+
+ rOutString = ::rtl::math::doubleToUString(fNumber, rtl_math_StringFormat_E2,
+ nPrec, rFormatter.GetNumDecimalSep()[0], true );
+}
+
+OUString lcl_GetPercentString(const ImpSvNumberformatInfo &rInfo, sal_uInt16 nCnt)
+{
+ sal_Int32 i;
+ OUStringBuffer aPercentString;
+ for( i = 0; i < nCnt; i++ )
+ {
+ if( rInfo.nTypeArray[i] == NF_SYMBOLTYPE_PERCENT )
+ {
+ aPercentString.append( rInfo.sStrArray[i] );
+ bool bStringFound = false;
+ for( i--; i >= 0 && rInfo.nTypeArray[i] == NF_SYMBOLTYPE_STRING ; i-- )
+ {
+ if( !bStringFound )
+ {
+ bStringFound = true;
+ aPercentString.insert( 0, "\"" );
+ }
+ aPercentString.insert( 0, rInfo.sStrArray[i] );
+ }
+ i = nCnt;
+ if( bStringFound )
+ aPercentString.insert( 0, "\"" );
+ }
+ }
+ return aPercentString.makeStringAndClear();
+}
+
+OUString lcl_GetDenominatorString(const ImpSvNumberformatInfo &rInfo, sal_uInt16 nCnt)
+{
+ sal_Int32 i;
+ OUStringBuffer aDenominatorString;
+ for( i = 0; i < nCnt; i++ )
+ {
+ if( rInfo.nTypeArray[i] == NF_SYMBOLTYPE_FRAC )
+ {
+ while ( ( ++i < nCnt ) && rInfo.nTypeArray[i] != NF_SYMBOLTYPE_FRAC_FDIV
+ && rInfo.nTypeArray[i] != NF_SYMBOLTYPE_DIGIT );
+ for( ; i < nCnt; i++ )
+ {
+ if( rInfo.nTypeArray[i] == NF_SYMBOLTYPE_FRAC_FDIV || rInfo.nTypeArray[i] == NF_SYMBOLTYPE_DIGIT )
+ aDenominatorString.append( rInfo.sStrArray[i] );
+ else
+ i = nCnt;
+ }
+ }
+ }
+ return aDenominatorString.makeStringAndClear();
+}
+
+OUString lcl_GetNumeratorString(const ImpSvNumberformatInfo &rInfo, sal_uInt16 nCnt)
+{
+ sal_Int32 i;
+ OUStringBuffer aNumeratorString;
+ for( i = 0; i < nCnt; i++ )
+ {
+ if( rInfo.nTypeArray[i] == NF_SYMBOLTYPE_FRAC )
+ {
+ for( i--; i >= 0 && rInfo.nTypeArray[i] == NF_SYMBOLTYPE_DIGIT ; i-- )
+ {
+ aNumeratorString.insert( 0, rInfo.sStrArray[i] );
+ }
+ i = nCnt;
+ }
+ }
+ return aNumeratorString.makeStringAndClear();
+}
+
+OUString lcl_GetFractionIntegerString(const ImpSvNumberformatInfo &rInfo, sal_uInt16 nCnt)
+{
+ sal_Int32 i;
+ OUStringBuffer aIntegerString;
+ for( i = 0; i < nCnt; i++ )
+ {
+ if( rInfo.nTypeArray[i] == NF_SYMBOLTYPE_FRACBLANK )
+ {
+ for( i--; i >= 0 && ( rInfo.nTypeArray[i] == NF_SYMBOLTYPE_DIGIT
+ || rInfo.nTypeArray[i] == NF_SYMBOLTYPE_THSEP ); i-- )
+ {
+ aIntegerString.insert( 0, rInfo.sStrArray[i] );
+ }
+ i = nCnt;
+ }
+ }
+ return aIntegerString.makeStringAndClear();
+}
+
+OUString lcl_GetIntegerFractionDelimiterString(const ImpSvNumberformatInfo &rInfo, sal_uInt16 nCnt)
+{
+ sal_uInt16 i;
+ for( i = 0; i < nCnt; i++ )
+ {
+ if( rInfo.nTypeArray[i] == NF_SYMBOLTYPE_FRACBLANK )
+ {
+ return rInfo.sStrArray[i];
+ }
+ }
+ return OUString();
+}
+
+}
+
+OUString SvNumberformat::GetPercentString( sal_uInt16 nNumFor ) const
+{
+ const ImpSvNumberformatInfo& rInfo = NumFor[nNumFor].Info();
+ sal_uInt16 nCnt = NumFor[nNumFor].GetCount();
+ return lcl_GetPercentString( rInfo, nCnt );
+}
+
+OUString SvNumberformat::GetDenominatorString( sal_uInt16 nNumFor ) const
+{
+ const ImpSvNumberformatInfo& rInfo = NumFor[nNumFor].Info();
+ sal_uInt16 nCnt = NumFor[nNumFor].GetCount();
+ return lcl_GetDenominatorString( rInfo, nCnt );
+}
+
+OUString SvNumberformat::GetNumeratorString( sal_uInt16 nNumFor ) const
+{
+ const ImpSvNumberformatInfo& rInfo = NumFor[nNumFor].Info();
+ sal_uInt16 nCnt = NumFor[nNumFor].GetCount();
+ return lcl_GetNumeratorString( rInfo, nCnt );
+}
+
+OUString SvNumberformat::GetIntegerFractionDelimiterString( sal_uInt16 nNumFor ) const
+{
+ const ImpSvNumberformatInfo& rInfo = NumFor[nNumFor].Info();
+ sal_uInt16 nCnt = NumFor[nNumFor].GetCount();
+ return lcl_GetIntegerFractionDelimiterString( rInfo, nCnt );
+}
+
+bool SvNumberformat::GetOutputString(double fNumber, sal_uInt16 nCharCount, OUString& rOutString) const
+{
+ if (eType != SvNumFormatType::NUMBER)
+ {
+ return false;
+ }
+ double fTestNum = fNumber;
+ bool bSign = std::signbit(fTestNum);
+ if (bSign)
+ {
+ fTestNum = -fTestNum;
+ }
+ if (fTestNum < EXP_LOWER_BOUND)
+ {
+ lcl_GetOutputStringScientific(fNumber, nCharCount, GetFormatter(), rOutString);
+ return true;
+ }
+
+ double fExp = log10(fTestNum);
+ // Values < 1.0 always have one digit before the decimal point.
+ sal_uInt16 nDigitPre = fExp >= 0.0 ? static_cast<sal_uInt16>(ceil(fExp)) : 1;
+
+ if (nDigitPre > 15)
+ {
+ lcl_GetOutputStringScientific(fNumber, nCharCount, GetFormatter(), rOutString);
+ return true;
+ }
+
+ sal_uInt16 nPrec = nCharCount >= nDigitPre ? nCharCount - nDigitPre : 0;
+ if (nPrec && bSign)
+ {
+ // Subtract the negative sign.
+ --nPrec;
+ }
+ if (nPrec)
+ {
+ // Subtract the decimal point.
+ --nPrec;
+ }
+ ImpGetOutputStdToPrecision(fNumber, rOutString, nPrec);
+ if (rOutString.getLength() > nCharCount)
+ {
+ // String still wider than desired. Switch to scientific notation.
+ lcl_GetOutputStringScientific(fNumber, nCharCount, GetFormatter(), rOutString);
+ }
+ return true;
+}
+
+sal_uInt16 SvNumberformat::GetSubformatIndex (double fNumber ) const
+{
+ sal_uInt16 nIx; // Index of the partial format
+ double fLimit_1 = fLimit1;
+ short nCheck = ImpCheckCondition(fNumber, fLimit_1, eOp1);
+ if (nCheck == -1 || nCheck == 1) // Only 1 String or True
+ {
+ nIx = 0;
+ }
+ else
+ {
+ double fLimit_2 = fLimit2;
+ nCheck = ImpCheckCondition(fNumber, fLimit_2, eOp2);
+ if (nCheck == -1 || nCheck == 1)
+ {
+ nIx = 1;
+ }
+ else
+ {
+ nIx = 2;
+ }
+ }
+ return nIx;
+}
+
+bool SvNumberformat::GetOutputString(double fNumber,
+ OUString& OutString,
+ const Color** ppColor)
+{
+ bool bRes = false;
+ OutString.clear();
+ *ppColor = nullptr; // No color change
+ if (eType & SvNumFormatType::LOGICAL && sFormatstring == rScan.GetKeywords()[NF_KEY_BOOLEAN])
+ {
+ if (fNumber)
+ {
+ OutString = rScan.GetTrueString();
+ }
+ else
+ {
+ OutString = rScan.GetFalseString();
+ }
+ return false;
+ }
+ OUStringBuffer sBuff(64);
+ if (eType & SvNumFormatType::TEXT)
+ {
+ ImpGetOutputStandard(fNumber, sBuff);
+ OutString = sBuff.makeStringAndClear();
+ return false;
+ }
+ bool bHadStandard = false;
+ if (bStandard) // Individual standard formats
+ {
+ if (rScan.GetStandardPrec() == SvNumberFormatter::INPUTSTRING_PRECISION) // All number format InputLine
+ {
+ ImpGetOutputInputLine(fNumber, OutString);
+ return false;
+ }
+ switch (eType)
+ {
+ case SvNumFormatType::NUMBER: // Standard number format
+ if (rScan.GetStandardPrec() == SvNumberFormatter::UNLIMITED_PRECISION)
+ {
+ if (std::signbit(fNumber))
+ {
+ if (!(fNumber < 0.0))
+ fNumber = -fNumber; // do not display -0.0
+ }
+ if (fNumber == 0.0)
+ {
+ OutString = "0";
+ }
+ else if (fNumber < 1.0 && fNumber > -1.0)
+ {
+ // Decide whether to display as 0.000000123... or 1.23...e-07
+ bool bFix = (fNumber < -EXP_LOWER_BOUND || EXP_LOWER_BOUND < fNumber);
+ if (!bFix)
+ {
+ // Arbitrary, not too many 0s visually, start E2 at 1E-10.
+ constexpr sal_Int32 kMaxExp = 9;
+ const sal_Int32 nExp = static_cast<sal_Int32>(ceil( -log10( fabs( fNumber))));
+ if (nExp <= kMaxExp && rtl::math::approxEqual(
+ rtl::math::round( fNumber, 16), rtl::math::round( fNumber, nExp + 16)))
+ {
+ // Not too many significant digits or accuracy
+ // artefacts, otherwise leave everything to E2
+ // format.
+ bFix = true;
+ }
+ }
+ if (bFix)
+ OutString = ::rtl::math::doubleToUString( fNumber,
+ rtl_math_StringFormat_F,
+ rtl_math_DecimalPlaces_Max,
+ GetFormatter().GetNumDecimalSep()[0], true);
+ else
+ OutString = ::rtl::math::doubleToUString( fNumber,
+ rtl_math_StringFormat_E2,
+ rtl_math_DecimalPlaces_Max,
+ GetFormatter().GetNumDecimalSep()[0], true);
+ }
+ else
+ {
+ OutString = ::rtl::math::doubleToUString( fNumber,
+ rtl_math_StringFormat_Automatic,
+ rtl_math_DecimalPlaces_Max,
+ GetFormatter().GetNumDecimalSep()[0], true);
+ }
+ return false;
+ }
+ ImpGetOutputStandard(fNumber, sBuff);
+ bHadStandard = true;
+ break;
+ case SvNumFormatType::DATE:
+ bRes |= ImpGetDateOutput(fNumber, 0, sBuff);
+ bHadStandard = true;
+ break;
+ case SvNumFormatType::TIME:
+ bRes |= ImpGetTimeOutput(fNumber, 0, sBuff);
+ bHadStandard = true;
+ break;
+ case SvNumFormatType::DATETIME:
+ bRes |= ImpGetDateTimeOutput(fNumber, 0, sBuff);
+ bHadStandard = true;
+ break;
+ default: break;
+ }
+ }
+ if ( !bHadStandard )
+ {
+ sal_uInt16 nIx = GetSubformatIndex ( fNumber ); // Index of the partial format
+ if (fNumber < 0.0 &&
+ ((nIx == 0 && IsFirstSubformatRealNegative()) || // 1st, usually positive subformat
+ (nIx == 1 && IsSecondSubformatRealNegative()))) // 2nd, usually negative subformat
+ {
+ fNumber = -fNumber; // eliminate sign
+ }
+ *ppColor = NumFor[nIx].GetColor();
+ const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info();
+ const sal_uInt16 nCnt = NumFor[nIx].GetCount();
+ if (nCnt == 0 && rInfo.eScannedType == SvNumFormatType::EMPTY)
+ {
+ return false; // Empty => nothing
+ }
+ else if (nCnt == 0) // Else Standard Format
+ {
+ ImpGetOutputStandard(fNumber, sBuff);
+ OutString = sBuff.makeStringAndClear();
+ return false;
+ }
+ switch (rInfo.eScannedType)
+ {
+ case SvNumFormatType::TEXT:
+ case SvNumFormatType::DEFINED:
+ for (sal_uInt16 i = 0; i < nCnt; i++)
+ {
+ switch (rInfo.nTypeArray[i])
+ {
+ case NF_SYMBOLTYPE_STAR:
+ if( bStarFlag )
+ {
+ bRes = lcl_appendStarFillChar( sBuff, rInfo.sStrArray[i]);
+ }
+ break;
+ case NF_SYMBOLTYPE_BLANK:
+ if (rInfo.sStrArray[i].getLength() >= 2)
+ InsertBlanks(sBuff, sBuff.getLength(), rInfo.sStrArray[i][1] );
+ break;
+ case NF_SYMBOLTYPE_STRING:
+ case NF_SYMBOLTYPE_CURRENCY:
+ sBuff.append(rInfo.sStrArray[i]);
+ break;
+ case NF_SYMBOLTYPE_THSEP:
+ if (rInfo.nThousand == 0)
+ {
+ sBuff.append(rInfo.sStrArray[i]);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ break;
+ case SvNumFormatType::DATE:
+ bRes |= ImpGetDateOutput(fNumber, nIx, sBuff);
+ break;
+ case SvNumFormatType::TIME:
+ bRes |= ImpGetTimeOutput(fNumber, nIx, sBuff);
+ break;
+ case SvNumFormatType::DATETIME:
+ bRes |= ImpGetDateTimeOutput(fNumber, nIx, sBuff);
+ break;
+ case SvNumFormatType::NUMBER:
+ case SvNumFormatType::PERCENT:
+ case SvNumFormatType::CURRENCY:
+ bRes |= ImpGetNumberOutput(fNumber, nIx, sBuff);
+ break;
+ case SvNumFormatType::LOGICAL:
+ bRes |= ImpGetLogicalOutput(fNumber, nIx, sBuff);
+ break;
+ case SvNumFormatType::FRACTION:
+ bRes |= ImpGetFractionOutput(fNumber, nIx, sBuff);
+ break;
+ case SvNumFormatType::SCIENTIFIC:
+ bRes |= ImpGetScientificOutput(fNumber, nIx, sBuff);
+ break;
+ default: break;
+ }
+ }
+ OutString = sBuff.makeStringAndClear();
+ return bRes;
+}
+
+bool SvNumberformat::ImpGetScientificOutput(double fNumber,
+ sal_uInt16 nIx,
+ OUStringBuffer& sStr)
+{
+ bool bRes = false;
+ bool bSign = false;
+
+ const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info();
+ const sal_uInt16 nCnt = NumFor[nIx].GetCount();
+
+ if (fNumber < 0)
+ {
+ if (nIx == 0) // Not in the ones at the end
+ {
+ bSign = true; // Formats
+ }
+ fNumber = -fNumber;
+ }
+
+ sStr = ::rtl::math::doubleToUString( fNumber,
+ rtl_math_StringFormat_E,
+ rInfo.nCntPre + rInfo.nCntPost - 1, '.' );
+ OUStringBuffer ExpStr;
+ short nExpSign = 1;
+ sal_Int32 nExPos = sStr.indexOf('E');
+ sal_Int32 nDecPos = -1;
+
+ if ( nExPos >= 0 )
+ {
+ // split into mantissa and exponent and get rid of "E+" or "E-"
+ sal_Int32 nExpStart = nExPos + 1;
+
+ switch ( sStr[ nExpStart ] )
+ {
+ case '-' :
+ nExpSign = -1;
+ [[fallthrough]];
+ case '+' :
+ ++nExpStart;
+ break;
+ }
+ ExpStr = sStr.subView( nExpStart ); // part following the "E+"
+ sStr.truncate( nExPos );
+
+ if ( rInfo.nCntPre != 1 ) // rescale Exp
+ {
+ sal_Int32 nExp = OUString::unacquired(ExpStr).toInt32() * nExpSign;
+ sal_Int32 nRescale = (rInfo.nCntPre != 0) ? nExp % static_cast<sal_Int32>(rInfo.nCntPre) : -1;
+ if( nRescale < 0 && rInfo.nCntPre != 0 )
+ nRescale += static_cast<sal_Int32>(rInfo.nCntPre);
+ nExp -= nRescale;
+ if ( nExp < 0 )
+ {
+ nExpSign = -1;
+ nExp = -nExp;
+ }
+ else
+ {
+ nExpSign = 1;
+ }
+ ExpStr = OUString::number( nExp );
+ const sal_Unicode cFirstDigit = sStr[0];
+ // rescale mantissa
+ sStr = ::rtl::math::doubleToUString( fNumber,
+ rtl_math_StringFormat_E,
+ nRescale + rInfo.nCntPost, '.' );
+
+ // sStr now may contain a rounded-up value shifted into the next
+ // magnitude, for example 1.000E+02 (4 digits) for fNumber 99.995
+ // (9.9995E+02 rounded to 3 decimals) but we want the final result
+ // to be 100.00E+00 (5 digits), so for the following fill routines
+ // below to work correctly append a zero decimal.
+ /* TODO: this is awkward, could an engineering notation mode be
+ * introduced to rtl_math_doubleToUString()? */
+ sStr.truncate( sStr.indexOf('E') );
+ if (sStr[0] == '1' && cFirstDigit != '1')
+ sStr.append('0');
+ }
+
+ // cut any decimal delimiter
+ sal_Int32 index = 0;
+
+ while((index = sStr.indexOf('.', index)) >= 0)
+ {
+ if (nDecPos < 0)
+ nDecPos = index;
+ sStr.remove(index, 1);
+ }
+ }
+
+ sal_uInt16 j = nCnt-1; // Last symbol
+ sal_Int32 k = ExpStr.getLength() - 1; // Position in ExpStr
+ sal_Int32 nZeros = 0; // Erase leading zeros
+
+ // erase all leading zeros except last one
+ while (nZeros < k && ExpStr[nZeros] == '0')
+ {
+ ++nZeros;
+ }
+ if (nZeros)
+ {
+ ExpStr.remove( 0, nZeros);
+ }
+
+ // restore leading zeros or blanks according to format '0' or '?' tdf#156449
+ bRes |= ImpNumberFill(ExpStr, fNumber, k, j, nIx, NF_SYMBOLTYPE_EXP);
+
+ bool bCont = true;
+
+ if (rInfo.nTypeArray[j] == NF_SYMBOLTYPE_EXP)
+ {
+ const OUString& rStr = rInfo.sStrArray[j];
+ if (nExpSign == -1)
+ {
+ ExpStr.insert(0, '-');
+ }
+ else if (rStr.getLength() > 1 && rStr[1] == '+')
+ {
+ ExpStr.insert(0, '+');
+ }
+ ExpStr.insert(0, rStr[0]);
+ if ( j )
+ {
+ j--;
+ }
+ else
+ {
+ bCont = false;
+ }
+ }
+ // Continue main number:
+ if ( !bCont )
+ {
+ sStr.truncate();
+ }
+ else
+ {
+ bRes |= ImpDecimalFill(sStr, fNumber, nDecPos, j, nIx, false);
+ }
+
+ if (bSign)
+ {
+ sStr.insert(0, '-');
+ }
+ sStr.append(ExpStr);
+
+ return bRes;
+}
+
+double SvNumberformat::GetRoundFractionValue ( double fNumber ) const
+{
+ sal_uInt16 nIx = GetSubformatIndex ( fNumber );
+ double fIntPart = 0.0; // integer part of fraction
+ sal_Int64 nFrac = 0, nDiv = 1; // numerator and denominator
+ double fSign = (fNumber < 0.0) ? -1.0 : 1.0;
+ // fNumber is modified in ImpGetFractionElements to absolute fractional part
+ ImpGetFractionElements ( fNumber, nIx, fIntPart, nFrac, nDiv );
+ if ( nDiv > 0 )
+ return fSign * ( fIntPart + static_cast<double>(nFrac) / static_cast<double>(nDiv) );
+ else
+ return fSign * fIntPart;
+}
+
+void SvNumberformat::ImpGetFractionElements ( double& fNumber, sal_uInt16 nIx,
+ double& fIntPart, sal_Int64& nFrac, sal_Int64& nDiv ) const
+{
+ if ( fNumber < 0.0 )
+ fNumber = -fNumber;
+ fIntPart = floor(fNumber); // Integral part
+ fNumber -= fIntPart; // Fractional part
+ const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info();
+ sal_Int64 nForcedDiv = lcl_GetDenominatorString( rInfo, NumFor[nIx].GetCount() ).toInt32();
+ if( nForcedDiv > 0 )
+ { // Forced Denominator
+ nDiv = nForcedDiv;
+ nFrac = static_cast<sal_Int64>(floor ( fNumber * nDiv ));
+ double fFracNew = static_cast<double>(nFrac) / static_cast<double>(nDiv);
+ double fFracNew1 = static_cast<double>(nFrac + 1) / static_cast<double>(nDiv);
+ double fDiff = fNumber - fFracNew;
+ if( fDiff > ( fFracNew1 - fNumber ) )
+ {
+ nFrac++;
+ }
+ }
+ else // Calculated Denominator
+ {
+ nDiv = 1;
+ sal_Int64 nBasis = static_cast<sal_Int64>(floor( pow(10.0,rInfo.nCntExp))) - 1; // 9, 99, 999 ,...
+ sal_Int64 nFracPrev = 1, nDivPrev = 0, nFracNext, nDivNext, nPartialDenom;
+ double fRemainder = fNumber;
+
+ // Use continued fraction representation of fNumber
+ // See https://en.wikipedia.org/wiki/Continued_fraction#Best_rational_approximations
+ while ( fRemainder > 0.0 )
+ {
+ double fTemp = 1.0 / fRemainder; // 64bits precision required when fRemainder is very weak
+ nPartialDenom = static_cast<sal_Int64>(floor(fTemp)); // due to floating point notation with double precision
+ fRemainder = fTemp - static_cast<double>(nPartialDenom);
+ nDivNext = nPartialDenom * nDiv + nDivPrev;
+ if ( nDivNext <= nBasis ) // continue loop
+ {
+ nFracNext = nPartialDenom * nFrac + nFracPrev;
+ nFracPrev = nFrac;
+ nFrac = nFracNext;
+ nDivPrev = nDiv;
+ nDiv = nDivNext;
+ }
+ else // calculate collateral fraction and exit
+ {
+ sal_Int64 nCollat = (nBasis - nDivPrev) / nDiv;
+ if ( 2 * nCollat >= nPartialDenom )
+ {
+ sal_Int64 nFracTest = nCollat * nFrac + nFracPrev;
+ sal_Int64 nDivTest = nCollat * nDiv + nDivPrev;
+ double fSign = (static_cast<double>(nFrac) > fNumber * static_cast<double>(nDiv))?1.0:-1.0;
+ if ( fSign * ( double(nFrac * nDivTest + nDiv * nFracTest) - 2.0 * double(nDiv * nDivTest) * fNumber ) > 0.0 )
+ {
+ nFrac = nFracTest;
+ nDiv = nDivTest;
+ }
+ }
+ fRemainder = 0.0; // exit while loop
+ }
+ }
+ }
+ if (nFrac >= nDiv)
+ {
+ ++fIntPart;
+ nFrac = 0;
+ nDiv = ( nForcedDiv > 0 ) ? nForcedDiv : 1;
+ }
+}
+
+bool SvNumberformat::ImpGetFractionOutput(double fNumber,
+ sal_uInt16 nIx,
+ OUStringBuffer& sBuff)
+{
+ bool bRes = false;
+ const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info();
+ const sal_uInt16 nCnt = NumFor[nIx].GetCount();
+ OUStringBuffer sStr, sFrac, sDiv; // Strings, value for Integral part Numerator and denominator
+ bool bSign = ( (fNumber < 0) && (nIx == 0) ); // sign Not in the ones at the end
+ const OUString sIntegerFormat = lcl_GetFractionIntegerString(rInfo, nCnt);
+ const OUString sNumeratorFormat = lcl_GetNumeratorString(rInfo, nCnt);
+ const OUString sDenominatorFormat = lcl_GetDenominatorString(rInfo, nCnt);
+
+ sal_Int64 nFrac = 0, nDiv = 1;
+ double fNum = floor(fNumber); // Integral part
+
+ if (fNum > D_MAX_U_INT32 || rInfo.nCntExp > 9) // Too large
+ {
+ sBuff = ImpSvNumberformatScan::sErrStr;
+ return false;
+ }
+ if (rInfo.nCntExp == 0)
+ {
+ SAL_WARN( "svl.numbers", "SvNumberformat:: Fraction, nCntExp == 0");
+ sBuff.truncate();
+ return false;
+ }
+
+ ImpGetFractionElements( fNumber, nIx, fNum, nFrac, nDiv);
+
+ if (rInfo.nCntPre == 0) // Improper fraction
+ {
+ double fNum1 = fNum * static_cast<double>(nDiv) + static_cast<double>(nFrac);
+
+ if (fNum1 > D_MAX_INTEGER)
+ {
+ sBuff = ImpSvNumberformatScan::sErrStr;
+ return false;
+ }
+ nFrac = static_cast<sal_Int64>(floor(fNum1));
+ }
+ else if (fNum == 0.0 && nFrac != 0)
+ {
+ }
+ else
+ {
+ char aBuf[100];
+ o3tl::sprintf( aBuf, "%.f", fNum ); // simple rounded integer
+ sStr.appendAscii( aBuf );
+ impTransliterate(sStr, NumFor[nIx].GetNatNum());
+ }
+ bool bHideFraction = (rInfo.nCntPre > 0 && nFrac == 0
+ && (sNumeratorFormat.indexOf('0') < 0)
+ && (sDenominatorFormat.indexOf('0') < 0
+ || sDenominatorFormat.toInt32() > 0) );
+ if ( bHideFraction )
+ {
+ sDiv.truncate();
+ }
+ else // if there are some '0' in format, force display of fraction
+ {
+ sFrac = ImpIntToString( nIx, nFrac );
+ sDiv = ImpIntToString( nIx, nDiv );
+ }
+
+ sal_uInt16 j = nCnt-1; // Last symbol -> backwards
+ sal_Int32 k; // Denominator
+
+ bRes |= ImpNumberFill(sDiv, fNumber, k, j, nIx, NF_SYMBOLTYPE_FRAC, true);
+
+ bool bCont = true;
+ if (rInfo.nTypeArray[j] == NF_SYMBOLTYPE_FRAC)
+ {
+ if ( bHideFraction )
+ { // do not insert blank for fraction if there is no '?'
+ if ( sNumeratorFormat.indexOf('?') >= 0
+ || sDenominatorFormat.indexOf('?') >= 0 )
+ sDiv.insert(0, ' ');
+ }
+ else
+ {
+ sDiv.insert(0, rInfo.sStrArray[j][0]);
+ }
+ if ( j )
+ {
+ j--;
+ }
+ else
+ {
+ bCont = false;
+ }
+ }
+ // Further numerators:
+ if ( !bCont )
+ {
+ sFrac.truncate();
+ }
+ else
+ {
+ bRes |= ImpNumberFill(sFrac, fNumber, k, j, nIx, NF_SYMBOLTYPE_FRACBLANK);
+ bCont = false; // there is no integer part?
+ if (rInfo.nTypeArray[j] == NF_SYMBOLTYPE_FRACBLANK)
+ {
+ if ( j )
+ {
+ if ( bHideFraction )
+ { // '?' in any format force display of blank as delimiter
+ if ( sIntegerFormat.indexOf('?') >= 0
+ || sNumeratorFormat.indexOf('?') >= 0
+ || sDenominatorFormat.indexOf('?') >= 0 )
+ {
+ for (sal_Int32 i = 0; i < rInfo.sStrArray[j].getLength(); i++)
+ sFrac.insert(0, ' ');
+ }
+ }
+ else
+ {
+ if ( fNum != 0.0 || sIntegerFormat.indexOf('0') >= 0 )
+ sFrac.insert(0, rInfo.sStrArray[j]); // insert Blank string only if there are both integer and fraction
+ else
+ {
+ if ( sIntegerFormat.indexOf('?') >= 0
+ || sNumeratorFormat.indexOf('?') >= 0 )
+ {
+ for (sal_Int32 i = 0; i < rInfo.sStrArray[j].getLength(); i++)
+ sFrac.insert(0, ' ');
+ }
+ }
+ }
+ j--;
+ bCont = true; // Yes, there is an integer
+ }
+ else
+ sFrac.insert(0, rInfo.sStrArray[j]);
+ }
+ }
+ // Continue integer part
+ if ( !bCont )
+ {
+ sStr.truncate();
+ }
+ else
+ {
+ k = sStr.getLength(); // After last figure
+ bRes |= ImpNumberFillWithThousands(sStr, fNumber, k, j, nIx,
+ rInfo.nCntPre);
+ }
+ if (bSign && (nFrac != 0 || fNum != 0.0))
+ {
+ sBuff.insert(0, '-'); // Not -0
+ }
+ sBuff.append(sStr);
+ sBuff.append(sFrac);
+ sBuff.append(sDiv);
+ return bRes;
+}
+
+sal_uInt16 SvNumberformat::ImpGetFractionOfSecondString( OUStringBuffer& rBuf, double fFractionOfSecond,
+ int nFractionDecimals, bool bAddOneRoundingDecimal, sal_uInt16 nIx, sal_uInt16 nMinimumInputLineDecimals )
+{
+ if (!nFractionDecimals)
+ return 0;
+
+ // nFractionDecimals+1 to not round up what Time::GetClock() carefully
+ // truncated.
+ rBuf.append( rtl::math::doubleToUString( fFractionOfSecond, rtl_math_StringFormat_F,
+ (bAddOneRoundingDecimal ? nFractionDecimals + 1 : nFractionDecimals), '.'));
+ rBuf.stripStart('0');
+ rBuf.stripStart('.');
+ if (bAddOneRoundingDecimal && rBuf.getLength() > nFractionDecimals)
+ rBuf.truncate( nFractionDecimals); // the digit appended because of nFractionDecimals+1
+ if (nMinimumInputLineDecimals)
+ {
+ rBuf.stripEnd('0');
+ for (sal_Int32 index = rBuf.getLength(); index < nMinimumInputLineDecimals; ++index)
+ {
+ rBuf.append('0');
+ }
+ impTransliterate(rBuf, NumFor[nIx].GetNatNum());
+ nFractionDecimals = rBuf.getLength();
+ }
+ else
+ {
+ impTransliterate(rBuf, NumFor[nIx].GetNatNum());
+ }
+ return static_cast<sal_uInt16>(nFractionDecimals);
+}
+
+bool SvNumberformat::ImpGetTimeOutput(double fNumber,
+ sal_uInt16 nIx,
+ OUStringBuffer& sBuff)
+{
+ using namespace ::com::sun::star::i18n;
+ bool bCalendarSet = false;
+ const double fNumberOrig = fNumber;
+ bool bRes = false;
+ bool bSign = false;
+ if (fNumber < 0.0)
+ {
+ fNumber = -fNumber;
+ if (nIx == 0)
+ {
+ bSign = true;
+ }
+ }
+ const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info();
+ bool bInputLine;
+ sal_Int32 nCntPost;
+ if ( rScan.GetStandardPrec() == SvNumberFormatter::INPUTSTRING_PRECISION &&
+ 0 < rInfo.nCntPost && rInfo.nCntPost < kTimeSignificantRound )
+ {
+ bInputLine = true;
+ nCntPost = kTimeSignificantRound;
+ }
+ else
+ {
+ bInputLine = false;
+ nCntPost = rInfo.nCntPost;
+ }
+
+ OUStringBuffer sSecStr;
+ sal_Int32 nSecPos = 0; // For figure by figure processing
+ sal_uInt32 nHour, nMin, nSec;
+ if (!rInfo.bThousand) // No [] format
+ {
+ sal_uInt16 nCHour, nCMinute, nCSecond;
+ double fFractionOfSecond;
+ tools::Time::GetClock( fNumberOrig, nCHour, nCMinute, nCSecond, fFractionOfSecond, nCntPost);
+ nHour = nCHour;
+ nMin = nCMinute;
+ nSec = nCSecond;
+ nCntPost = ImpGetFractionOfSecondString( sSecStr, fFractionOfSecond, nCntPost, true, nIx,
+ (bInputLine ? rInfo.nCntPost : 0));
+ }
+ else
+ {
+ const double fTime = rtl::math::round( fNumber * 86400.0, int(nCntPost));
+ if (bSign && fTime == 0.0)
+ {
+ bSign = false; // Not -00:00:00
+ }
+ if (fTime > D_MAX_U_INT32)
+ {
+ sBuff = ImpSvNumberformatScan::sErrStr;
+ return false;
+ }
+ sal_uInt32 nSeconds = static_cast<sal_uInt32>(fTime);
+
+ nCntPost = ImpGetFractionOfSecondString( sSecStr, fTime - nSeconds, nCntPost, false, nIx,
+ (bInputLine ? rInfo.nCntPost : 0));
+
+ if (rInfo.nThousand == 3) // [ss]
+ {
+ nHour = 0;
+ nMin = 0;
+ nSec = nSeconds;
+ }
+ else if (rInfo.nThousand == 2) // [mm]:ss
+ {
+ nHour = 0;
+ nMin = nSeconds / 60;
+ nSec = nSeconds % 60;
+ }
+ else if (rInfo.nThousand == 1) // [hh]:mm:ss
+ {
+ nHour = nSeconds / 3600;
+ nMin = (nSeconds%3600) / 60;
+ nSec = nSeconds%60;
+ }
+ else
+ {
+ // TODO What should these be set to?
+ nHour = 0;
+ nMin = 0;
+ nSec = 0;
+ }
+ }
+
+ sal_Unicode cAmPm = ' '; // a or p
+ if (rInfo.nCntExp) // AM/PM
+ {
+ if (nHour == 0)
+ {
+ nHour = 12;
+ cAmPm = 'a';
+ }
+ else if (nHour < 12)
+ {
+ cAmPm = 'a';
+ }
+ else
+ {
+ cAmPm = 'p';
+ if (nHour > 12)
+ {
+ nHour -= 12;
+ }
+ }
+ }
+ const sal_uInt16 nCnt = NumFor[nIx].GetCount();
+ for (sal_uInt16 i = 0; i < nCnt; i++)
+ {
+ sal_Int32 nLen;
+ switch (rInfo.nTypeArray[i])
+ {
+ case NF_SYMBOLTYPE_STAR:
+ if( bStarFlag )
+ {
+ bRes = lcl_appendStarFillChar( sBuff, rInfo.sStrArray[i]);
+ }
+ break;
+ case NF_SYMBOLTYPE_BLANK:
+ if (rInfo.sStrArray[i].getLength() >= 2)
+ InsertBlanks(sBuff, sBuff.getLength(), rInfo.sStrArray[i][1] );
+ break;
+ case NF_SYMBOLTYPE_STRING:
+ case NF_SYMBOLTYPE_CURRENCY:
+ case NF_SYMBOLTYPE_DATESEP:
+ case NF_SYMBOLTYPE_TIMESEP:
+ case NF_SYMBOLTYPE_TIME100SECSEP:
+ sBuff.append(rInfo.sStrArray[i]);
+ break;
+ case NF_SYMBOLTYPE_DIGIT:
+ nLen = ( bInputLine && i > 0 &&
+ (rInfo.nTypeArray[i-1] == NF_SYMBOLTYPE_STRING ||
+ rInfo.nTypeArray[i-1] == NF_SYMBOLTYPE_TIME100SECSEP) ?
+ nCntPost : rInfo.sStrArray[i].getLength() );
+ for (sal_Int32 j = 0; j < nLen && nSecPos < nCntPost && nSecPos < sSecStr.getLength(); ++j)
+ {
+ sBuff.append(sSecStr[nSecPos]);
+ nSecPos++;
+ }
+ break;
+ case NF_KEY_AMPM: // AM/PM
+ if ( !bCalendarSet )
+ {
+ double fDiff = DateTime::Sub( DateTime(rScan.GetNullDate()), GetCal().getEpochStart());
+ fDiff += fNumberOrig;
+ GetCal().setLocalDateTime( fDiff );
+ bCalendarSet = true;
+ }
+ if (cAmPm == 'a')
+ {
+ sBuff.append(GetCal().getDisplayName(
+ CalendarDisplayIndex::AM_PM, AmPmValue::AM, 0 ));
+ }
+ else
+ {
+ sBuff.append(GetCal().getDisplayName(
+ CalendarDisplayIndex::AM_PM, AmPmValue::PM, 0 ));
+ }
+ break;
+ case NF_KEY_AP: // A/P
+ if (cAmPm == 'a')
+ {
+ sBuff.append('a');
+ }
+ else
+ {
+ sBuff.append('p');
+ }
+ break;
+ case NF_KEY_MI: // M
+ sBuff.append(ImpIntToString( nIx, nMin ));
+ break;
+ case NF_KEY_MMI: // MM
+ sBuff.append(ImpIntToString( nIx, nMin, 2 ));
+ break;
+ case NF_KEY_H: // H
+ sBuff.append(ImpIntToString( nIx, nHour ));
+ break;
+ case NF_KEY_HH: // HH
+ sBuff.append(ImpIntToString( nIx, nHour, 2 ));
+ break;
+ case NF_KEY_S: // S
+ sBuff.append(ImpIntToString( nIx, nSec ));
+ break;
+ case NF_KEY_SS: // SS
+ sBuff.append(ImpIntToString( nIx, nSec, 2 ));
+ break;
+ default:
+ break;
+ }
+ }
+ if (bSign && rInfo.bThousand)
+ {
+ sBuff.insert(0, '-');
+ }
+ return bRes;
+}
+
+
+/** If a day of month occurs within the format, the month name is in possessive
+ genitive case if the day follows the month, and partitive case if the day
+ precedes the month. If there is no day of month the nominative case (noun)
+ is returned. Also if the month is immediately preceded or followed by a
+ literal string other than space and not followed by a comma, the nominative
+ name is used, this prevents duplicated casing for MMMM\t\a and such in
+ documents imported from (e.g. Finnish) Excel or older LibO/OOo releases.
+ */
+
+// IDEA: instead of eCodeType pass the index to nTypeArray and restrict
+// inspection of month name around that one, that would enable different month
+// cases in one format. Though probably the most rare use case ever...
+
+sal_Int32 SvNumberformat::ImpUseMonthCase( int & io_nState, const ImpSvNumFor& rNumFor, NfKeywordIndex eCodeType )
+{
+ using namespace ::com::sun::star::i18n;
+ if (!io_nState)
+ {
+ bool bMonthSeen = false;
+ bool bDaySeen = false;
+ const ImpSvNumberformatInfo& rInfo = rNumFor.Info();
+ const sal_uInt16 nCount = rNumFor.GetCount();
+ for (sal_uInt16 i = 0; i < nCount && io_nState == 0; ++i)
+ {
+ sal_Int32 nLen;
+ switch (rInfo.nTypeArray[i])
+ {
+ case NF_KEY_D :
+ case NF_KEY_DD :
+ if (bMonthSeen)
+ {
+ io_nState = 2;
+ }
+ else
+ {
+ bDaySeen = true;
+ }
+ break;
+ case NF_KEY_MMM:
+ case NF_KEY_MMMM:
+ case NF_KEY_MMMMM:
+ if ((i < nCount-1 &&
+ rInfo.nTypeArray[i+1] == NF_SYMBOLTYPE_STRING &&
+ // Literal following, not empty, space nor comma.
+ !rInfo.sStrArray[i+1].isEmpty() &&
+ rInfo.sStrArray[i+1][0] != ' ' && rInfo.sStrArray[i+1][0] != ',') ||
+ (i > 0 && rInfo.nTypeArray[i-1] == NF_SYMBOLTYPE_STRING &&
+ ((nLen = rInfo.sStrArray[i-1].getLength()) > 0) &&
+ // Literal preceding, not space.
+ rInfo.sStrArray[i-1][nLen-1] != ' '))
+ {
+ io_nState = 1;
+ }
+ else if (bDaySeen)
+ {
+ io_nState = 3;
+ }
+ else
+ {
+ bMonthSeen = true;
+ }
+ break;
+ }
+ }
+ if (io_nState == 0)
+ {
+ io_nState = 1; // No day of month
+ }
+ }
+ switch (io_nState)
+ {
+ case 1:
+ // No day of month or forced nominative
+ switch (eCodeType)
+ {
+ case NF_KEY_MMM:
+ return CalendarDisplayCode::SHORT_MONTH_NAME;
+ case NF_KEY_MMMM:
+ return CalendarDisplayCode::LONG_MONTH_NAME;
+ case NF_KEY_MMMMM:
+ return CalendarDisplayCode::NARROW_MONTH_NAME;
+ default:
+ ; // nothing
+ }
+ break;
+ case 2:
+ // Day of month follows month (the month's 17th)
+ switch (eCodeType)
+ {
+ case NF_KEY_MMM:
+ return CalendarDisplayCode::SHORT_GENITIVE_MONTH_NAME;
+ case NF_KEY_MMMM:
+ return CalendarDisplayCode::LONG_GENITIVE_MONTH_NAME;
+ case NF_KEY_MMMMM:
+ return CalendarDisplayCode::NARROW_GENITIVE_MONTH_NAME;
+ default:
+ ; // Nothing
+ }
+ break;
+ case 3:
+ // Day of month precedes month (17 of month)
+ switch (eCodeType)
+ {
+ case NF_KEY_MMM:
+ return CalendarDisplayCode::SHORT_PARTITIVE_MONTH_NAME;
+ case NF_KEY_MMMM:
+ return CalendarDisplayCode::LONG_PARTITIVE_MONTH_NAME;
+ case NF_KEY_MMMMM:
+ return CalendarDisplayCode::NARROW_PARTITIVE_MONTH_NAME;
+ default:
+ ; // nothing
+ }
+ break;
+ }
+ SAL_WARN( "svl.numbers", "ImpUseMonthCase: unhandled keyword index eCodeType");
+ return CalendarDisplayCode::LONG_MONTH_NAME;
+}
+
+
+bool SvNumberformat::ImpIsOtherCalendar( const ImpSvNumFor& rNumFor ) const
+{
+ if ( GetCal().getUniqueID() != GREGORIAN )
+ {
+ return false;
+ }
+ const ImpSvNumberformatInfo& rInfo = rNumFor.Info();
+ const sal_uInt16 nCnt = rNumFor.GetCount();
+ sal_uInt16 i;
+ for ( i = 0; i < nCnt; i++ )
+ {
+ switch ( rInfo.nTypeArray[i] )
+ {
+ case NF_SYMBOLTYPE_CALENDAR :
+ return false;
+ case NF_KEY_EC :
+ case NF_KEY_EEC :
+ case NF_KEY_R :
+ case NF_KEY_RR :
+ case NF_KEY_AAA :
+ case NF_KEY_AAAA :
+ case NF_KEY_G :
+ case NF_KEY_GG :
+ case NF_KEY_GGG :
+ return true;
+ }
+ }
+ return false;
+}
+
+void SvNumberformat::SwitchToOtherCalendar( OUString& rOrgCalendar,
+ double& fOrgDateTime ) const
+{
+ CalendarWrapper& rCal = GetCal();
+ if ( rCal.getUniqueID() != GREGORIAN )
+ return;
+
+ using namespace ::com::sun::star::i18n;
+ const css::uno::Sequence< OUString > xCals = rCal.getAllCalendars(
+ rLoc().getLanguageTag().getLocale() );
+ sal_Int32 nCnt = xCals.getLength();
+ if ( nCnt <= 1 )
+ return;
+
+ auto pCal = std::find_if(xCals.begin(), xCals.end(),
+ [](const OUString& rCalName) { return rCalName != GREGORIAN; });
+ if (pCal == xCals.end())
+ return;
+
+ if ( !rOrgCalendar.getLength() )
+ {
+ rOrgCalendar = rCal.getUniqueID();
+ fOrgDateTime = rCal.getDateTime();
+ }
+ rCal.loadCalendar( *pCal, rLoc().getLanguageTag().getLocale() );
+ rCal.setDateTime( fOrgDateTime );
+}
+
+void SvNumberformat::SwitchToGregorianCalendar( std::u16string_view rOrgCalendar,
+ double fOrgDateTime ) const
+{
+ CalendarWrapper& rCal = GetCal();
+ if ( rOrgCalendar.size() && rCal.getUniqueID() != GREGORIAN )
+ {
+ rCal.loadCalendar( GREGORIAN, rLoc().getLanguageTag().getLocale() );
+ rCal.setDateTime( fOrgDateTime );
+ }
+}
+
+bool SvNumberformat::ImpFallBackToGregorianCalendar( OUString& rOrgCalendar, double& fOrgDateTime )
+{
+ using namespace ::com::sun::star::i18n;
+ CalendarWrapper& rCal = GetCal();
+ if ( rCal.getUniqueID() != GREGORIAN )
+ {
+ sal_Int16 nVal = rCal.getValue( CalendarFieldIndex::ERA );
+ if ( nVal == 0 && rCal.getLoadedCalendar().Eras[0].ID == "Dummy" )
+ {
+ if ( !rOrgCalendar.getLength() )
+ {
+ rOrgCalendar = rCal.getUniqueID();
+ fOrgDateTime = rCal.getDateTime();
+ }
+ else if ( rOrgCalendar == GREGORIAN )
+ {
+ rOrgCalendar.clear();
+ }
+ rCal.loadCalendar( GREGORIAN, rLoc().getLanguageTag().getLocale() );
+ rCal.setDateTime( fOrgDateTime );
+ return true;
+ }
+ }
+ return false;
+}
+
+
+#ifdef THE_FUTURE
+/* XXX NOTE: even if the ImpSwitchToSpecifiedCalendar method is currently
+ * unused please don't remove it, it would be needed by
+ * SwitchToSpecifiedCalendar(), see comment in
+ * ImpSvNumberInputScan::GetDateRef() */
+
+bool SvNumberformat::ImpSwitchToSpecifiedCalendar( OUString& rOrgCalendar,
+ double& fOrgDateTime,
+ const ImpSvNumFor& rNumFor ) const
+{
+ const ImpSvNumberformatInfo& rInfo = rNumFor.Info();
+ const sal_uInt16 nCnt = rNumFor.GetCount();
+ for ( sal_uInt16 i = 0; i < nCnt; i++ )
+ {
+ if ( rInfo.nTypeArray[i] == NF_SYMBOLTYPE_CALENDAR )
+ {
+ CalendarWrapper& rCal = GetCal();
+ if ( !rOrgCalendar.getLength() )
+ {
+ rOrgCalendar = rCal.getUniqueID();
+ fOrgDateTime = rCal.getDateTime();
+ }
+ rCal.loadCalendar( rInfo.sStrArray[i], rLoc().getLocale() );
+ rCal.setDateTime( fOrgDateTime );
+ return true;
+ }
+ }
+ return false;
+}
+#endif
+
+// static
+void SvNumberformat::ImpAppendEraG( OUStringBuffer& OutString,
+ const CalendarWrapper& rCal,
+ sal_Int16 nNatNum )
+{
+ using namespace ::com::sun::star::i18n;
+ if ( rCal.getUniqueID() == "gengou" )
+ {
+ sal_Unicode cEra;
+ sal_Int16 nVal = rCal.getValue( CalendarFieldIndex::ERA );
+ switch ( nVal )
+ {
+ case 1:
+ cEra = 'M';
+ break;
+ case 2:
+ cEra = 'T';
+ break;
+ case 3:
+ cEra = 'S';
+ break;
+ case 4:
+ cEra = 'H';
+ break;
+ case 5:
+ cEra = 'R';
+ break;
+ default:
+ cEra = '?';
+ break;
+ }
+ OutString.append(cEra);
+ }
+ else
+ {
+ OutString.append(rCal.getDisplayString( CalendarDisplayCode::SHORT_ERA, nNatNum ));
+ }
+}
+
+bool SvNumberformat::ImpIsIso8601( const ImpSvNumFor& rNumFor ) const
+{
+ bool bIsIso = false;
+ if (eType & SvNumFormatType::DATE)
+ {
+ enum State
+ {
+ eNone,
+ eAtYear,
+ eAtSep1,
+ eAtMonth,
+ eAtSep2,
+ eNotIso
+ };
+ State eState = eNone;
+ auto & rTypeArray = rNumFor.Info().nTypeArray;
+ sal_uInt16 nCnt = rNumFor.GetCount();
+ for (sal_uInt16 i=0; i < nCnt && !bIsIso && eState != eNotIso; ++i)
+ {
+ switch ( rTypeArray[i] )
+ {
+ case NF_KEY_YY: // two digits not strictly ISO 8601
+ case NF_KEY_YYYY:
+ if (eState != eNone)
+ {
+ eState = eNotIso;
+ }
+ else
+ {
+ eState = eAtYear;
+ }
+ break;
+ case NF_KEY_M: // single digit not strictly ISO 8601
+ case NF_KEY_MM:
+ if (eState != eAtSep1)
+ {
+ eState = eNotIso;
+ }
+ else
+ {
+ eState = eAtMonth;
+ }
+ break;
+ case NF_KEY_D: // single digit not strictly ISO 8601
+ case NF_KEY_DD:
+ if (eState != eAtSep2)
+ {
+ eState = eNotIso;
+ }
+ else
+ {
+ bIsIso = true;
+ }
+ break;
+ case NF_SYMBOLTYPE_STRING:
+ case NF_SYMBOLTYPE_DATESEP:
+ if (rNumFor.Info().sStrArray[i] == "-")
+ {
+ if (eState == eAtYear)
+ {
+ eState = eAtSep1;
+ }
+ else if (eState == eAtMonth)
+ {
+ eState = eAtSep2;
+ }
+ else
+ {
+ eState = eNotIso;
+ }
+ }
+ else
+ {
+ eState = eNotIso;
+ }
+ break;
+ default:
+ eState = eNotIso;
+ }
+ }
+ }
+ else
+ {
+ SAL_WARN( "svl.numbers", "SvNumberformat::ImpIsIso8601: no date" );
+ }
+ return bIsIso;
+}
+
+static bool lcl_hasEra( const ImpSvNumFor& rNumFor )
+{
+ const ImpSvNumberformatInfo& rInfo = rNumFor.Info();
+ const sal_uInt16 nCnt = rNumFor.GetCount();
+ for ( sal_uInt16 i = 0; i < nCnt; i++ )
+ {
+ switch ( rInfo.nTypeArray[i] )
+ {
+ case NF_KEY_RR :
+ case NF_KEY_G :
+ case NF_KEY_GG :
+ case NF_KEY_GGG :
+ return true;
+ }
+ }
+ return false;
+}
+
+static bool lcl_isSignedYear( const CalendarWrapper& rCal, const ImpSvNumFor& rNumFor )
+{
+ return rCal.getValue( css::i18n::CalendarFieldIndex::ERA ) == 0 &&
+ rCal.getUniqueID() == GREGORIAN && !lcl_hasEra( rNumFor );
+}
+
+/* XXX: if needed this could be stripped from rEpochStart and diff adding and
+ * moved to tools' DateTime to be reused elsewhere. */
+static bool lcl_getValidDate( const DateTime& rNullDate, const DateTime& rEpochStart, double& fNumber )
+{
+ static const DateTime aCE( Date(1,1,1));
+ static const DateTime aMin( Date(1,1, SAL_MIN_INT16));
+ static const DateTime aMax( Date(31,12, SAL_MAX_INT16), tools::Time(23,59,59, tools::Time::nanoSecPerSec - 1));
+ static const double fMin = DateTime::Sub( aMin, aCE);
+ static const double fMax = DateTime::Sub( aMax, aCE);
+ // Value must be representable in our tools::Date proleptic Gregorian
+ // calendar as well.
+ const double fOff = DateTime::Sub( rNullDate, aCE) + fNumber;
+ // Add diff between epochs to serial date number.
+ const double fDiff = DateTime::Sub( rNullDate, rEpochStart);
+ fNumber += fDiff;
+ return fMin <= fOff && fOff <= fMax;
+}
+
+bool SvNumberformat::ImpGetDateOutput(double fNumber,
+ sal_uInt16 nIx,
+ OUStringBuffer& sBuff)
+{
+ using namespace ::com::sun::star::i18n;
+ bool bRes = false;
+
+ CalendarWrapper& rCal = GetCal();
+ if (!lcl_getValidDate( rScan.GetNullDate(), rCal.getEpochStart(), fNumber))
+ {
+ sBuff = ImpSvNumberformatScan::sErrStr;
+ return false;
+ }
+ rCal.setLocalDateTime( fNumber );
+ int nUseMonthCase = 0; // Not decided yet
+ OUString aOrgCalendar; // empty => not changed yet
+
+ double fOrgDateTime(0.0);
+ bool bOtherCalendar = ImpIsOtherCalendar( NumFor[nIx] );
+ if ( bOtherCalendar )
+ {
+ SwitchToOtherCalendar( aOrgCalendar, fOrgDateTime );
+ }
+ if ( ImpFallBackToGregorianCalendar( aOrgCalendar, fOrgDateTime ) )
+ {
+ bOtherCalendar = false;
+ }
+ const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info();
+ const sal_uInt16 nCnt = NumFor[nIx].GetCount();
+ sal_Int16 nNatNum = NumFor[nIx].GetNatNum().GetNatNum();
+ OUString aStr;
+
+ // NatNum12: if the date format contains more than a date
+ // field, it needs to specify in NatNum12 argument
+ // which date element needs special formatting:
+ //
+ // '[NatNum12 ordinal-number]D' -> "1st"
+ // '[NatNum12 D=ordinal-number]D" of "MMMM' -> "1st of April"
+ // '[NatNum12 D=ordinal]D" of "MMMM' -> "first of April"
+ // '[NatNum12 YYYY=year,D=ordinal]D" of "MMMM", "YYYY' -> "first of April, nineteen ninety"
+ //
+ // Note: set only for YYYY, MMMM, M, DDDD, D and NNN/AAAA in date formats.
+ // Additionally for MMMMM, MMM, DDD and NN/AA to support at least
+ // capitalize, upper, lower, title.
+ // XXX It's possible to extend this for other keywords and date + time
+ // combinations, as required.
+
+ bool bUseSpellout = NatNumTakesParameters(nNatNum) &&
+ (nCnt == 1 || NumFor[nIx].GetNatNum().GetParams().indexOf('=') > -1);
+
+ for (sal_uInt16 i = 0; i < nCnt; i++)
+ {
+ switch (rInfo.nTypeArray[i])
+ {
+ case NF_SYMBOLTYPE_CALENDAR :
+ if ( !aOrgCalendar.getLength() )
+ {
+ aOrgCalendar = rCal.getUniqueID();
+ fOrgDateTime = rCal.getDateTime();
+ }
+ rCal.loadCalendar( rInfo.sStrArray[i], rLoc().getLanguageTag().getLocale() );
+ rCal.setDateTime( fOrgDateTime );
+ ImpFallBackToGregorianCalendar( aOrgCalendar, fOrgDateTime );
+ break;
+ case NF_SYMBOLTYPE_STAR:
+ if( bStarFlag )
+ {
+ bRes = lcl_appendStarFillChar( sBuff, rInfo.sStrArray[i]);
+ }
+ break;
+ case NF_SYMBOLTYPE_BLANK:
+ if (rInfo.sStrArray[i].getLength() >= 2)
+ InsertBlanks( sBuff, sBuff.getLength(), rInfo.sStrArray[i][1] );
+ break;
+ case NF_SYMBOLTYPE_STRING:
+ case NF_SYMBOLTYPE_CURRENCY:
+ case NF_SYMBOLTYPE_DATESEP:
+ case NF_SYMBOLTYPE_TIMESEP:
+ case NF_SYMBOLTYPE_TIME100SECSEP:
+ sBuff.append(rInfo.sStrArray[i]);
+ break;
+ case NF_KEY_M: // M
+ aStr = rCal.getDisplayString( CalendarDisplayCode::SHORT_MONTH, nNatNum );
+ // NatNum12: support variants of preposition, suffixation or article
+ // for example, Catalan "de març", but "d'abril" etc.
+ if ( bUseSpellout )
+ {
+ aStr = impTransliterate(aStr, NumFor[nIx].GetNatNum(), rInfo.nTypeArray[i]);
+ }
+ sBuff.append(aStr);
+ break;
+ case NF_KEY_MM: // MM
+ sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_MONTH, nNatNum ));
+ break;
+ case NF_KEY_MMM: // MMM
+ case NF_KEY_MMMM: // MMMM
+ case NF_KEY_MMMMM: // MMMMM
+ // NatNum12: support variants of preposition, suffixation or
+ // article, or capitalize, upper, lower, title.
+ // Note: result of the "spell out" conversion can depend from the optional
+ // PartitiveMonths or GenitiveMonths defined in the locale data,
+ // see description of ImpUseMonthCase(), and locale data in
+ // i18npool/source/localedata/data/ and libnumbertext
+ aStr = rCal.getDisplayString( ImpUseMonthCase( nUseMonthCase, NumFor[nIx],
+ static_cast<NfKeywordIndex>(rInfo.nTypeArray[i])),
+ nNatNum);
+ if ( bUseSpellout )
+ {
+ aStr = impTransliterate(aStr, NumFor[nIx].GetNatNum(), rInfo.nTypeArray[i]);
+ }
+ sBuff.append(aStr);
+ break;
+ case NF_KEY_Q: // Q
+ sBuff.append(rCal.getDisplayString( CalendarDisplayCode::SHORT_QUARTER, nNatNum ));
+ break;
+ case NF_KEY_QQ: // QQ
+ sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_QUARTER, nNatNum ));
+ break;
+ case NF_KEY_D: // D
+ aStr = rCal.getDisplayString( CalendarDisplayCode::SHORT_DAY, nNatNum );
+ // NatNum12: support variants of preposition, suffixation or article
+ if ( bUseSpellout )
+ {
+ aStr = impTransliterate(aStr, NumFor[nIx].GetNatNum(), rInfo.nTypeArray[i]);
+ }
+ sBuff.append(aStr);
+ break;
+ case NF_KEY_DD: // DD
+ sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_DAY, nNatNum ));
+ break;
+ case NF_KEY_DDD: // DDD
+ if ( bOtherCalendar )
+ {
+ SwitchToGregorianCalendar( aOrgCalendar, fOrgDateTime );
+ }
+ aStr = rCal.getDisplayString( CalendarDisplayCode::SHORT_DAY_NAME, nNatNum );
+ // NatNum12: support at least capitalize, upper, lower, title
+ if ( bUseSpellout )
+ {
+ aStr = impTransliterate(aStr, NumFor[nIx].GetNatNum(), rInfo.nTypeArray[i]);
+ }
+ sBuff.append(aStr);
+ if ( bOtherCalendar )
+ {
+ SwitchToOtherCalendar( aOrgCalendar, fOrgDateTime );
+ }
+ break;
+ case NF_KEY_DDDD: // DDDD
+ if ( bOtherCalendar )
+ {
+ SwitchToGregorianCalendar( aOrgCalendar, fOrgDateTime );
+ }
+ aStr = rCal.getDisplayString( CalendarDisplayCode::LONG_DAY_NAME, nNatNum );
+ // NatNum12: support variants of preposition, suffixation or article
+ if ( bUseSpellout )
+ {
+ aStr = impTransliterate(aStr, NumFor[nIx].GetNatNum(), rInfo.nTypeArray[i]);
+ }
+ sBuff.append(aStr);
+ if ( bOtherCalendar )
+ {
+ SwitchToOtherCalendar( aOrgCalendar, fOrgDateTime );
+ }
+ break;
+ case NF_KEY_YY: // YY
+ if ( bOtherCalendar )
+ {
+ SwitchToGregorianCalendar( aOrgCalendar, fOrgDateTime );
+ }
+ // Prepend a minus sign if Gregorian BCE and era is not displayed.
+ if (lcl_isSignedYear( rCal, NumFor[nIx] ))
+ {
+ sBuff.append('-');
+ }
+ sBuff.append(rCal.getDisplayString( CalendarDisplayCode::SHORT_YEAR, nNatNum ));
+ if ( bOtherCalendar )
+ {
+ SwitchToOtherCalendar( aOrgCalendar, fOrgDateTime );
+ }
+ break;
+ case NF_KEY_YYYY: // YYYY
+ if ( bOtherCalendar )
+ {
+ SwitchToGregorianCalendar( aOrgCalendar, fOrgDateTime );
+ }
+ // Prepend a minus sign if Gregorian BCE and era is not displayed.
+ if (lcl_isSignedYear( rCal, NumFor[nIx] ))
+ {
+ sBuff.append('-');
+ }
+ aStr = rCal.getDisplayString( CalendarDisplayCode::LONG_YEAR, nNatNum );
+ if (aStr.getLength() < 4 && !lcl_hasEra(NumFor[nIx]))
+ {
+ using namespace comphelper::string;
+ // Ensure that year consists of at least 4 digits, so it
+ // can be distinguished from 2 digits display and edited
+ // without suddenly being hit by the 2-digit year magic.
+ OUStringBuffer aBuf;
+ padToLength(aBuf, 4 - aStr.getLength(), '0');
+ impTransliterate(aBuf, NumFor[nIx].GetNatNum());
+ aBuf.append(aStr);
+ aStr = aBuf.makeStringAndClear();
+ }
+ // NatNum12: support variants of preposition, suffixation or article
+ if ( bUseSpellout )
+ {
+ aStr = impTransliterate(aStr, NumFor[nIx].GetNatNum(), rInfo.nTypeArray[i]);
+ }
+ sBuff.append(aStr);
+ if ( bOtherCalendar )
+ {
+ SwitchToOtherCalendar( aOrgCalendar, fOrgDateTime );
+ }
+ break;
+ case NF_KEY_EC: // E
+ sBuff.append(rCal.getDisplayString( CalendarDisplayCode::SHORT_YEAR, nNatNum ));
+ break;
+ case NF_KEY_EEC: // EE
+ case NF_KEY_R: // R
+ sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_YEAR, nNatNum ));
+ break;
+ case NF_KEY_NN: // NN
+ case NF_KEY_AAA: // AAA
+ aStr = rCal.getDisplayString( CalendarDisplayCode::SHORT_DAY_NAME, nNatNum );
+ // NatNum12: support at least capitalize, upper, lower, title
+ if ( bUseSpellout )
+ {
+ aStr = impTransliterate(aStr, NumFor[nIx].GetNatNum(), rInfo.nTypeArray[i]);
+ }
+ sBuff.append(aStr);
+ break;
+ case NF_KEY_NNN: // NNN
+ case NF_KEY_AAAA: // AAAA
+ aStr = rCal.getDisplayString( CalendarDisplayCode::LONG_DAY_NAME, nNatNum );
+ // NatNum12: support variants of preposition, suffixation or article
+ if ( bUseSpellout )
+ {
+ aStr = impTransliterate(aStr, NumFor[nIx].GetNatNum(), rInfo.nTypeArray[i]);
+ }
+ sBuff.append(aStr);
+ break;
+ case NF_KEY_NNNN: // NNNN
+ sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_DAY_NAME, nNatNum ));
+ sBuff.append(rLoc().getLongDateDayOfWeekSep());
+ break;
+ case NF_KEY_WW : // WW
+ sBuff.append(ImpIntToString( nIx,
+ rCal.getValue( CalendarFieldIndex::WEEK_OF_YEAR )));
+ break;
+ case NF_KEY_G: // G
+ ImpAppendEraG(sBuff, rCal, nNatNum );
+ break;
+ case NF_KEY_GG: // GG
+ sBuff.append(rCal.getDisplayString( CalendarDisplayCode::SHORT_ERA, nNatNum ));
+ break;
+ case NF_KEY_GGG: // GGG
+ sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_ERA, nNatNum ));
+ break;
+ case NF_KEY_RR: // RR => GGGEE
+ sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_YEAR_AND_ERA, nNatNum ));
+ break;
+ }
+ }
+ if ( aOrgCalendar.getLength() )
+ {
+ rCal.loadCalendar( aOrgCalendar, rLoc().getLanguageTag().getLocale() ); // restore calendar
+ }
+ return bRes;
+}
+
+bool SvNumberformat::ImpGetDateTimeOutput(double fNumber,
+ sal_uInt16 nIx,
+ OUStringBuffer& sBuff)
+{
+ using namespace ::com::sun::star::i18n;
+ bool bRes = false;
+
+ CalendarWrapper& rCal = GetCal();
+ if (!lcl_getValidDate( rScan.GetNullDate(), rCal.getEpochStart(), fNumber))
+ {
+ sBuff = ImpSvNumberformatScan::sErrStr;
+ return false;
+ }
+
+ const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info();
+ bool bInputLine;
+ sal_Int32 nCntPost, nFirstRounding;
+ if ( rScan.GetStandardPrec() == SvNumberFormatter::INPUTSTRING_PRECISION &&
+ 0 < rInfo.nCntPost && rInfo.nCntPost < kTimeSignificantRound )
+ {
+ bInputLine = true;
+ nCntPost = nFirstRounding = kTimeSignificantRound;
+ }
+ else
+ {
+ bInputLine = false;
+ nCntPost = rInfo.nCntPost;
+ // For clock format (not []) do not round up to seconds and thus days.
+ nFirstRounding = (rInfo.bThousand ? nCntPost : kTimeSignificantRound);
+ }
+ double fTime = (fNumber - floor( fNumber )) * 86400.0;
+ fTime = ::rtl::math::round( fTime, int(nFirstRounding) );
+ if (fTime >= 86400.0)
+ {
+ // result of fNumber==x.999999999... rounded up, use correct date/time
+ fTime -= 86400.0;
+ fNumber = floor( fNumber + 0.5) + fTime;
+ }
+ rCal.setLocalDateTime( fNumber );
+
+ int nUseMonthCase = 0; // Not decided yet
+ OUString aOrgCalendar; // empty => not changed yet
+ double fOrgDateTime(0.0);
+ bool bOtherCalendar = ImpIsOtherCalendar( NumFor[nIx] );
+ if ( bOtherCalendar )
+ {
+ SwitchToOtherCalendar( aOrgCalendar, fOrgDateTime );
+ }
+ if ( ImpFallBackToGregorianCalendar( aOrgCalendar, fOrgDateTime ) )
+ {
+ bOtherCalendar = false;
+ }
+ sal_Int16 nNatNum = NumFor[nIx].GetNatNum().GetNatNum();
+
+ OUStringBuffer sSecStr;
+ sal_Int32 nSecPos = 0; // For figure by figure processing
+ sal_uInt32 nHour, nMin, nSec;
+ if (!rInfo.bThousand) // No [] format
+ {
+ sal_uInt16 nCHour, nCMinute, nCSecond;
+ double fFractionOfSecond;
+ tools::Time::GetClock( fNumber, nCHour, nCMinute, nCSecond, fFractionOfSecond, nCntPost);
+ nHour = nCHour;
+ nMin = nCMinute;
+ nSec = nCSecond;
+ nCntPost = ImpGetFractionOfSecondString( sSecStr, fFractionOfSecond, nCntPost, true, nIx,
+ (bInputLine ? rInfo.nCntPost : 0));
+ }
+ else
+ {
+ sal_uInt32 nSeconds = static_cast<sal_uInt32>(floor( fTime ));
+
+ nCntPost = ImpGetFractionOfSecondString( sSecStr, fTime - nSeconds, nCntPost, false, nIx,
+ (bInputLine ? rInfo.nCntPost : 0));
+
+ if (rInfo.nThousand == 3) // [ss]
+ {
+ nHour = 0;
+ nMin = 0;
+ nSec = nSeconds;
+ }
+ else if (rInfo.nThousand == 2) // [mm]:ss
+ {
+ nHour = 0;
+ nMin = nSeconds / 60;
+ nSec = nSeconds % 60;
+ }
+ else if (rInfo.nThousand == 1) // [hh]:mm:ss
+ {
+ nHour = nSeconds / 3600;
+ nMin = (nSeconds%3600) / 60;
+ nSec = nSeconds%60;
+ }
+ else
+ {
+ nHour = 0; // TODO What should these values be?
+ nMin = 0;
+ nSec = 0;
+ }
+ }
+ sal_Unicode cAmPm = ' '; // a or p
+ if (rInfo.nCntExp) // AM/PM
+ {
+ if (nHour == 0)
+ {
+ nHour = 12;
+ cAmPm = 'a';
+ }
+ else if (nHour < 12)
+ {
+ cAmPm = 'a';
+ }
+ else
+ {
+ cAmPm = 'p';
+ if (nHour > 12)
+ {
+ nHour -= 12;
+ }
+ }
+ }
+ const sal_uInt16 nCnt = NumFor[nIx].GetCount();
+ sal_Int32 nLen;
+ OUString aYear;
+ for (sal_uInt16 i = 0; i < nCnt; i++)
+ {
+ switch (rInfo.nTypeArray[i])
+ {
+ case NF_SYMBOLTYPE_CALENDAR :
+ if ( !aOrgCalendar.getLength() )
+ {
+ aOrgCalendar = rCal.getUniqueID();
+ fOrgDateTime = rCal.getDateTime();
+ }
+ rCal.loadCalendar( rInfo.sStrArray[i], rLoc().getLanguageTag().getLocale() );
+ rCal.setDateTime( fOrgDateTime );
+ ImpFallBackToGregorianCalendar( aOrgCalendar, fOrgDateTime );
+ break;
+ case NF_SYMBOLTYPE_STAR:
+ if( bStarFlag )
+ {
+ bRes = lcl_appendStarFillChar( sBuff, rInfo.sStrArray[i]);
+ }
+ break;
+ case NF_SYMBOLTYPE_BLANK:
+ if (rInfo.sStrArray[i].getLength() >= 2)
+ InsertBlanks( sBuff, sBuff.getLength(), rInfo.sStrArray[i][1] );
+ break;
+ case NF_SYMBOLTYPE_STRING:
+ case NF_SYMBOLTYPE_CURRENCY:
+ case NF_SYMBOLTYPE_DATESEP:
+ case NF_SYMBOLTYPE_TIMESEP:
+ case NF_SYMBOLTYPE_TIME100SECSEP:
+ sBuff.append(rInfo.sStrArray[i]);
+ break;
+ case NF_SYMBOLTYPE_DIGIT:
+ nLen = ( bInputLine && i > 0 &&
+ (rInfo.nTypeArray[i-1] == NF_SYMBOLTYPE_STRING ||
+ rInfo.nTypeArray[i-1] == NF_SYMBOLTYPE_TIME100SECSEP) ?
+ nCntPost : rInfo.sStrArray[i].getLength() );
+ for (sal_Int32 j = 0; j < nLen && nSecPos < nCntPost && nSecPos < sSecStr.getLength(); ++j)
+ {
+ sBuff.append(sSecStr[ nSecPos ]);
+ nSecPos++;
+ }
+ break;
+ case NF_KEY_AMPM: // AM/PM
+ if (cAmPm == 'a')
+ {
+ sBuff.append(rCal.getDisplayName( CalendarDisplayIndex::AM_PM,
+ AmPmValue::AM, 0 ));
+ }
+ else
+ {
+ sBuff.append(rCal.getDisplayName( CalendarDisplayIndex::AM_PM,
+ AmPmValue::PM, 0 ));
+ }
+ break;
+ case NF_KEY_AP: // A/P
+ if (cAmPm == 'a')
+ {
+ sBuff.append('a');
+ }
+ else
+ {
+ sBuff.append('p');
+ }
+ break;
+ case NF_KEY_MI: // M
+ sBuff.append(ImpIntToString( nIx, nMin ));
+ break;
+ case NF_KEY_MMI: // MM
+ sBuff.append(ImpIntToString( nIx, nMin, 2 ));
+ break;
+ case NF_KEY_H: // H
+ sBuff.append(ImpIntToString( nIx, nHour ));
+ break;
+ case NF_KEY_HH: // HH
+ sBuff.append(ImpIntToString( nIx, nHour, 2 ));
+ break;
+ case NF_KEY_S: // S
+ sBuff.append(ImpIntToString( nIx, nSec ));
+ break;
+ case NF_KEY_SS: // SS
+ sBuff.append(ImpIntToString( nIx, nSec, 2 ));
+ break;
+ case NF_KEY_M: // M
+ sBuff.append(rCal.getDisplayString(
+ CalendarDisplayCode::SHORT_MONTH, nNatNum ));
+ break;
+ case NF_KEY_MM: // MM
+ sBuff.append(rCal.getDisplayString(
+ CalendarDisplayCode::LONG_MONTH, nNatNum ));
+ break;
+ case NF_KEY_MMM: // MMM
+ sBuff.append(rCal.getDisplayString( ImpUseMonthCase( nUseMonthCase, NumFor[nIx],
+ static_cast<NfKeywordIndex>(rInfo.nTypeArray[i])),
+ nNatNum));
+ break;
+ case NF_KEY_MMMM: // MMMM
+ sBuff.append(rCal.getDisplayString( ImpUseMonthCase( nUseMonthCase, NumFor[nIx],
+ static_cast<NfKeywordIndex>(rInfo.nTypeArray[i])),
+ nNatNum));
+ break;
+ case NF_KEY_MMMMM: // MMMMM
+ sBuff.append(rCal.getDisplayString( ImpUseMonthCase( nUseMonthCase, NumFor[nIx],
+ static_cast<NfKeywordIndex>(rInfo.nTypeArray[i])),
+ nNatNum));
+ break;
+ case NF_KEY_Q: // Q
+ sBuff.append(rCal.getDisplayString( CalendarDisplayCode::SHORT_QUARTER, nNatNum ));
+ break;
+ case NF_KEY_QQ: // QQ
+ sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_QUARTER, nNatNum ));
+ break;
+ case NF_KEY_D: // D
+ sBuff.append(rCal.getDisplayString( CalendarDisplayCode::SHORT_DAY, nNatNum ));
+ break;
+ case NF_KEY_DD: // DD
+ sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_DAY, nNatNum ));
+ break;
+ case NF_KEY_DDD: // DDD
+ if ( bOtherCalendar )
+ {
+ SwitchToGregorianCalendar( aOrgCalendar, fOrgDateTime );
+ }
+ sBuff.append(rCal.getDisplayString( CalendarDisplayCode::SHORT_DAY_NAME, nNatNum ));
+ if ( bOtherCalendar )
+ {
+ SwitchToOtherCalendar( aOrgCalendar, fOrgDateTime );
+ }
+ break;
+ case NF_KEY_DDDD: // DDDD
+ if ( bOtherCalendar )
+ {
+ SwitchToGregorianCalendar( aOrgCalendar, fOrgDateTime );
+ }
+ sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_DAY_NAME, nNatNum ));
+ if ( bOtherCalendar )
+ {
+ SwitchToOtherCalendar( aOrgCalendar, fOrgDateTime );
+ }
+ break;
+ case NF_KEY_YY: // YY
+ if ( bOtherCalendar )
+ {
+ SwitchToGregorianCalendar( aOrgCalendar, fOrgDateTime );
+ }
+ // Prepend a minus sign if Gregorian BCE and era is not displayed.
+ if (lcl_isSignedYear( rCal, NumFor[nIx] ))
+ {
+ sBuff.append('-');
+ }
+ sBuff.append(rCal.getDisplayString( CalendarDisplayCode::SHORT_YEAR, nNatNum ));
+ if ( bOtherCalendar )
+ {
+ SwitchToOtherCalendar( aOrgCalendar, fOrgDateTime );
+ }
+ break;
+ case NF_KEY_YYYY: // YYYY
+ if ( bOtherCalendar )
+ {
+ SwitchToGregorianCalendar( aOrgCalendar, fOrgDateTime );
+ }
+ // Prepend a minus sign if Gregorian BCE and era is not displayed.
+ if (lcl_isSignedYear( rCal, NumFor[nIx] ))
+ {
+ sBuff.append('-');
+ }
+ aYear = rCal.getDisplayString( CalendarDisplayCode::LONG_YEAR, nNatNum );
+ if (aYear.getLength() < 4 && !lcl_hasEra(NumFor[nIx]))
+ {
+ using namespace comphelper::string;
+ // Ensure that year consists of at least 4 digits, so it
+ // can be distinguished from 2 digits display and edited
+ // without suddenly being hit by the 2-digit year magic.
+ OUStringBuffer aBuf;
+ padToLength(aBuf, 4 - aYear.getLength(), '0');
+ impTransliterate(aBuf, NumFor[nIx].GetNatNum());
+ aBuf.append(aYear);
+ sBuff.append(aBuf);
+ }
+ else
+ {
+ sBuff.append(aYear);
+ }
+ if ( bOtherCalendar )
+ {
+ SwitchToOtherCalendar( aOrgCalendar, fOrgDateTime );
+ }
+ break;
+ case NF_KEY_EC: // E
+ sBuff.append(rCal.getDisplayString( CalendarDisplayCode::SHORT_YEAR, nNatNum ));
+ break;
+ case NF_KEY_EEC: // EE
+ case NF_KEY_R: // R
+ sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_YEAR, nNatNum ));
+ break;
+ case NF_KEY_NN: // NN
+ case NF_KEY_AAA: // AAA
+ sBuff.append(rCal.getDisplayString( CalendarDisplayCode::SHORT_DAY_NAME, nNatNum ));
+ break;
+ case NF_KEY_NNN: // NNN
+ case NF_KEY_AAAA: // AAAA
+ sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_DAY_NAME, nNatNum ));
+ break;
+ case NF_KEY_NNNN: // NNNN
+ sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_DAY_NAME, nNatNum ));
+ sBuff.append(rLoc().getLongDateDayOfWeekSep());
+ break;
+ case NF_KEY_WW : // WW
+ sBuff.append(ImpIntToString( nIx, rCal.getValue( CalendarFieldIndex::WEEK_OF_YEAR )));
+ break;
+ case NF_KEY_G: // G
+ ImpAppendEraG( sBuff, rCal, nNatNum );
+ break;
+ case NF_KEY_GG: // GG
+ sBuff.append(rCal.getDisplayString( CalendarDisplayCode::SHORT_ERA, nNatNum ));
+ break;
+ case NF_KEY_GGG: // GGG
+ sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_ERA, nNatNum ));
+ break;
+ case NF_KEY_RR: // RR => GGGEE
+ sBuff.append(rCal.getDisplayString( CalendarDisplayCode::LONG_YEAR_AND_ERA, nNatNum ));
+ break;
+ }
+ }
+ if ( aOrgCalendar.getLength() )
+ {
+ rCal.loadCalendar( aOrgCalendar, rLoc().getLanguageTag().getLocale() ); // restore calendar
+ }
+ return bRes;
+}
+
+bool SvNumberformat::ImpGetLogicalOutput(double fNumber,
+ sal_uInt16 nIx,
+ OUStringBuffer& sStr)
+{
+ bool bRes = false;
+ const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info();
+ const sal_uInt16 nCnt = NumFor[nIx].GetCount();
+ for (sal_uInt16 j = 0; j < nCnt; ++j)
+ {
+ switch (rInfo.nTypeArray[j])
+ {
+ case NF_KEY_BOOLEAN:
+ sStr.append( fNumber ? rScan.GetTrueString() : rScan.GetFalseString());
+ break;
+ case NF_SYMBOLTYPE_STRING:
+ sStr.append( rInfo.sStrArray[j]);
+ break;
+ }
+ }
+ impTransliterate(sStr, NumFor[nIx].GetNatNum());
+ return bRes;
+}
+
+bool SvNumberformat::ImpGetNumberOutput(double fNumber,
+ sal_uInt16 nIx,
+ OUStringBuffer& sStr)
+{
+ bool bRes = false;
+ bool bSign;
+ if (fNumber < 0.0)
+ {
+ bSign = (nIx == 0); // Not in the ones at the back;
+ fNumber = -fNumber;
+ }
+ else
+ {
+ bSign = false;
+ if ( std::signbit( fNumber ) )
+ {
+ fNumber = -fNumber; // yes, -0.0 is possible, eliminate '-'
+ }
+ }
+ const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info();
+ if (rInfo.eScannedType == SvNumFormatType::PERCENT)
+ {
+ if (fNumber < D_MAX_D_BY_100)
+ {
+ fNumber *= 100.0;
+ }
+ else
+ {
+ sStr = ImpSvNumberformatScan::sErrStr;
+ return false;
+ }
+ }
+ sal_uInt16 i, j;
+ sal_Int32 nDecPos = -1;
+ bool bInteger = false;
+ if ( rInfo.nThousand != FLAG_STANDARD_IN_FORMAT )
+ {
+ // Special formatting only if no GENERAL keyword in format code
+ const sal_uInt16 nThousand = rInfo.nThousand;
+ tools::Long nPrecExp;
+ for (i = 0; i < nThousand; i++)
+ {
+ if (fNumber > D_MIN_M_BY_1000)
+ {
+ fNumber /= 1000.0;
+ }
+ else
+ {
+ fNumber = 0.0;
+ }
+ }
+ if (fNumber > 0.0)
+ {
+ nPrecExp = GetPrecExp( fNumber );
+ }
+ else
+ {
+ nPrecExp = 0;
+ }
+ if (rInfo.nCntPost) // Decimal places
+ {
+ if ((rInfo.nCntPost + nPrecExp) > 15 && nPrecExp < 15)
+ {
+ sStr = ::rtl::math::doubleToUString( fNumber, rtl_math_StringFormat_F, 15-nPrecExp, '.');
+ for (tools::Long l = 15-nPrecExp; l < static_cast<tools::Long>(rInfo.nCntPost); l++)
+ {
+ sStr.append('0');
+ }
+ }
+ else
+ {
+ sStr = ::rtl::math::doubleToUString( fNumber, rtl_math_StringFormat_F, rInfo.nCntPost, '.' );
+ }
+ sStr.stripStart('0'); // Strip leading zeros
+ }
+ else if (fNumber == 0.0) // Null
+ {
+ // Nothing to be done here, keep empty string sStr,
+ // ImpNumberFillWithThousands does the rest
+ }
+ else // Integer
+ {
+ sStr = ::rtl::math::doubleToUString( fNumber, rtl_math_StringFormat_F, 0, '.');
+ sStr.stripStart('0'); // Strip leading zeros
+ }
+ nDecPos = sStr.indexOf('.' );
+ if ( nDecPos >= 0)
+ {
+ const sal_Unicode* p = sStr.getStr() + nDecPos;
+ while ( *++p == '0' )
+ ;
+ if ( !*p )
+ {
+ bInteger = true;
+ }
+ sStr.remove( nDecPos, 1 ); // Remove .
+ }
+ if (bSign && (sStr.isEmpty() || checkForAll0s(sStr))) // Only 00000
+ {
+ bSign = false; // Not -0.00
+ }
+ } // End of != FLAG_STANDARD_IN_FORMAT
+
+ // Edit backwards:
+ j = NumFor[nIx].GetCount()-1; // Last symbol
+ // Decimal places:
+ bRes |= ImpDecimalFill( sStr, fNumber, nDecPos, j, nIx, bInteger );
+ if (bSign)
+ {
+ sStr.insert(0, '-');
+ }
+ impTransliterate(sStr, NumFor[nIx].GetNatNum());
+ return bRes;
+}
+
+bool SvNumberformat::ImpDecimalFill( OUStringBuffer& sStr, // number string
+ double& rNumber, // number
+ sal_Int32 nDecPos, // decimals start
+ sal_uInt16 j, // symbol index within format code
+ sal_uInt16 nIx, // subformat index
+ bool bInteger) // is integer
+{
+ bool bRes = false;
+ bool bFilled = false; // Was filled?
+ const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info();
+ sal_Int32 k = sStr.getLength(); // After last figure
+ // Decimal places:
+ if (rInfo.nCntPost > 0)
+ {
+ bool bTrailing = true; // Trailing zeros?
+ short nType;
+ while (j > 0 && // Backwards
+ (nType = rInfo.nTypeArray[j]) != NF_SYMBOLTYPE_DECSEP)
+ {
+ switch ( nType )
+ {
+ case NF_SYMBOLTYPE_STAR:
+ if( bStarFlag )
+ {
+ bRes = lcl_insertStarFillChar( sStr, k, rInfo.sStrArray[j]);
+ }
+ break;
+ case NF_SYMBOLTYPE_BLANK:
+ if (rInfo.sStrArray[j].getLength() >= 2)
+ /*k = */ InsertBlanks(sStr, k, rInfo.sStrArray[j][1] );
+ break;
+ case NF_SYMBOLTYPE_STRING:
+ case NF_SYMBOLTYPE_CURRENCY:
+ case NF_SYMBOLTYPE_PERCENT:
+ sStr.insert(k, rInfo.sStrArray[j]);
+ break;
+ case NF_SYMBOLTYPE_THSEP:
+ if (rInfo.nThousand == 0)
+ {
+ sStr.insert(k, rInfo.sStrArray[j]);
+ }
+ break;
+ case NF_SYMBOLTYPE_DIGIT:
+ {
+ const OUString& rStr = rInfo.sStrArray[j];
+ const sal_Unicode* p1 = rStr.getStr();
+ const sal_Unicode* p = p1 + rStr.getLength();
+ // In case the number of decimals passed are less than the
+ // "digits" given, append trailing '0' characters, which here
+ // means insert them because literal strings may have been
+ // appended already. If they weren't to be '0' characters
+ // they'll be changed below, as if decimals with trailing zeros
+ // were passed.
+ if (nDecPos >= 0 && nDecPos <= k)
+ {
+ sal_Int32 nAppend = rStr.getLength() - (k - nDecPos);
+ while (nAppend-- > 0)
+ {
+ sStr.insert( k++, '0');
+ }
+ }
+ while (k && p1 < p--)
+ {
+ const sal_Unicode c = *p;
+ k--;
+ if ( sStr[k] != '0' )
+ {
+ bTrailing = false;
+ bFilled = true;
+ }
+ if (bTrailing)
+ {
+ if ( c == '0' )
+ {
+ bFilled = true;
+ }
+ else if ( c == '-' )
+ {
+ if ( bInteger )
+ {
+ sStr[ k ] = '-';
+ }
+ bFilled = true;
+ }
+ else if ( c == '?' )
+ {
+ sStr[ k ] = ' ';
+ bFilled = true;
+ }
+ else if ( !bFilled ) // #
+ {
+ sStr.remove(k,1);
+ }
+ }
+ } // of for
+ break;
+ } // of case digi
+ case NF_KEY_CCC: // CCC currency
+ sStr.insert(k, rScan.GetCurAbbrev());
+ break;
+ case NF_KEY_GENERAL: // Standard in the String
+ {
+ OUStringBuffer sNum;
+ ImpGetOutputStandard(rNumber, sNum);
+ sNum.stripStart('-');
+ sStr.insert(k, sNum);
+ break;
+ }
+ default:
+ break;
+ } // of switch
+ j--;
+ } // of while
+ } // of decimal places
+
+ bRes |= ImpNumberFillWithThousands(sStr, rNumber, k, j, nIx, // Fill with . if needed
+ rInfo.nCntPre, bFilled );
+
+ return bRes;
+}
+
+bool SvNumberformat::ImpNumberFillWithThousands( OUStringBuffer& sBuff, // number string
+ double& rNumber, // number
+ sal_Int32 k, // position within string
+ sal_uInt16 j, // symbol index within format code
+ sal_uInt16 nIx, // subformat index
+ sal_Int32 nDigCnt, // count of integer digits in format
+ bool bAddDecSep) // add decimal separator if necessary
+{
+ bool bRes = false;
+ sal_Int32 nLeadingStringChars = 0; // inserted StringChars before number
+ sal_Int32 nDigitCount = 0; // count of integer digits from the right
+ bool bStop = false;
+ const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info();
+ // no normal thousands separators if number divided by thousands
+ bool bDoThousands = (rInfo.nThousand == 0);
+ utl::DigitGroupingIterator aGrouping( GetFormatter().GetLocaleData()->getDigitGrouping());
+
+ while (!bStop) // backwards
+ {
+ if (j == 0)
+ {
+ bStop = true;
+ }
+ switch (rInfo.nTypeArray[j])
+ {
+ case NF_SYMBOLTYPE_DECSEP:
+ aGrouping.reset();
+ [[fallthrough]];
+ case NF_SYMBOLTYPE_STRING:
+ case NF_SYMBOLTYPE_CURRENCY:
+ case NF_SYMBOLTYPE_PERCENT:
+ if ( rInfo.nTypeArray[j] != NF_SYMBOLTYPE_DECSEP || bAddDecSep )
+ sBuff.insert(k, rInfo.sStrArray[j]);
+ if ( k == 0 )
+ {
+ nLeadingStringChars = nLeadingStringChars + rInfo.sStrArray[j].getLength();
+ }
+ break;
+ case NF_SYMBOLTYPE_STAR:
+ if( bStarFlag )
+ {
+ bRes = lcl_insertStarFillChar( sBuff, k, rInfo.sStrArray[j]);
+ }
+ break;
+ case NF_SYMBOLTYPE_BLANK:
+ if (rInfo.sStrArray[j].getLength() >= 2)
+ /*k = */ InsertBlanks(sBuff, k, rInfo.sStrArray[j][1] );
+ break;
+ case NF_SYMBOLTYPE_THSEP:
+ // #i7284# #102685# Insert separator also if number is divided
+ // by thousands and the separator is specified somewhere in
+ // between and not only at the end.
+ // #i12596# But do not insert if it's a parenthesized negative
+ // format like (#,)
+ // In fact, do not insert if divided and regex [0#,],[^0#] and
+ // no other digit symbol follows (which was already detected
+ // during scan of format code, otherwise there would be no
+ // division), else do insert. Same in ImpNumberFill() below.
+ if ( !bDoThousands && j < NumFor[nIx].GetCount()-1 )
+ {
+ bDoThousands = ((j == 0) ||
+ (rInfo.nTypeArray[j-1] != NF_SYMBOLTYPE_DIGIT &&
+ rInfo.nTypeArray[j-1] != NF_SYMBOLTYPE_THSEP) ||
+ (rInfo.nTypeArray[j+1] == NF_SYMBOLTYPE_DIGIT));
+ }
+ if ( bDoThousands )
+ {
+ if (k > 0)
+ {
+ sBuff.insert(k, rInfo.sStrArray[j]);
+ }
+ else if (nDigitCount < nDigCnt)
+ {
+ // Leading '#' displays nothing (e.g. no leading
+ // separator for numbers <1000 with #,##0 format).
+ // Leading '?' displays blank.
+ // Everything else, including nothing, displays the
+ // separator.
+ sal_Unicode cLeader = 0;
+ if (j > 0 && rInfo.nTypeArray[j-1] == NF_SYMBOLTYPE_DIGIT)
+ {
+ const OUString& rStr = rInfo.sStrArray[j-1];
+ sal_Int32 nLen = rStr.getLength();
+ if (nLen)
+ {
+ cLeader = rStr[ nLen - 1 ];
+ }
+ }
+ switch (cLeader)
+ {
+ case '#':
+ ; // nothing
+ break;
+ case '?':
+ // replace thousand separator with blank
+ sBuff.insert(k, ' ');
+ break;
+ default:
+ sBuff.insert(k, rInfo.sStrArray[j]);
+ }
+ }
+ aGrouping.advance();
+ }
+ break;
+ case NF_SYMBOLTYPE_DIGIT:
+ {
+ const OUString& rStr = rInfo.sStrArray[j];
+ const sal_Unicode* p1 = rStr.getStr();
+ const sal_Unicode* p = p1 + rStr.getLength();
+ while ( p1 < p-- )
+ {
+ nDigitCount++;
+ if (k > 0)
+ {
+ k--;
+ }
+ else
+ {
+ switch (*p)
+ {
+ case '0':
+ sBuff.insert(0, '0');
+ break;
+ case '?':
+ sBuff.insert(0, ' ');
+ break;
+ }
+ }
+ if (nDigitCount == nDigCnt && k > 0)
+ {
+ // more digits than specified
+ ImpDigitFill(sBuff, 0, k, nIx, nDigitCount, aGrouping);
+ }
+ }
+ break;
+ }
+ case NF_KEY_CCC: // CCC currency
+ sBuff.insert(k, rScan.GetCurAbbrev());
+ break;
+ case NF_KEY_GENERAL: // "General" in string
+ {
+ OUStringBuffer sNum;
+ ImpGetOutputStandard(rNumber, sNum);
+ sNum.stripStart('-');
+ sBuff.insert(k, sNum);
+ break;
+ }
+ default:
+ break;
+ } // switch
+ j--; // next format code string
+ } // while
+
+ k = k + nLeadingStringChars; // MSC converts += to int and then warns, so ...
+ if (k > nLeadingStringChars)
+ {
+ ImpDigitFill(sBuff, nLeadingStringChars, k, nIx, nDigitCount, aGrouping);
+ }
+ return bRes;
+}
+
+void SvNumberformat::ImpDigitFill(OUStringBuffer& sStr, // number string
+ sal_Int32 nStart, // start of digits
+ sal_Int32 & k, // position within string
+ sal_uInt16 nIx, // subformat index
+ sal_Int32 & nDigitCount, // count of integer digits from the right so far
+ utl::DigitGroupingIterator & rGrouping ) // current grouping
+{
+ if (NumFor[nIx].Info().bThousand) // Only if grouping fill in separators
+ {
+ const OUString& rThousandSep = GetFormatter().GetNumThousandSep();
+ while (k > nStart)
+ {
+ if (nDigitCount == rGrouping.getPos())
+ {
+ sStr.insert( k, rThousandSep );
+ rGrouping.advance();
+ }
+ nDigitCount++;
+ k--;
+ }
+ }
+ else // simply skip
+ {
+ k = nStart;
+ }
+}
+
+bool SvNumberformat::ImpNumberFill( OUStringBuffer& sBuff, // number string
+ double& rNumber, // number for "General" format
+ sal_Int32& k, // position within string
+ sal_uInt16& j, // symbol index within format code
+ sal_uInt16 nIx, // subformat index
+ short eSymbolType, // type of stop condition
+ bool bInsertRightBlank)// insert blank on right for denominator (default = false)
+{
+ bool bRes = false;
+ bool bStop = false;
+ const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info();
+ // no normal thousands separators if number divided by thousands
+ bool bDoThousands = (rInfo.nThousand == 0);
+ bool bFoundNumber = false;
+ short nType;
+
+ k = sBuff.getLength(); // behind last digit
+
+ while (!bStop && (nType = rInfo.nTypeArray[j]) != eSymbolType ) // Backwards
+ {
+ switch ( nType )
+ {
+ case NF_SYMBOLTYPE_STAR:
+ if( bStarFlag )
+ {
+ if ( bFoundNumber && eSymbolType != NF_SYMBOLTYPE_EXP )
+ k = 0; // tdf#100842 jump to beginning of number before inserting something else
+ bRes = lcl_insertStarFillChar( sBuff, k, rInfo.sStrArray[j]);
+ }
+ break;
+ case NF_SYMBOLTYPE_BLANK:
+ if (rInfo.sStrArray[j].getLength() >= 2)
+ {
+ if ( bFoundNumber && eSymbolType != NF_SYMBOLTYPE_EXP )
+ k = 0; // tdf#100842 jump to beginning of number before inserting something else
+ k = InsertBlanks(sBuff, k, rInfo.sStrArray[j][1] );
+ }
+ break;
+ case NF_SYMBOLTYPE_THSEP:
+ // Same as in ImpNumberFillWithThousands() above, do not insert
+ // if divided and regex [0#,],[^0#] and no other digit symbol
+ // follows (which was already detected during scan of format
+ // code, otherwise there would be no division), else do insert.
+ if ( !bDoThousands && j < NumFor[nIx].GetCount()-1 )
+ {
+ bDoThousands = ((j == 0) ||
+ (rInfo.nTypeArray[j-1] != NF_SYMBOLTYPE_DIGIT &&
+ rInfo.nTypeArray[j-1] != NF_SYMBOLTYPE_THSEP) ||
+ (rInfo.nTypeArray[j+1] == NF_SYMBOLTYPE_DIGIT));
+ }
+ if ( bDoThousands && k > 0 )
+ {
+ sBuff.insert(k, rInfo.sStrArray[j]);
+ }
+ break;
+ case NF_SYMBOLTYPE_DIGIT:
+ {
+ bFoundNumber = true;
+ sal_uInt16 nPosInsertBlank = bInsertRightBlank ? k : 0; // left alignment of denominator
+ const OUString& rStr = rInfo.sStrArray[j];
+ const sal_Unicode* p1 = rStr.getStr();
+ const sal_Unicode* p = p1 + rStr.getLength();
+ while ( p1 < p-- )
+ {
+ if (k > 0)
+ {
+ k--;
+ }
+ else
+ {
+ switch (*p)
+ {
+ case '0':
+ sBuff.insert(0, '0');
+ break;
+ case '?':
+ sBuff.insert(nPosInsertBlank, ' ');
+ break;
+ }
+ }
+ }
+ }
+ break;
+ case NF_KEY_CCC: // CCC currency
+ sBuff.insert(k, rScan.GetCurAbbrev());
+ break;
+ case NF_KEY_GENERAL: // Standard in the String
+ {
+ OUStringBuffer sNum;
+ bFoundNumber = true;
+ ImpGetOutputStandard(rNumber, sNum);
+ sNum.stripStart('-');
+ sBuff.insert(k, sNum);
+ }
+ break;
+ case NF_SYMBOLTYPE_FRAC_FDIV: // Do Nothing
+ if (k > 0)
+ {
+ k--;
+ }
+ break;
+
+ default:
+ if ( bFoundNumber && eSymbolType != NF_SYMBOLTYPE_EXP )
+ k = 0; // tdf#100842 jump to beginning of number before inserting something else
+ sBuff.insert(k, rInfo.sStrArray[j]);
+ break;
+ } // of switch
+ if ( j )
+ j--; // Next String
+ else
+ bStop = true;
+ } // of while
+ return bRes;
+}
+
+void SvNumberformat::GetFormatSpecialInfo(bool& bThousand,
+ bool& IsRed,
+ sal_uInt16& nPrecision,
+ sal_uInt16& nLeadingCnt) const
+{
+ // as before: take info from nNumFor=0 for whole format (for dialog etc.)
+
+ SvNumFormatType nDummyType;
+ GetNumForInfo( 0, nDummyType, bThousand, nPrecision, nLeadingCnt );
+
+ // "negative in red" is only useful for the whole format
+
+ const Color* pColor = NumFor[1].GetColor();
+ IsRed = fLimit1 == 0.0 && fLimit2 == 0.0 && pColor
+ && (*pColor == ImpSvNumberformatScan::GetRedColor());
+}
+
+void SvNumberformat::GetNumForInfo( sal_uInt16 nNumFor, SvNumFormatType& rScannedType,
+ bool& bThousand, sal_uInt16& nPrecision, sal_uInt16& nLeadingCnt ) const
+{
+ // take info from a specified sub-format (for XML export)
+
+ if ( nNumFor > 3 )
+ {
+ return; // invalid
+ }
+
+ const ImpSvNumberformatInfo& rInfo = NumFor[nNumFor].Info();
+ rScannedType = rInfo.eScannedType;
+ bThousand = rInfo.bThousand;
+ nPrecision = (rInfo.eScannedType == SvNumFormatType::FRACTION)
+ ? rInfo.nCntExp // number of denominator digits for fraction
+ : rInfo.nCntPost;
+ sal_Int32 nPosHash = 1;
+ if ( rInfo.eScannedType == SvNumFormatType::FRACTION &&
+ ( (nPosHash += GetDenominatorString(nNumFor).indexOf('#')) > 0 ) )
+ nPrecision -= nPosHash;
+ if (bStandard && rInfo.eScannedType == SvNumFormatType::NUMBER)
+ {
+ // StandardFormat
+ nLeadingCnt = 1;
+ }
+ else
+ {
+ nLeadingCnt = 0;
+ bool bStop = false;
+ sal_uInt16 i = 0;
+ const sal_uInt16 nCnt = NumFor[nNumFor].GetCount();
+ while (!bStop && i < nCnt)
+ {
+ short nType = rInfo.nTypeArray[i];
+ if ( nType == NF_SYMBOLTYPE_DIGIT)
+ {
+ const sal_Unicode* p = rInfo.sStrArray[i].getStr();
+ while ( *p == '#' )
+ {
+ p++;
+ }
+ while ( *p == '0' || *p == '?' )
+ {
+ nLeadingCnt++;
+ p++;
+ }
+ }
+ else if (nType == NF_SYMBOLTYPE_DECSEP
+ || nType == NF_SYMBOLTYPE_EXP
+ || nType == NF_SYMBOLTYPE_FRACBLANK) // Fraction: stop after integer part,
+ { // do not count '0' of fraction
+ bStop = true;
+ }
+ i++;
+ }
+ }
+}
+
+const OUString* SvNumberformat::GetNumForString( sal_uInt16 nNumFor, sal_uInt16 nPos,
+ bool bString /* = false */ ) const
+{
+ if ( nNumFor > 3 )
+ {
+ return nullptr;
+ }
+ sal_uInt16 nCnt = NumFor[nNumFor].GetCount();
+ if ( !nCnt )
+ {
+ return nullptr;
+ }
+ if ( nPos == 0xFFFF )
+ {
+ nPos = nCnt - 1;
+ if ( bString )
+ { // Backwards
+ short const * pType = NumFor[nNumFor].Info().nTypeArray.data() + nPos;
+ while ( nPos > 0 && (*pType != NF_SYMBOLTYPE_STRING) &&
+ (*pType != NF_SYMBOLTYPE_CURRENCY) )
+ {
+ pType--;
+ nPos--;
+ }
+ if ( (*pType != NF_SYMBOLTYPE_STRING) && (*pType != NF_SYMBOLTYPE_CURRENCY) )
+ {
+ return nullptr;
+ }
+ }
+ }
+ else if ( nPos > nCnt - 1 )
+ {
+ return nullptr;
+ }
+ else if ( bString )
+ {
+ // forward
+ short const * pType = NumFor[nNumFor].Info().nTypeArray.data() + nPos;
+ while ( nPos < nCnt && (*pType != NF_SYMBOLTYPE_STRING) &&
+ (*pType != NF_SYMBOLTYPE_CURRENCY) )
+ {
+ pType++;
+ nPos++;
+ }
+ if ( nPos >= nCnt || ((*pType != NF_SYMBOLTYPE_STRING) &&
+ (*pType != NF_SYMBOLTYPE_CURRENCY)) )
+ {
+ return nullptr;
+ }
+ }
+ return &NumFor[nNumFor].Info().sStrArray[nPos];
+}
+
+short SvNumberformat::GetNumForType( sal_uInt16 nNumFor, sal_uInt16 nPos ) const
+{
+ if ( nNumFor > 3 )
+ {
+ return 0;
+ }
+ sal_uInt16 nCnt = NumFor[nNumFor].GetCount();
+ if ( !nCnt )
+ {
+ return 0;
+ }
+ if ( nPos == 0xFFFF )
+ {
+ nPos = nCnt - 1;
+ }
+ else if ( nPos > nCnt - 1 )
+ {
+ return 0;
+ }
+ return NumFor[nNumFor].Info().nTypeArray[nPos];
+}
+
+bool SvNumberformat::IsNegativeWithoutSign() const
+{
+ if ( IsSecondSubformatRealNegative() )
+ {
+ const OUString* pStr = GetNumForString( 1, 0, true );
+ if ( pStr )
+ {
+ return !HasStringNegativeSign( *pStr );
+ }
+ }
+ return false;
+}
+
+bool SvNumberformat::IsNegativeInBracket() const
+{
+ sal_uInt16 nCnt = NumFor[1].GetCount();
+ if (!nCnt)
+ {
+ return false;
+ }
+ auto& tmp = NumFor[1].Info().sStrArray;
+ return tmp[0] == "(" && tmp[nCnt-1] == ")";
+}
+
+bool SvNumberformat::HasPositiveBracketPlaceholder() const
+{
+ sal_uInt16 nCnt = NumFor[0].GetCount();
+ return NumFor[0].Info().sStrArray[nCnt-1] == "_)";
+}
+
+DateOrder SvNumberformat::GetDateOrder() const
+{
+ if ( eType & SvNumFormatType::DATE )
+ {
+ auto& rTypeArray = NumFor[0].Info().nTypeArray;
+ sal_uInt16 nCnt = NumFor[0].GetCount();
+ for ( sal_uInt16 j=0; j<nCnt; j++ )
+ {
+ switch ( rTypeArray[j] )
+ {
+ case NF_KEY_D :
+ case NF_KEY_DD :
+ return DateOrder::DMY;
+ case NF_KEY_M :
+ case NF_KEY_MM :
+ case NF_KEY_MMM :
+ case NF_KEY_MMMM :
+ case NF_KEY_MMMMM :
+ return DateOrder::MDY;
+ case NF_KEY_YY :
+ case NF_KEY_YYYY :
+ case NF_KEY_EC :
+ case NF_KEY_EEC :
+ case NF_KEY_R :
+ case NF_KEY_RR :
+ return DateOrder::YMD;
+ }
+ }
+ }
+ else
+ {
+ SAL_WARN( "svl.numbers", "SvNumberformat::GetDateOrder: no date" );
+ }
+ return rLoc().getDateOrder();
+}
+
+sal_uInt32 SvNumberformat::GetExactDateOrder() const
+{
+ sal_uInt32 nRet = 0;
+ if ( !(eType & SvNumFormatType::DATE) )
+ {
+ SAL_WARN( "svl.numbers", "SvNumberformat::GetExactDateOrder: no date" );
+ return nRet;
+ }
+ auto& rTypeArray = NumFor[0].Info().nTypeArray;
+ sal_uInt16 nCnt = NumFor[0].GetCount();
+ int nShift = 0;
+ for ( sal_uInt16 j=0; j<nCnt && nShift < 3; j++ )
+ {
+ switch ( rTypeArray[j] )
+ {
+ case NF_KEY_D :
+ case NF_KEY_DD :
+ nRet = (nRet << 8) | 'D';
+ ++nShift;
+ break;
+ case NF_KEY_M :
+ case NF_KEY_MM :
+ case NF_KEY_MMM :
+ case NF_KEY_MMMM :
+ case NF_KEY_MMMMM :
+ nRet = (nRet << 8) | 'M';
+ ++nShift;
+ break;
+ case NF_KEY_YY :
+ case NF_KEY_YYYY :
+ case NF_KEY_EC :
+ case NF_KEY_EEC :
+ case NF_KEY_R :
+ case NF_KEY_RR :
+ nRet = (nRet << 8) | 'Y';
+ ++nShift;
+ break;
+ }
+ }
+ return nRet;
+}
+
+void SvNumberformat::GetConditions( SvNumberformatLimitOps& rOper1, double& rVal1,
+ SvNumberformatLimitOps& rOper2, double& rVal2 ) const
+{
+ rOper1 = eOp1;
+ rOper2 = eOp2;
+ rVal1 = fLimit1;
+ rVal2 = fLimit2;
+}
+
+const Color* SvNumberformat::GetColor( sal_uInt16 nNumFor ) const
+{
+ if ( nNumFor > 3 )
+ {
+ return nullptr;
+ }
+ return NumFor[nNumFor].GetColor();
+}
+
+static void lcl_SvNumberformat_AddLimitStringImpl( OUString& rStr,
+ SvNumberformatLimitOps eOp,
+ double fLimit, std::u16string_view rDecSep )
+{
+ if ( eOp == NUMBERFORMAT_OP_NO )
+ return;
+
+ switch ( eOp )
+ {
+ case NUMBERFORMAT_OP_EQ :
+ rStr = "[=";
+ break;
+ case NUMBERFORMAT_OP_NE :
+ rStr = "[<>";
+ break;
+ case NUMBERFORMAT_OP_LT :
+ rStr = "[<";
+ break;
+ case NUMBERFORMAT_OP_LE :
+ rStr = "[<=";
+ break;
+ case NUMBERFORMAT_OP_GT :
+ rStr = "[>";
+ break;
+ case NUMBERFORMAT_OP_GE :
+ rStr = "[>=";
+ break;
+ default:
+ SAL_WARN( "svl.numbers", "unsupported number format" );
+ break;
+ }
+ rStr += ::rtl::math::doubleToUString( fLimit,
+ rtl_math_StringFormat_Automatic, rtl_math_DecimalPlaces_Max,
+ rDecSep[0], true);
+ rStr += "]";
+}
+
+static void lcl_insertLCID( OUStringBuffer& rFormatStr, sal_uInt32 nLCID, sal_Int32 nPosInsertLCID, bool bDBNumInserted )
+{
+ if ( nLCID == 0 )
+ return;
+ if (nPosInsertLCID == rFormatStr.getLength() && !bDBNumInserted)
+ // No format code, no locale.
+ return;
+
+ auto aLCIDString = OUString::number( nLCID , 16 ).toAsciiUpperCase();
+ // Search for only last DBNum which is the last element before insertion position
+ if ( bDBNumInserted && nPosInsertLCID >= 8
+ && aLCIDString.length > 4
+ && OUString::unacquired(rFormatStr).match( "[DBNum", nPosInsertLCID-8) )
+ { // remove DBNumX code if long LCID
+ nPosInsertLCID -= 8;
+ rFormatStr.remove( nPosInsertLCID, 8 );
+ }
+ rFormatStr.insert( nPosInsertLCID, "[$-" + aLCIDString + "]" );
+}
+
+/** Increment nAlphabetID for CJK numerals
+ * +1 for financial numerals [NatNum2]
+ * +2 for Arabic fullwidth numerals [NatNum3]
+ * */
+static void lcl_incrementAlphabetWithNatNum ( sal_uInt32& nAlphabetID, sal_uInt32 nNatNum )
+{
+ if ( nNatNum == 2) // financial
+ nAlphabetID += 1;
+ else if ( nNatNum == 3)
+ nAlphabetID += 2;
+ nAlphabetID = nAlphabetID << 24;
+}
+
+OUString SvNumberformat::GetMappedFormatstring( const NfKeywordTable& rKeywords,
+ const LocaleDataWrapper& rLocWrp,
+ LanguageType nOriginalLang /* =LANGUAGE_DONTKNOW */,
+ bool bSystemLanguage /* =false */ ) const
+{
+ OUStringBuffer aStr;
+ if (maLocale.meSubstitute != LocaleType::Substitute::NONE)
+ {
+ // XXX: theoretically this could clash with the first subformat's
+ // lcl_insertLCID() below, in practice as long as it is used for system
+ // time and date modifiers it shouldn't (i.e. there is no calendar or
+ // numeral specified as well).
+ aStr.append("[$-" + maLocale.generateCode() + "]");
+ }
+ bool bDefault[4];
+ // 1 subformat matches all if no condition specified,
+ bDefault[0] = ( NumFor[1].GetCount() == 0 && eOp1 == NUMBERFORMAT_OP_NO );
+ // with 2 subformats [>=0];[<0] is implied if no condition specified
+ bDefault[1] = ( !bDefault[0] && NumFor[2].GetCount() == 0 &&
+ eOp1 == NUMBERFORMAT_OP_GE && fLimit1 == 0.0 &&
+ eOp2 == NUMBERFORMAT_OP_NO && fLimit2 == 0.0 );
+ // with 3 or more subformats [>0];[<0];[=0] is implied if no condition specified,
+ // note that subformats may be empty (;;;) and NumFor[2].GetCount()>0 is not checked.
+ bDefault[2] = ( !bDefault[0] && !bDefault[1] &&
+ eOp1 == NUMBERFORMAT_OP_GT && fLimit1 == 0.0 &&
+ eOp2 == NUMBERFORMAT_OP_LT && fLimit2 == 0.0 );
+ bool bDefaults = bDefault[0] || bDefault[1] || bDefault[2];
+ // from now on bDefault[] values are used to append empty subformats at the end
+ bDefault[3] = false;
+ if ( !bDefaults )
+ {
+ // conditions specified
+ if ( eOp1 != NUMBERFORMAT_OP_NO && eOp2 == NUMBERFORMAT_OP_NO )
+ {
+ bDefault[0] = bDefault[1] = true; // [];x
+ }
+ else if ( eOp1 != NUMBERFORMAT_OP_NO && eOp2 != NUMBERFORMAT_OP_NO &&
+ NumFor[2].GetCount() == 0 )
+ {
+ bDefault[0] = bDefault[1] = bDefault[2] = bDefault[3] = true; // [];[];;
+ }
+ // nothing to do if conditions specified for every subformat
+ }
+ else if ( bDefault[0] )
+ {
+ bDefault[0] = false; // a single unconditional subformat is never delimited
+ }
+ else
+ {
+ if ( bDefault[2] && NumFor[2].GetCount() == 0 && NumFor[1].GetCount() > 0 )
+ {
+ bDefault[3] = true; // special cases x;x;; and ;x;;
+ }
+ for ( int i=0; i<3 && !bDefault[i]; ++i )
+ {
+ bDefault[i] = true;
+ }
+ }
+ int nSem = 0; // needed ';' delimiters
+ int nSub = 0; // subformats delimited so far
+ for ( int n=0; n<4; n++ )
+ {
+ if ( n > 0 && NumFor[n].Info().eScannedType != SvNumFormatType::UNDEFINED )
+ {
+ nSem++;
+ }
+ OUString aPrefix;
+
+ if ( !bDefaults )
+ {
+ switch ( n )
+ {
+ case 0 :
+ lcl_SvNumberformat_AddLimitStringImpl( aPrefix, eOp1,
+ fLimit1, rLocWrp.getNumDecimalSep() );
+ break;
+ case 1 :
+ lcl_SvNumberformat_AddLimitStringImpl( aPrefix, eOp2,
+ fLimit2, rLocWrp.getNumDecimalSep() );
+ break;
+ }
+ }
+
+ const OUString& rColorName = NumFor[n].GetColorName();
+ if ( !rColorName.isEmpty() )
+ {
+ const NfKeywordTable & rKey = rScan.GetKeywords();
+ for ( int j = NF_KEY_FIRSTCOLOR; j <= NF_KEY_LASTCOLOR; j++ )
+ {
+ if ( rKey[j] == rColorName )
+ {
+ aPrefix += "[" + rKeywords[j] + "]";
+ break; // for
+ }
+ }
+ }
+
+ SvNumberNatNum aNatNum = NumFor[n].GetNatNum();
+ bool bDBNumInserted = false;
+ if (aNatNum.IsComplete() && (aNatNum.GetDBNum() > 0 || nOriginalLang != LANGUAGE_DONTKNOW))
+ { // GetFormatStringForExcel() may have changed language to en_US
+ if (aNatNum.GetLang() == LANGUAGE_ENGLISH_US && nOriginalLang != LANGUAGE_DONTKNOW)
+ aNatNum.SetLang( nOriginalLang );
+ if ( aNatNum.GetDBNum() > 0 )
+ {
+ aPrefix += "[DBNum" + OUString::number( aNatNum.GetDBNum() ) + "]";
+ bDBNumInserted = true;
+ }
+ }
+
+ sal_uInt16 nCnt = NumFor[n].GetCount();
+ if ( nSem && (nCnt || !aPrefix.isEmpty()) )
+ {
+ for ( ; nSem; --nSem )
+ {
+ aStr.append( ';' );
+ }
+ for ( ; nSub <= n; ++nSub )
+ {
+ bDefault[nSub] = false;
+ }
+ }
+
+ if ( !aPrefix.isEmpty() )
+ {
+ aStr.append( aPrefix );
+ }
+ sal_Int32 nPosHaveLCID = -1;
+ sal_Int32 nPosInsertLCID = aStr.getLength();
+ sal_uInt32 nCalendarID = 0x0000000; // Excel ID of calendar used in sub-format see tdf#36038
+ constexpr sal_uInt32 kCalGengou = 0x0030000;
+ if ( nCnt )
+ {
+ auto& rTypeArray = NumFor[n].Info().nTypeArray;
+ auto& rStrArray = NumFor[n].Info().sStrArray;
+ for ( sal_uInt16 j=0; j<nCnt; j++ )
+ {
+ if ( 0 <= rTypeArray[j] && rTypeArray[j] < NF_KEYWORD_ENTRIES_COUNT )
+ {
+ aStr.append( rKeywords[rTypeArray[j]] );
+ if( NF_KEY_NNNN == rTypeArray[j] )
+ {
+ aStr.append( rLocWrp.getLongDateDayOfWeekSep() );
+ }
+ switch (rTypeArray[j])
+ {
+ case NF_KEY_EC:
+ case NF_KEY_EEC:
+ case NF_KEY_R:
+ case NF_KEY_RR:
+ // Implicit secondary (non-gregorian) calendar.
+ // Currently only for ja-JP.
+ /* TODO: same for all locales in
+ * LocaleDataWrapper::doesSecondaryCalendarUseEC() ?
+ * Should split the locales off that then. */
+ if (!nCalendarID)
+ {
+ const LanguageType nLang = MsLangId::getRealLanguage( nOriginalLang);
+ if (nLang == LANGUAGE_JAPANESE)
+ nCalendarID = kCalGengou;
+ }
+ break;
+ default:
+ ; // nothing
+ }
+ }
+ else
+ {
+ switch ( rTypeArray[j] )
+ {
+ case NF_SYMBOLTYPE_DECSEP :
+ aStr.append( rLocWrp.getNumDecimalSep() );
+ break;
+ case NF_SYMBOLTYPE_THSEP :
+ aStr.append( rLocWrp.getNumThousandSep() );
+ break;
+ case NF_SYMBOLTYPE_EXP :
+ aStr.append( rKeywords[NF_KEY_E] );
+ if ( rStrArray[j].getLength() > 1 && rStrArray[j][1] == '+' )
+ aStr.append( "+" );
+ else
+ // tdf#102370: Excel code for exponent without sign
+ aStr.append( "-" );
+ break;
+ case NF_SYMBOLTYPE_DATESEP :
+ aStr.append( rLocWrp.getDateSep() );
+ break;
+ case NF_SYMBOLTYPE_TIMESEP :
+ aStr.append( rLocWrp.getTimeSep() );
+ break;
+ case NF_SYMBOLTYPE_TIME100SECSEP :
+ aStr.append( rLocWrp.getTime100SecSep() );
+ break;
+ case NF_SYMBOLTYPE_FRACBLANK :
+ case NF_SYMBOLTYPE_STRING :
+ if ( rStrArray[j].getLength() == 1 )
+ {
+ if ( rTypeArray[j] == NF_SYMBOLTYPE_STRING )
+ aStr.append( '\\' );
+ aStr.append( rStrArray[j] );
+ }
+ else
+ {
+ aStr.append( "\"" + rStrArray[j] + "\"" );
+ }
+ break;
+ case NF_SYMBOLTYPE_CALDEL :
+ if (j + 1 >= nCnt)
+ break;
+ if ( rStrArray[j+1] == "gengou" )
+ {
+ nCalendarID = kCalGengou;
+ }
+ else if ( rStrArray[j+1] == "hijri" )
+ {
+ nCalendarID = 0x0060000;
+ }
+ else if ( rStrArray[j+1] == "buddhist" )
+ {
+ nCalendarID = 0x0070000;
+ }
+ else if ( rStrArray[j+1] == "jewish" )
+ {
+ nCalendarID = 0x0080000;
+ }
+ // Other calendars (see tdf#36038) not corresponding between LibO and XL.
+ // However, skip any calendar modifier and don't write
+ // as format code (if not as literal string).
+ j += 2;
+ break;
+ case NF_SYMBOLTYPE_CURREXT :
+ nPosHaveLCID = aStr.getLength();
+ aStr.append( rStrArray[j] );
+ break;
+ default:
+ aStr.append( rStrArray[j] );
+ }
+ }
+ }
+ }
+ sal_uInt32 nAlphabetID = 0x0000000; // Excel ID of alphabet used for numerals see tdf#36038
+ LanguageType nLanguageID = LANGUAGE_SYSTEM;
+ if ( aNatNum.IsComplete() )
+ {
+ nLanguageID = MsLangId::getRealLanguage( aNatNum.GetLang());
+ if ( aNatNum.GetNatNum() == 0 )
+ {
+ nAlphabetID = 0x01000000; // Arabic-european numerals
+ }
+ else if ( nCalendarID > 0 || aNatNum.GetDBNum() == 0 || aNatNum.GetDBNum() == aNatNum.GetNatNum() )
+ { // if no DBNum code then use long LCID
+ // if DBNum value != NatNum value, use DBNum and not extended LCID
+ // if calendar, then DBNum will be removed
+ LanguageType pri = primary(nLanguageID);
+ if ( pri == LANGUAGE_ARABIC_PRIMARY_ONLY )
+ nAlphabetID = 0x02000000; // Arabic-indic numerals
+ else if ( pri == primary(LANGUAGE_FARSI) )
+ nAlphabetID = 0x03000000; // Farsi numerals
+ else if ( pri.anyOf(
+ primary(LANGUAGE_HINDI),
+ primary(LANGUAGE_MARATHI),
+ primary(LANGUAGE_NEPALI) ))
+ nAlphabetID = 0x04000000; // Devanagari numerals
+ else if ( pri == primary(LANGUAGE_BENGALI) )
+ nAlphabetID = 0x05000000; // Bengali numerals
+ else if ( pri == primary(LANGUAGE_PUNJABI) )
+ {
+ if ( nLanguageID == LANGUAGE_PUNJABI_ARABIC_LSO )
+ nAlphabetID = 0x02000000; // Arabic-indic numerals
+ else
+ nAlphabetID = 0x06000000; // Punjabi numerals
+ }
+ else if ( pri == primary(LANGUAGE_GUJARATI) )
+ nAlphabetID = 0x07000000; // Gujarati numerals
+ else if ( pri == primary(LANGUAGE_ODIA))
+ nAlphabetID = 0x08000000; // Odia (Oriya) numerals
+ else if ( pri == primary(LANGUAGE_TAMIL))
+ nAlphabetID = 0x09000000; // Tamil numerals
+ else if ( pri == primary(LANGUAGE_TELUGU))
+ nAlphabetID = 0x0A000000; // Telugu numerals
+ else if ( pri == primary(LANGUAGE_KANNADA))
+ nAlphabetID = 0x0B000000; // Kannada numerals
+ else if ( pri == primary(LANGUAGE_MALAYALAM))
+ nAlphabetID = 0x0C000000; // Malayalam numerals
+ else if ( pri == primary(LANGUAGE_THAI))
+ {
+ // The Thai T NatNum modifier during Xcl export.
+ if ( rKeywords[NF_KEY_THAI_T] == "T" )
+ nAlphabetID = 0x0D000000; // Thai numerals
+ }
+ else if ( pri == primary(LANGUAGE_LAO))
+ nAlphabetID = 0x0E000000; // Lao numerals
+ else if ( pri == primary(LANGUAGE_TIBETAN))
+ nAlphabetID = 0x0F000000; // Tibetan numerals
+ else if ( pri == primary(LANGUAGE_BURMESE))
+ nAlphabetID = 0x10000000; // Burmese numerals
+ else if ( pri == primary(LANGUAGE_TIGRIGNA_ETHIOPIA))
+ nAlphabetID = 0x11000000; // Tigrigna numerals
+ else if ( pri == primary(LANGUAGE_KHMER))
+ nAlphabetID = 0x12000000; // Khmer numerals
+ else if ( pri == primary(LANGUAGE_MONGOLIAN_MONGOLIAN_MONGOLIA))
+ {
+ if ( nLanguageID != LANGUAGE_MONGOLIAN_CYRILLIC_MONGOLIA
+ && nLanguageID != LANGUAGE_MONGOLIAN_CYRILLIC_LSO )
+ nAlphabetID = 0x13000000; // Mongolian numerals
+ }
+ // CJK numerals
+ else if ( pri == primary(LANGUAGE_JAPANESE))
+ {
+ nAlphabetID = 0x1B;
+ lcl_incrementAlphabetWithNatNum ( nAlphabetID, aNatNum.GetNatNum() );
+ }
+ else if ( pri == primary(LANGUAGE_CHINESE))
+ {
+ if ( nLanguageID == LANGUAGE_CHINESE_TRADITIONAL
+ || nLanguageID == LANGUAGE_CHINESE_HONGKONG
+ || nLanguageID == LANGUAGE_CHINESE_MACAU )
+ {
+ nAlphabetID = 0x21;
+ lcl_incrementAlphabetWithNatNum ( nAlphabetID, aNatNum.GetNatNum() );
+ }
+ else // LANGUAGE_CHINESE_SIMPLIFIED
+ {
+ nAlphabetID = 0x1E;
+ lcl_incrementAlphabetWithNatNum ( nAlphabetID, aNatNum.GetNatNum() );
+ }
+ }
+ else if ( pri == primary(LANGUAGE_KOREAN))
+ {
+ if ( aNatNum.GetNatNum() == 9 ) // Hangul
+ {
+ nAlphabetID = 0x27000000;
+ }
+ else
+ {
+ nAlphabetID = 0x24;
+ lcl_incrementAlphabetWithNatNum ( nAlphabetID, aNatNum.GetNatNum() );
+ }
+ }
+ }
+ // Add LCID to DBNum
+ if ( aNatNum.GetDBNum() > 0 && nLanguageID == LANGUAGE_SYSTEM )
+ nLanguageID = MsLangId::getRealLanguage( aNatNum.GetLang());
+ }
+ else if (nPosHaveLCID < 0)
+ {
+ // Do not insert a duplicated LCID that was already given with a
+ // currency format as [$R-1C09]
+ if (!bSystemLanguage && nOriginalLang != LANGUAGE_DONTKNOW)
+ {
+ // Explicit locale, write only to the first subformat.
+ if (n == 0)
+ nLanguageID = MsLangId::getRealLanguage( nOriginalLang);
+ }
+ else if (bSystemLanguage && maLocale.meLanguageWithoutLocaleData != LANGUAGE_DONTKNOW)
+ {
+ // Explicit locale but no locale data thus assigned to system
+ // locale, preserve for roundtrip, write only to the first
+ // subformat.
+ if (n == 0)
+ nLanguageID = maLocale.meLanguageWithoutLocaleData;
+ }
+ }
+ if ( nCalendarID > 0 )
+ { // Add alphabet and language to calendar
+ if ( nAlphabetID == 0 )
+ nAlphabetID = 0x01000000;
+ if ( nLanguageID == LANGUAGE_SYSTEM && nOriginalLang != LANGUAGE_DONTKNOW )
+ nLanguageID = nOriginalLang;
+ }
+ lcl_insertLCID( aStr, nAlphabetID + nCalendarID + static_cast<sal_uInt16>(nLanguageID), nPosInsertLCID,
+ bDBNumInserted);
+ }
+ for ( ; nSub<4 && bDefault[nSub]; ++nSub )
+ { // append empty subformats
+ aStr.append( ';' );
+ }
+ return aStr.makeStringAndClear();
+}
+
+OUString SvNumberformat::ImpGetNatNumString( const SvNumberNatNum& rNum,
+ sal_Int64 nVal, sal_uInt16 nMinDigits ) const
+{
+ OUString aStr;
+ if ( nMinDigits )
+ {
+ if ( nMinDigits == 2 )
+ {
+ // speed up the most common case
+ if ( 0 <= nVal && nVal < 10 )
+ {
+ sal_Unicode aBuf[2];
+ aBuf[0] = '0';
+ aBuf[1] = '0' + nVal;
+ aStr = OUString(aBuf, SAL_N_ELEMENTS(aBuf));
+ }
+ else
+ {
+ aStr = OUString::number( nVal );
+ }
+ }
+ else
+ {
+ OUString aValStr( OUString::number( nVal ) );
+ if ( aValStr.getLength() >= nMinDigits )
+ {
+ aStr = aValStr;
+ }
+ else
+ {
+ OUStringBuffer aBuf;
+ for(sal_Int32 index = 0; index < nMinDigits - aValStr.getLength(); ++index)
+ {
+ aBuf.append('0');
+ }
+ aBuf.append(aValStr);
+ aStr = aBuf.makeStringAndClear();
+ }
+ }
+ }
+ else
+ {
+ aStr = OUString::number( nVal );
+ }
+ return impTransliterate(aStr, rNum);
+}
+
+OUString SvNumberformat::impTransliterateImpl(const OUString& rStr,
+ const SvNumberNatNum& rNum ) const
+{
+ css::lang::Locale aLocale( LanguageTag( rNum.GetLang() ).getLocale() );
+ return GetFormatter().GetNatNum()->getNativeNumberStringParams(rStr, aLocale, rNum.GetNatNum(),
+ rNum.GetParams());
+}
+
+void SvNumberformat::impTransliterateImpl(OUStringBuffer& rStr,
+ const SvNumberNatNum& rNum ) const
+{
+ css::lang::Locale aLocale( LanguageTag( rNum.GetLang() ).getLocale() );
+
+ rStr = GetFormatter().GetNatNum()->getNativeNumberStringParams(
+ OUString::unacquired(rStr), aLocale, rNum.GetNatNum(), rNum.GetParams());
+}
+
+OUString SvNumberformat::impTransliterateImpl(const OUString& rStr,
+ const SvNumberNatNum& rNum,
+ const sal_uInt16 nDateKey) const
+{
+ // no KEYWORD=argument list in NatNum12
+ if (rNum.GetParams().indexOf('=') == -1)
+ return impTransliterateImpl( rStr, rNum);
+
+ const NfKeywordTable & rKeywords = rScan.GetKeywords();
+
+ // Format: KEYWORD=numbertext_prefix, ..., for example:
+ // [NatNum12 YYYY=title ordinal,MMMM=article, D=ordinal-number]
+ sal_Int32 nField = -1;
+ do
+ {
+ nField = rNum.GetParams().indexOf(Concat2View(rKeywords[nDateKey] + "="), ++nField);
+ }
+ while (nField != -1 && nField != 0 &&
+ (rNum.GetParams()[nField - 1] != ',' &&
+ rNum.GetParams()[nField - 1] != ' '));
+
+ // no format specified for actual keyword
+ if (nField == -1)
+ return rStr;
+
+ sal_Int32 nKeywordLen = rKeywords[nDateKey].getLength() + 1;
+ sal_Int32 nFieldEnd = rNum.GetParams().indexOf(',', nField);
+
+ if (nFieldEnd == -1)
+ nFieldEnd = rNum.GetParams().getLength();
+
+ css::lang::Locale aLocale( LanguageTag( rNum.GetLang() ).getLocale() );
+
+ return GetFormatter().GetNatNum()->getNativeNumberStringParams(
+ rStr, aLocale, rNum.GetNatNum(),
+ rNum.GetParams().copy(nField + nKeywordLen, nFieldEnd - nField - nKeywordLen));
+}
+
+void SvNumberformat::GetNatNumXml( css::i18n::NativeNumberXmlAttributes2& rAttr,
+ sal_uInt16 nNumFor ) const
+{
+ if ( nNumFor <= 3 )
+ {
+ const SvNumberNatNum& rNum = NumFor[nNumFor].GetNatNum();
+ if ( rNum.IsSet() )
+ {
+ css::lang::Locale aLocale(
+ LanguageTag( rNum.GetLang() ).getLocale() );
+
+ /* TODO: a new XNativeNumberSupplier2::convertToXmlAttributes()
+ * should rather return NativeNumberXmlAttributes2 and places
+ * adapted, and whether to fill Spellout or something different
+ * should be internal there. */
+ css::i18n::NativeNumberXmlAttributes aTmp(
+ GetFormatter().GetNatNum()->convertToXmlAttributes(
+ aLocale, rNum.GetNatNum()));
+ rAttr.Locale = aTmp.Locale;
+ rAttr.Format = aTmp.Format;
+ rAttr.Style = aTmp.Style;
+ if ( NatNumTakesParameters(rNum.GetNatNum()) )
+ {
+ // NatNum12 spell out numbers, dates and money amounts
+ rAttr.Spellout = rNum.GetParams();
+ // Mutually exclusive.
+ rAttr.Format.clear();
+ rAttr.Style.clear();
+ }
+ else
+ {
+ rAttr.Spellout.clear();
+ }
+ }
+ else
+ {
+ rAttr = css::i18n::NativeNumberXmlAttributes2();
+ }
+ }
+ else
+ {
+ rAttr = css::i18n::NativeNumberXmlAttributes2();
+ }
+}
+
+OUString SvNumberformat::GetNatNumModifierString( sal_uInt16 nNumFor ) const
+{
+ if ( nNumFor > 3 )
+ return "";
+ const SvNumberNatNum& rNum = NumFor[nNumFor].GetNatNum();
+ if ( !rNum.IsSet() )
+ return "";
+ const sal_Int32 nNum = rNum.GetNatNum();
+ OUStringBuffer sNatNumModifier = "[NatNum" + OUString::number( nNum );
+ if ( NatNumTakesParameters( nNum ) )
+ {
+ sNatNumModifier.append( " " + rNum.GetParams() );
+ }
+ sNatNumModifier.append( "]" );
+
+ return sNatNumModifier.makeStringAndClear();
+}
+
+// static
+bool SvNumberformat::HasStringNegativeSign( const OUString& rStr )
+{
+ // For Sign '-' needs to be at the start or at the end of the string (blanks ignored)
+ sal_Int32 nLen = rStr.getLength();
+ if ( !nLen )
+ {
+ return false;
+ }
+ const sal_Unicode* const pBeg = rStr.getStr();
+ const sal_Unicode* const pEnd = pBeg + nLen;
+ const sal_Unicode* p = pBeg;
+ do
+ { // Start
+ if ( *p == '-' )
+ {
+ return true;
+ }
+ }
+ while ( *p == ' ' && ++p < pEnd );
+
+ p = pEnd - 1;
+
+ do
+ { // End
+ if ( *p == '-' )
+ {
+ return true;
+ }
+ }
+ while ( *p == ' ' && pBeg < --p );
+ return false;
+}
+
+// static
+bool SvNumberformat::IsInQuote( const OUString& rStr, sal_Int32 nPos,
+ sal_Unicode cQuote, sal_Unicode cEscIn, sal_Unicode cEscOut )
+{
+ sal_Int32 nLen = rStr.getLength();
+ if ( nPos >= nLen )
+ {
+ return false;
+ }
+ const sal_Unicode* p0 = rStr.getStr();
+ const sal_Unicode* p = p0;
+ const sal_Unicode* p1 = p0 + nPos;
+ bool bQuoted = false;
+ while ( p <= p1 )
+ {
+ if ( *p == cQuote )
+ {
+ if ( p == p0 )
+ {
+ bQuoted = true;
+ }
+ else if ( bQuoted )
+ {
+ if ( *(p-1) != cEscIn )
+ {
+ bQuoted = false;
+ }
+ }
+ else
+ {
+ if ( *(p-1) != cEscOut )
+ {
+ bQuoted = true;
+ }
+ }
+ }
+ p++;
+ }
+ return bQuoted;
+}
+
+// static
+sal_Int32 SvNumberformat::GetQuoteEnd( const OUString& rStr, sal_Int32 nPos,
+ sal_Unicode cQuote, sal_Unicode cEscIn )
+{
+ if ( nPos < 0 )
+ {
+ return -1;
+ }
+ sal_Int32 nLen = rStr.getLength();
+ if ( nPos >= nLen )
+ {
+ return -1;
+ }
+ if ( !IsInQuote( rStr, nPos, cQuote, cEscIn ) )
+ {
+ if ( rStr[ nPos ] == cQuote )
+ {
+ return nPos; // Closing cQuote
+ }
+ return -1;
+ }
+ const sal_Unicode* p0 = rStr.getStr();
+ const sal_Unicode* p = p0 + nPos;
+ const sal_Unicode* p1 = p0 + nLen;
+ while ( p < p1 )
+ {
+ if ( *p == cQuote && p > p0 && *(p-1) != cEscIn )
+ {
+ return sal::static_int_cast< sal_Int32 >(p - p0);
+ }
+ p++;
+ }
+ return nLen; // End of String
+}
+
+sal_uInt16 SvNumberformat::GetNumForNumberElementCount( sal_uInt16 nNumFor ) const
+{
+ if ( nNumFor < 4 )
+ {
+ sal_uInt16 nCnt = NumFor[nNumFor].GetCount();
+ return nCnt - ImpGetNumForStringElementCount( nNumFor );
+ }
+ return 0;
+}
+
+sal_uInt16 SvNumberformat::ImpGetNumForStringElementCount( sal_uInt16 nNumFor ) const
+{
+ sal_uInt16 nCnt = 0;
+ sal_uInt16 nNumForCnt = NumFor[nNumFor].GetCount();
+ auto& rTypeArray = NumFor[nNumFor].Info().nTypeArray;
+ for ( sal_uInt16 j=0; j<nNumForCnt; ++j )
+ {
+ switch ( rTypeArray[j] )
+ {
+ case NF_SYMBOLTYPE_STRING:
+ case NF_SYMBOLTYPE_CURRENCY:
+ case NF_SYMBOLTYPE_DATESEP:
+ case NF_SYMBOLTYPE_TIMESEP:
+ case NF_SYMBOLTYPE_TIME100SECSEP:
+ case NF_SYMBOLTYPE_PERCENT:
+ ++nCnt;
+ break;
+ }
+ }
+ return nCnt;
+}
+
+bool SvNumberformat::IsMinuteSecondFormat() const
+{
+ if (GetMaskedType() != SvNumFormatType::TIME)
+ return false;
+
+ constexpr sal_uInt16 k00 = 0x00; // Nada, Nilch
+ constexpr sal_uInt16 kLB = 0x01; // '[' Left Bracket
+ constexpr sal_uInt16 kRB = 0x02; // ']' Right Bracket
+ constexpr sal_uInt16 kMM = 0x04; // M or MM
+ constexpr sal_uInt16 kTS = 0x08; // Time Separator
+ constexpr sal_uInt16 kSS = 0x10; // S or SS
+#define HAS_MINUTE_SECOND(state) ((state) == (kMM|kTS|kSS) || (state) == (kLB|kMM|kRB|kTS|kSS))
+ // Also (kMM|kTS|kLB|kSS|kRB) but those are the same bits.
+
+ sal_uInt16 nState = k00;
+ bool bSep = false;
+ sal_uInt16 nNumForCnt = NumFor[0].GetCount();
+ auto const & rTypeArray = NumFor[0].Info().nTypeArray;
+ for (sal_uInt16 j=0; j < nNumForCnt; ++j)
+ {
+ switch (rTypeArray[j])
+ {
+ case NF_SYMBOLTYPE_DEL:
+ {
+ // '[' or ']' before/after MM or SS
+ const OUString& rStr = NumFor[0].Info().sStrArray[j];
+ if (rStr == "[")
+ {
+ if (nState != k00 && nState != (kMM|kTS))
+ return false;
+ nState |= kLB;
+ }
+ else if (rStr == "]")
+ {
+ if (nState != (kLB|kMM) && nState != (kMM|kTS|kLB|kSS))
+ return false;
+ nState |= kRB;
+ }
+ else
+ return false;
+ }
+ break;
+ case NF_KEY_MI:
+ case NF_KEY_MMI:
+ if (nState != k00 && nState != kLB)
+ return false;
+ nState |= kMM;
+ break;
+ case NF_SYMBOLTYPE_TIMESEP:
+ if (nState != kMM && nState != (kLB|kMM|kRB))
+ return false;
+ nState |= kTS;
+ break;
+ case NF_KEY_S:
+ case NF_KEY_SS:
+ if (nState != (kMM|kTS) && nState != (kLB|kMM|kRB|kTS) && nState != (kMM|kTS|kLB))
+ return false;
+ nState |= kSS;
+ break;
+ case NF_SYMBOLTYPE_TIME100SECSEP:
+ // Trailing fraction of seconds allowed.
+ if (!HAS_MINUTE_SECOND(nState))
+ return false;
+ bSep = true;
+ break;
+ case NF_SYMBOLTYPE_DIGIT:
+ if (!bSep)
+ return false;
+ break;
+ case NF_SYMBOLTYPE_STRING:
+ // nothing, display literal
+ break;
+ default:
+ return false;
+ }
+ }
+ return HAS_MINUTE_SECOND(nState);
+#undef HAS_MINUTE_SECOND
+}
+
+OUString SvNumberformat::GetFormatStringForTimePrecision( int nPrecision ) const
+{
+ OUStringBuffer sString;
+ using comphelper::string::padToLength;
+
+ sal_uInt16 nNumForCnt = NumFor[0].GetCount();
+ auto const & rTypeArray = NumFor[0].Info().nTypeArray;
+ for (sal_uInt16 j=0; j < nNumForCnt; ++j)
+ {
+ switch (rTypeArray[j])
+ {
+ case NF_KEY_S :
+ case NF_KEY_SS:
+ sString.append( NumFor[0].Info().sStrArray[j] );
+ if ( j > 0 && rTypeArray[j-1] == NF_SYMBOLTYPE_DEL && j < nNumForCnt-1 )
+ {
+ j++;
+ sString.append( NumFor[0].Info().sStrArray[j] );
+ }
+ if (nPrecision > 0)
+ {
+ sString.append( rLoc().getTime100SecSep() );
+ padToLength(sString, sString.getLength() + nPrecision, '0');
+ }
+ break;
+ case NF_SYMBOLTYPE_TIME100SECSEP:
+ case NF_SYMBOLTYPE_DIGIT:
+ break;
+ case NF_SYMBOLTYPE_STRING:
+ sString.append( "\"" );
+ [[fallthrough]];
+ default:
+ sString.append( NumFor[0].Info().sStrArray[j] );
+ if (rTypeArray[j] == NF_SYMBOLTYPE_STRING)
+ {
+ sString.append( "\"" );
+ }
+ }
+ }
+
+ return sString.makeStringAndClear();
+}
+
+sal_uInt16 SvNumberformat::GetThousandDivisorPrecision( sal_uInt16 nIx ) const
+{
+ if (nIx >= 4)
+ return 0;
+
+ const ImpSvNumberformatInfo& rInfo = NumFor[nIx].Info();
+
+ if (rInfo.eScannedType != SvNumFormatType::NUMBER && rInfo.eScannedType != SvNumFormatType::CURRENCY)
+ return 0;
+
+ if (rInfo.nThousand == FLAG_STANDARD_IN_FORMAT)
+ return SvNumberFormatter::UNLIMITED_PRECISION;
+
+ return rInfo.nThousand * 3;
+}
+
+const CharClass& SvNumberformat::rChrCls() const
+{
+ return rScan.GetChrCls();
+}
+
+const LocaleDataWrapper& SvNumberformat::rLoc() const
+{
+ return rScan.GetLoc();
+}
+
+CalendarWrapper& SvNumberformat::GetCal() const
+{
+ return rScan.GetCal();
+}
+
+const SvNumberFormatter& SvNumberformat::GetFormatter() const
+{
+ return *rScan.GetNumberformatter();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/numbers/zforscan.cxx b/svl/source/numbers/zforscan.cxx
new file mode 100644
index 0000000000..537c19415f
--- /dev/null
+++ b/svl/source/numbers/zforscan.cxx
@@ -0,0 +1,3332 @@
+/* -*- 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 <stdlib.h>
+#include <comphelper/string.hxx>
+#include <sal/log.hxx>
+#include <tools/debug.hxx>
+#include <i18nlangtag/mslangid.hxx>
+#include <unotools/charclass.hxx>
+#include <unotools/localedatawrapper.hxx>
+#include <com/sun/star/i18n/NumberFormatCode.hpp>
+#include <com/sun/star/i18n/NumberFormatMapper.hpp>
+
+#include <svl/zforlist.hxx>
+#include <svl/zformat.hxx>
+#include <unotools/digitgroupingiterator.hxx>
+
+#include "zforscan.hxx"
+
+#include <svl/nfsymbol.hxx>
+using namespace svt;
+
+const sal_Unicode cNoBreakSpace = 0xA0;
+const sal_Unicode cNarrowNoBreakSpace = 0x202F;
+
+const int MaxCntPost = 20; //max dec places allow by rtl_math_round
+
+const NfKeywordTable ImpSvNumberformatScan::sEnglishKeyword =
+{ // Syntax keywords in English (USA)
+ //! All keywords MUST be UPPERCASE! In same order as NfKeywordIndex
+ "", // NF_KEY_NONE 0
+ "E", // NF_KEY_E Exponent
+ "AM/PM", // NF_KEY_AMPM AM/PM
+ "A/P", // NF_KEY_AP AM/PM short
+ "M", // NF_KEY_MI Minute
+ "MM", // NF_KEY_MMI Minute 02
+ "M", // NF_KEY_M month (!)
+ "MM", // NF_KEY_MM month 02 (!)
+ "MMM", // NF_KEY_MMM month short name
+ "MMMM", // NF_KEY_MMMM month long name
+ "MMMMM", // NF_KEY_MMMMM first letter of month name
+ "H", // NF_KEY_H hour
+ "HH", // NF_KEY_HH hour 02
+ "S", // NF_KEY_S Second
+ "SS", // NF_KEY_SS Second 02
+ "Q", // NF_KEY_Q Quarter short 'Q'
+ "QQ", // NF_KEY_QQ Quarter long
+ "D", // NF_KEY_D day of month
+ "DD", // NF_KEY_DD day of month 02
+ "DDD", // NF_KEY_DDD day of week short
+ "DDDD", // NF_KEY_DDDD day of week long
+ "YY", // NF_KEY_YY year two digits
+ "YYYY", // NF_KEY_YYYY year four digits
+ "NN", // NF_KEY_NN Day of week short
+ "NNN", // NF_KEY_NNN Day of week long
+ "NNNN", // NF_KEY_NNNN Day of week long incl. separator
+ "AAA", // NF_KEY_AAA
+ "AAAA", // NF_KEY_AAAA
+ "E", // NF_KEY_EC
+ "EE", // NF_KEY_EEC
+ "G", // NF_KEY_G
+ "GG", // NF_KEY_GG
+ "GGG", // NF_KEY_GGG
+ "R", // NF_KEY_R
+ "RR", // NF_KEY_RR
+ "WW", // NF_KEY_WW Week of year
+ "t", // NF_KEY_THAI_T Thai T modifier, speciality of Thai Excel, only
+ // used with Thai locale and converted to [NatNum1], only
+ // exception as lowercase
+ "CCC", // NF_KEY_CCC Currency abbreviation
+ "BOOLEAN", // NF_KEY_BOOLEAN boolean
+ "GENERAL", // NF_KEY_GENERAL General / Standard
+
+ // Reserved words translated and color names follow:
+ "TRUE", // NF_KEY_TRUE boolean true
+ "FALSE", // NF_KEY_FALSE boolean false
+ "COLOR", // NF_KEY_COLOR color
+ // colours
+ "BLACK", // NF_KEY_BLACK
+ "BLUE", // NF_KEY_BLUE
+ "GREEN", // NF_KEY_GREEN
+ "CYAN", // NF_KEY_CYAN
+ "RED", // NF_KEY_RED
+ "MAGENTA", // NF_KEY_MAGENTA
+ "BROWN", // NF_KEY_BROWN
+ "GREY", // NF_KEY_GREY
+ "YELLOW", // NF_KEY_YELLOW
+ "WHITE" // NF_KEY_WHITE
+};
+
+const ::std::vector<Color> ImpSvNumberformatScan::StandardColor{
+ COL_BLACK, COL_LIGHTBLUE, COL_LIGHTGREEN, COL_LIGHTCYAN, COL_LIGHTRED,
+ COL_LIGHTMAGENTA, COL_BROWN, COL_GRAY, COL_YELLOW, COL_WHITE
+};
+
+// This vector will hold *only* the color names in German language.
+static const std::u16string_view& GermanColorName(size_t i)
+{
+ static const std::u16string_view sGermanColorNames[]{ u"FARBE", u"SCHWARZ", u"BLAU",
+ u"GRÃœN", u"CYAN", u"ROT",
+ u"MAGENTA", u"BRAUN", u"GRAU",
+ u"GELB", u"WEISS" };
+ assert(i < SAL_N_ELEMENTS(sGermanColorNames));
+ return sGermanColorNames[i];
+}
+
+ImpSvNumberformatScan::ImpSvNumberformatScan( SvNumberFormatter* pFormatterP )
+ : maNullDate( 30, 12, 1899)
+ , eNewLnge(LANGUAGE_DONTKNOW)
+ , eTmpLnge(LANGUAGE_DONTKNOW)
+ , nCurrPos(-1)
+ , meKeywordLocalization(KeywordLocalization::AllowEnglish)
+{
+ pFormatter = pFormatterP;
+ xNFC = css::i18n::NumberFormatMapper::create( pFormatter->GetComponentContext() );
+ bConvertMode = false;
+ mbConvertDateOrder = false;
+ bConvertSystemToSystem = false;
+ bKeywordsNeedInit = true; // locale dependent and not locale dependent keywords
+ bCompatCurNeedInit = true; // locale dependent compatibility currency strings
+
+ static_assert( NF_KEY_BLACK - NF_KEY_COLOR == 1, "bad FARBE(COLOR), SCHWARZ(BLACK) sequence");
+ static_assert( NF_KEY_FIRSTCOLOR - NF_KEY_COLOR == 1, "bad color sequence");
+ static_assert( NF_MAX_DEFAULT_COLORS + 1 == 11, "bad color count");
+ static_assert( NF_KEY_WHITE - NF_KEY_COLOR + 1 == 11, "bad color sequence count");
+
+ nStandardPrec = 2;
+
+ Reset();
+}
+
+ImpSvNumberformatScan::~ImpSvNumberformatScan()
+{
+ Reset();
+}
+
+void ImpSvNumberformatScan::ChangeIntl( KeywordLocalization eKeywordLocalization )
+{
+ meKeywordLocalization = eKeywordLocalization;
+ bKeywordsNeedInit = true;
+ bCompatCurNeedInit = true;
+ // may be initialized by InitSpecialKeyword()
+ sKeyword[NF_KEY_TRUE].clear();
+ sKeyword[NF_KEY_FALSE].clear();
+}
+
+void ImpSvNumberformatScan::InitSpecialKeyword( NfKeywordIndex eIdx ) const
+{
+ switch ( eIdx )
+ {
+ case NF_KEY_TRUE :
+ const_cast<ImpSvNumberformatScan*>(this)->sKeyword[NF_KEY_TRUE] =
+ pFormatter->GetCharClass()->uppercase( pFormatter->GetLocaleData()->getTrueWord() );
+ if ( sKeyword[NF_KEY_TRUE].isEmpty() )
+ {
+ SAL_WARN( "svl.numbers", "InitSpecialKeyword: TRUE_WORD?" );
+ const_cast<ImpSvNumberformatScan*>(this)->sKeyword[NF_KEY_TRUE] = sEnglishKeyword[NF_KEY_TRUE];
+ }
+ break;
+ case NF_KEY_FALSE :
+ const_cast<ImpSvNumberformatScan*>(this)->sKeyword[NF_KEY_FALSE] =
+ pFormatter->GetCharClass()->uppercase( pFormatter->GetLocaleData()->getFalseWord() );
+ if ( sKeyword[NF_KEY_FALSE].isEmpty() )
+ {
+ SAL_WARN( "svl.numbers", "InitSpecialKeyword: FALSE_WORD?" );
+ const_cast<ImpSvNumberformatScan*>(this)->sKeyword[NF_KEY_FALSE] = sEnglishKeyword[NF_KEY_FALSE];
+ }
+ break;
+ default:
+ SAL_WARN( "svl.numbers", "InitSpecialKeyword: unknown request" );
+ }
+}
+
+void ImpSvNumberformatScan::InitCompatCur() const
+{
+ ImpSvNumberformatScan* pThis = const_cast<ImpSvNumberformatScan*>(this);
+ // currency symbol for old style ("automatic") compatibility format codes
+ pFormatter->GetCompatibilityCurrency( pThis->sCurSymbol, pThis->sCurAbbrev );
+ // currency symbol upper case
+ pThis->sCurString = pFormatter->GetCharClass()->uppercase( sCurSymbol );
+ bCompatCurNeedInit = false;
+}
+
+void ImpSvNumberformatScan::InitKeywords() const
+{
+ if ( !bKeywordsNeedInit )
+ return ;
+ const_cast<ImpSvNumberformatScan*>(this)->SetDependentKeywords();
+ bKeywordsNeedInit = false;
+}
+
+/** Extract the name of General, Standard, Whatever, ignoring leading modifiers
+ such as [NatNum1]. */
+static OUString lcl_extractStandardGeneralName( const OUString & rCode )
+{
+ OUString aStr;
+ const sal_Unicode* p = rCode.getStr();
+ const sal_Unicode* const pStop = p + rCode.getLength();
+ const sal_Unicode* pBeg = p; // name begins here
+ bool bMod = false;
+ bool bDone = false;
+ while (p < pStop && !bDone)
+ {
+ switch (*p)
+ {
+ case '[':
+ bMod = true;
+ break;
+ case ']':
+ if (bMod)
+ {
+ bMod = false;
+ pBeg = p+1;
+ }
+ // else: would be a locale data error, easily to be spotted in
+ // UI dialog
+ break;
+ case ';':
+ if (!bMod)
+ {
+ bDone = true;
+ --p; // put back, increment by one follows
+ }
+ break;
+ }
+ ++p;
+ if (bMod)
+ {
+ pBeg = p;
+ }
+ }
+ if (pBeg < p)
+ {
+ aStr = rCode.copy( pBeg - rCode.getStr(), p - pBeg);
+ }
+ return aStr;
+}
+
+void ImpSvNumberformatScan::SetDependentKeywords()
+{
+ using namespace ::com::sun::star;
+ using namespace ::com::sun::star::uno;
+
+ const CharClass* pCharClass = pFormatter->GetCharClass();
+ const LocaleDataWrapper* pLocaleData = pFormatter->GetLocaleData();
+ // #80023# be sure to generate keywords for the loaded Locale, not for the
+ // requested Locale, otherwise number format codes might not match
+ const LanguageTag& rLoadedLocale = pLocaleData->getLoadedLanguageTag();
+ LanguageType eLang = rLoadedLocale.getLanguageType( false);
+
+ bool bL10n = (meKeywordLocalization != KeywordLocalization::EnglishOnly);
+ if (bL10n)
+ {
+ // Check if this actually is a locale that uses any localized keywords,
+ // if not then disable localized keywords completely.
+ if ( !eLang.anyOf( LANGUAGE_GERMAN,
+ LANGUAGE_GERMAN_SWISS,
+ LANGUAGE_GERMAN_AUSTRIAN,
+ LANGUAGE_GERMAN_LUXEMBOURG,
+ LANGUAGE_GERMAN_LIECHTENSTEIN,
+ LANGUAGE_DUTCH,
+ LANGUAGE_DUTCH_BELGIAN,
+ LANGUAGE_FRENCH,
+ LANGUAGE_FRENCH_BELGIAN,
+ LANGUAGE_FRENCH_CANADIAN,
+ LANGUAGE_FRENCH_SWISS,
+ LANGUAGE_FRENCH_LUXEMBOURG,
+ LANGUAGE_FRENCH_MONACO,
+ LANGUAGE_FINNISH,
+ LANGUAGE_ITALIAN,
+ LANGUAGE_ITALIAN_SWISS,
+ LANGUAGE_DANISH,
+ LANGUAGE_NORWEGIAN,
+ LANGUAGE_NORWEGIAN_BOKMAL,
+ LANGUAGE_NORWEGIAN_NYNORSK,
+ LANGUAGE_SWEDISH,
+ LANGUAGE_SWEDISH_FINLAND,
+ LANGUAGE_PORTUGUESE,
+ LANGUAGE_PORTUGUESE_BRAZILIAN,
+ LANGUAGE_SPANISH_MODERN,
+ LANGUAGE_SPANISH_DATED,
+ LANGUAGE_SPANISH_MEXICAN,
+ LANGUAGE_SPANISH_GUATEMALA,
+ LANGUAGE_SPANISH_COSTARICA,
+ LANGUAGE_SPANISH_PANAMA,
+ LANGUAGE_SPANISH_DOMINICAN_REPUBLIC,
+ LANGUAGE_SPANISH_VENEZUELA,
+ LANGUAGE_SPANISH_COLOMBIA,
+ LANGUAGE_SPANISH_PERU,
+ LANGUAGE_SPANISH_ARGENTINA,
+ LANGUAGE_SPANISH_ECUADOR,
+ LANGUAGE_SPANISH_CHILE,
+ LANGUAGE_SPANISH_URUGUAY,
+ LANGUAGE_SPANISH_PARAGUAY,
+ LANGUAGE_SPANISH_BOLIVIA,
+ LANGUAGE_SPANISH_EL_SALVADOR,
+ LANGUAGE_SPANISH_HONDURAS,
+ LANGUAGE_SPANISH_NICARAGUA,
+ LANGUAGE_SPANISH_PUERTO_RICO ))
+ {
+ bL10n = false;
+ meKeywordLocalization = KeywordLocalization::EnglishOnly;
+ }
+ }
+
+ // Init the current NfKeywordTable with English keywords.
+ sKeyword = sEnglishKeyword;
+
+ // Set the uppercase localized General name, e.g. Standard -> STANDARD
+ i18n::NumberFormatCode aFormat = xNFC->getFormatCode( NF_NUMBER_STANDARD, rLoadedLocale.getLocale() );
+ sNameStandardFormat = lcl_extractStandardGeneralName( aFormat.Code );
+ sKeyword[NF_KEY_GENERAL] = pCharClass->uppercase( sNameStandardFormat );
+
+ // Thai T NatNum special. Other locale's small letter 't' results in upper
+ // case comparison not matching but length does in conversion mode. Ugly.
+ if (eLang == LANGUAGE_THAI)
+ {
+ sKeyword[NF_KEY_THAI_T] = "T";
+ }
+ else
+ {
+ sKeyword[NF_KEY_THAI_T] = sEnglishKeyword[NF_KEY_THAI_T];
+ }
+
+ // boolean keywords
+ InitSpecialKeyword( NF_KEY_TRUE );
+ InitSpecialKeyword( NF_KEY_FALSE );
+
+ // Boolean equivalent format codes that are written to Excel files, may
+ // have been written to ODF as well, specifically if such loaded Excel file
+ // was saved as ODF, and shall result in proper Boolean again.
+ // "TRUE";"TRUE";"FALSE"
+ sBooleanEquivalent1 = "\"" + sKeyword[NF_KEY_TRUE] + "\";\"" +
+ sKeyword[NF_KEY_TRUE] + "\";\"" + sKeyword[NF_KEY_FALSE] + "\"";
+ // [>0]"TRUE";[<0]"TRUE";"FALSE"
+ sBooleanEquivalent2 = "[>0]\"" + sKeyword[NF_KEY_TRUE] + "\";[<0]\"" +
+ sKeyword[NF_KEY_TRUE] + "\";\"" + sKeyword[NF_KEY_FALSE] + "\"";
+
+ // compatibility currency strings
+ InitCompatCur();
+
+ if (!bL10n)
+ return;
+
+ // All locale dependent keywords overrides follow.
+
+ if ( eLang.anyOf(
+ LANGUAGE_GERMAN,
+ LANGUAGE_GERMAN_SWISS,
+ LANGUAGE_GERMAN_AUSTRIAN,
+ LANGUAGE_GERMAN_LUXEMBOURG,
+ LANGUAGE_GERMAN_LIECHTENSTEIN))
+ {
+ //! all capital letters
+ sKeyword[NF_KEY_M] = "M"; // month 1
+ sKeyword[NF_KEY_MM] = "MM"; // month 01
+ sKeyword[NF_KEY_MMM] = "MMM"; // month Jan
+ sKeyword[NF_KEY_MMMM] = "MMMM"; // month Januar
+ sKeyword[NF_KEY_MMMMM] = "MMMMM"; // month J
+ sKeyword[NF_KEY_H] = "H"; // hour 2
+ sKeyword[NF_KEY_HH] = "HH"; // hour 02
+ sKeyword[NF_KEY_D] = "T";
+ sKeyword[NF_KEY_DD] = "TT";
+ sKeyword[NF_KEY_DDD] = "TTT";
+ sKeyword[NF_KEY_DDDD] = "TTTT";
+ sKeyword[NF_KEY_YY] = "JJ";
+ sKeyword[NF_KEY_YYYY] = "JJJJ";
+ sKeyword[NF_KEY_BOOLEAN] = "LOGISCH";
+ sKeyword[NF_KEY_COLOR] = GermanColorName(NF_KEY_COLOR - NF_KEY_COLOR);
+ sKeyword[NF_KEY_BLACK] = GermanColorName(NF_KEY_BLACK - NF_KEY_COLOR);
+ sKeyword[NF_KEY_BLUE] = GermanColorName(NF_KEY_BLUE - NF_KEY_COLOR);
+ sKeyword[NF_KEY_GREEN] = GermanColorName(NF_KEY_GREEN - NF_KEY_COLOR);
+ sKeyword[NF_KEY_CYAN] = GermanColorName(NF_KEY_CYAN - NF_KEY_COLOR);
+ sKeyword[NF_KEY_RED] = GermanColorName(NF_KEY_RED - NF_KEY_COLOR);
+ sKeyword[NF_KEY_MAGENTA] = GermanColorName(NF_KEY_MAGENTA - NF_KEY_COLOR);
+ sKeyword[NF_KEY_BROWN] = GermanColorName(NF_KEY_BROWN - NF_KEY_COLOR);
+ sKeyword[NF_KEY_GREY] = GermanColorName(NF_KEY_GREY - NF_KEY_COLOR);
+ sKeyword[NF_KEY_YELLOW] = GermanColorName(NF_KEY_YELLOW - NF_KEY_COLOR);
+ sKeyword[NF_KEY_WHITE] = GermanColorName(NF_KEY_WHITE - NF_KEY_COLOR);
+ }
+ else
+ {
+ // day
+ if ( eLang.anyOf(
+ LANGUAGE_ITALIAN,
+ LANGUAGE_ITALIAN_SWISS))
+ {
+ sKeyword[NF_KEY_D] = "G";
+ sKeyword[NF_KEY_DD] = "GG";
+ sKeyword[NF_KEY_DDD] = "GGG";
+ sKeyword[NF_KEY_DDDD] = "GGGG";
+ // must exchange the era code, same as Xcl
+ sKeyword[NF_KEY_G] = "X";
+ sKeyword[NF_KEY_GG] = "XX";
+ sKeyword[NF_KEY_GGG] = "XXX";
+ }
+ else if ( eLang.anyOf(
+ LANGUAGE_FRENCH,
+ LANGUAGE_FRENCH_BELGIAN,
+ LANGUAGE_FRENCH_CANADIAN,
+ LANGUAGE_FRENCH_SWISS,
+ LANGUAGE_FRENCH_LUXEMBOURG,
+ LANGUAGE_FRENCH_MONACO))
+ {
+ sKeyword[NF_KEY_D] = "J";
+ sKeyword[NF_KEY_DD] = "JJ";
+ sKeyword[NF_KEY_DDD] = "JJJ";
+ sKeyword[NF_KEY_DDDD] = "JJJJ";
+ }
+ else if ( eLang == LANGUAGE_FINNISH )
+ {
+ sKeyword[NF_KEY_D] = "P";
+ sKeyword[NF_KEY_DD] = "PP";
+ sKeyword[NF_KEY_DDD] = "PPP";
+ sKeyword[NF_KEY_DDDD] = "PPPP";
+ }
+
+ // month
+ if ( eLang == LANGUAGE_FINNISH )
+ {
+ sKeyword[NF_KEY_M] = "K";
+ sKeyword[NF_KEY_MM] = "KK";
+ sKeyword[NF_KEY_MMM] = "KKK";
+ sKeyword[NF_KEY_MMMM] = "KKKK";
+ sKeyword[NF_KEY_MMMMM] = "KKKKK";
+ }
+
+ // year
+ if ( eLang.anyOf(
+ LANGUAGE_ITALIAN,
+ LANGUAGE_ITALIAN_SWISS,
+ LANGUAGE_FRENCH,
+ LANGUAGE_FRENCH_BELGIAN,
+ LANGUAGE_FRENCH_CANADIAN,
+ LANGUAGE_FRENCH_SWISS,
+ LANGUAGE_FRENCH_LUXEMBOURG,
+ LANGUAGE_FRENCH_MONACO,
+ LANGUAGE_PORTUGUESE,
+ LANGUAGE_PORTUGUESE_BRAZILIAN,
+ LANGUAGE_SPANISH_MODERN,
+ LANGUAGE_SPANISH_DATED,
+ LANGUAGE_SPANISH_MEXICAN,
+ LANGUAGE_SPANISH_GUATEMALA,
+ LANGUAGE_SPANISH_COSTARICA,
+ LANGUAGE_SPANISH_PANAMA,
+ LANGUAGE_SPANISH_DOMINICAN_REPUBLIC,
+ LANGUAGE_SPANISH_VENEZUELA,
+ LANGUAGE_SPANISH_COLOMBIA,
+ LANGUAGE_SPANISH_PERU,
+ LANGUAGE_SPANISH_ARGENTINA,
+ LANGUAGE_SPANISH_ECUADOR,
+ LANGUAGE_SPANISH_CHILE,
+ LANGUAGE_SPANISH_URUGUAY,
+ LANGUAGE_SPANISH_PARAGUAY,
+ LANGUAGE_SPANISH_BOLIVIA,
+ LANGUAGE_SPANISH_EL_SALVADOR,
+ LANGUAGE_SPANISH_HONDURAS,
+ LANGUAGE_SPANISH_NICARAGUA,
+ LANGUAGE_SPANISH_PUERTO_RICO))
+ {
+ sKeyword[NF_KEY_YY] = "AA";
+ sKeyword[NF_KEY_YYYY] = "AAAA";
+ // must exchange the day of week name code, same as Xcl
+ sKeyword[NF_KEY_AAA] = "OOO";
+ sKeyword[NF_KEY_AAAA] = "OOOO";
+ }
+ else if ( eLang.anyOf(
+ LANGUAGE_DUTCH,
+ LANGUAGE_DUTCH_BELGIAN))
+ {
+ sKeyword[NF_KEY_YY] = "JJ";
+ sKeyword[NF_KEY_YYYY] = "JJJJ";
+ }
+ else if ( eLang == LANGUAGE_FINNISH )
+ {
+ sKeyword[NF_KEY_YY] = "VV";
+ sKeyword[NF_KEY_YYYY] = "VVVV";
+ }
+
+ // hour
+ if ( eLang.anyOf(
+ LANGUAGE_DUTCH,
+ LANGUAGE_DUTCH_BELGIAN))
+ {
+ sKeyword[NF_KEY_H] = "U";
+ sKeyword[NF_KEY_HH] = "UU";
+ }
+ else if ( eLang.anyOf(
+ LANGUAGE_FINNISH,
+ LANGUAGE_SWEDISH,
+ LANGUAGE_SWEDISH_FINLAND,
+ LANGUAGE_DANISH,
+ LANGUAGE_NORWEGIAN,
+ LANGUAGE_NORWEGIAN_BOKMAL,
+ LANGUAGE_NORWEGIAN_NYNORSK))
+ {
+ sKeyword[NF_KEY_H] = "T";
+ sKeyword[NF_KEY_HH] = "TT";
+ }
+ }
+}
+
+void ImpSvNumberformatScan::ChangeNullDate(sal_uInt16 nDay, sal_uInt16 nMonth, sal_Int16 nYear)
+{
+ Date aDate(nDay, nMonth, nYear);
+ if (!aDate.IsValidDate())
+ {
+ aDate.Normalize();
+ SAL_WARN("svl.numbers","ImpSvNumberformatScan::ChangeNullDate - not valid"
+ " d: " << nDay << " m: " << nMonth << " y: " << nYear << " normalized to"
+ " d: " << aDate.GetDay() << " m: " << aDate.GetMonth() << " y: " << aDate.GetYear());
+ // Slap the caller if really bad, like year 0.
+ assert(aDate.IsValidDate());
+ }
+ if (aDate.IsValidDate())
+ maNullDate = aDate;
+}
+
+void ImpSvNumberformatScan::ChangeStandardPrec(sal_uInt16 nPrec)
+{
+ nStandardPrec = nPrec;
+}
+
+const Color* ImpSvNumberformatScan::GetColor(OUString& sStr) const
+{
+ OUString sString = pFormatter->GetCharClass()->uppercase(sStr);
+ const NfKeywordTable & rKeyword = GetKeywords();
+ size_t i = 0;
+ while (i < NF_MAX_DEFAULT_COLORS && sString != rKeyword[NF_KEY_FIRSTCOLOR+i] )
+ {
+ i++;
+ }
+ if (i >= NF_MAX_DEFAULT_COLORS && meKeywordLocalization == KeywordLocalization::AllowEnglish)
+ {
+ LanguageType eLang = pFormatter->GetLocaleData()->getLoadedLanguageTag().getLanguageType( false);
+ if ( eLang.anyOf(
+ LANGUAGE_GERMAN,
+ LANGUAGE_GERMAN_SWISS,
+ LANGUAGE_GERMAN_AUSTRIAN,
+ LANGUAGE_GERMAN_LUXEMBOURG,
+ LANGUAGE_GERMAN_LIECHTENSTEIN )) // only German uses localized color names
+ {
+ size_t j = 0;
+ while ( j < NF_MAX_DEFAULT_COLORS && sString != sEnglishKeyword[NF_KEY_FIRSTCOLOR + j] )
+ {
+ ++j;
+ }
+ if ( j < NF_MAX_DEFAULT_COLORS )
+ {
+ i = j;
+ }
+ }
+ }
+
+ enum ColorKeywordConversion
+ {
+ None,
+ GermanToEnglish,
+ EnglishToGerman
+ } eColorKeywordConversion(None);
+
+ if (bConvertMode)
+ {
+ const bool bFromGerman = eTmpLnge.anyOf(
+ LANGUAGE_GERMAN,
+ LANGUAGE_GERMAN_SWISS,
+ LANGUAGE_GERMAN_AUSTRIAN,
+ LANGUAGE_GERMAN_LUXEMBOURG,
+ LANGUAGE_GERMAN_LIECHTENSTEIN);
+ const bool bToGerman = eNewLnge.anyOf(
+ LANGUAGE_GERMAN,
+ LANGUAGE_GERMAN_SWISS,
+ LANGUAGE_GERMAN_AUSTRIAN,
+ LANGUAGE_GERMAN_LUXEMBOURG,
+ LANGUAGE_GERMAN_LIECHTENSTEIN);
+ if (bFromGerman && !bToGerman)
+ eColorKeywordConversion = ColorKeywordConversion::GermanToEnglish;
+ else if (!bFromGerman && bToGerman)
+ eColorKeywordConversion = ColorKeywordConversion::EnglishToGerman;
+ }
+
+ const Color* pResult = nullptr;
+ if (i >= NF_MAX_DEFAULT_COLORS)
+ {
+ const OUString& rColorWord = rKeyword[NF_KEY_COLOR];
+ bool bL10n = true;
+ if ((bL10n = sString.startsWith(rColorWord)) ||
+ ((meKeywordLocalization == KeywordLocalization::AllowEnglish) &&
+ sString.startsWith(sEnglishKeyword[NF_KEY_COLOR])))
+ {
+ sal_Int32 nPos = (bL10n ? rColorWord.getLength() : sEnglishKeyword[NF_KEY_COLOR].getLength());
+ sStr = sStr.copy(nPos);
+ sStr = comphelper::string::strip(sStr, ' ');
+ switch (eColorKeywordConversion)
+ {
+ case ColorKeywordConversion::None:
+ sStr = rColorWord + sStr;
+ break;
+ case ColorKeywordConversion::GermanToEnglish:
+ sStr = sEnglishKeyword[NF_KEY_COLOR] + sStr; // Farbe -> COLOR
+ break;
+ case ColorKeywordConversion::EnglishToGerman:
+ sStr = GermanColorName(NF_KEY_COLOR - NF_KEY_COLOR) + sStr; // Color -> FARBE
+ break;
+ }
+ sString = sString.copy(nPos);
+ sString = comphelper::string::strip(sString, ' ');
+
+ if ( CharClass::isAsciiNumeric( sString ) )
+ {
+ sal_Int32 nIndex = sString.toInt32();
+ if (nIndex > 0 && nIndex <= 64)
+ {
+ pResult = pFormatter->GetUserDefColor(static_cast<sal_uInt16>(nIndex)-1);
+ }
+ }
+ }
+ }
+ else
+ {
+ sStr.clear();
+ switch (eColorKeywordConversion)
+ {
+ case ColorKeywordConversion::None:
+ sStr = rKeyword[NF_KEY_FIRSTCOLOR+i];
+ break;
+ case ColorKeywordConversion::GermanToEnglish:
+ sStr = sEnglishKeyword[NF_KEY_FIRSTCOLOR + i]; // Rot -> RED
+ break;
+ case ColorKeywordConversion::EnglishToGerman:
+ sStr = GermanColorName(NF_KEY_FIRSTCOLOR - NF_KEY_COLOR + i); // Red -> ROT
+ break;
+ }
+ pResult = &(StandardColor[i]);
+ }
+ return pResult;
+}
+
+short ImpSvNumberformatScan::GetKeyWord( const OUString& sSymbol, sal_Int32 nPos, bool& rbFoundEnglish ) const
+{
+ OUString sString = pFormatter->GetCharClass()->uppercase( sSymbol, nPos, sSymbol.getLength() - nPos );
+ const NfKeywordTable & rKeyword = GetKeywords();
+ // #77026# for the Xcl perverts: the GENERAL keyword is recognized anywhere
+ if (sString.startsWith( rKeyword[NF_KEY_GENERAL] ))
+ {
+ return NF_KEY_GENERAL;
+ }
+ if ((meKeywordLocalization == KeywordLocalization::AllowEnglish) &&
+ sString.startsWith( sEnglishKeyword[NF_KEY_GENERAL]))
+ {
+ rbFoundEnglish = true;
+ return NF_KEY_GENERAL;
+ }
+
+ // MUST be a reverse search to find longer strings first,
+ // new keywords take precedence over old keywords,
+ // skip colors et al after keywords.
+ short i = NF_KEY_LASTKEYWORD;
+ while (i > 0 && !sString.startsWith( rKeyword[i]))
+ {
+ i--;
+ }
+ if (i == 0 && meKeywordLocalization == KeywordLocalization::AllowEnglish)
+ {
+ // No localized (if so) keyword, try English keywords if keywords
+ // are localized. That was already checked in SetDependentKeywords().
+ i = NF_KEY_LASTKEYWORD;
+ while (i > 0 && !sString.startsWith( sEnglishKeyword[i]))
+ {
+ i--;
+ }
+ }
+
+ // The Thai T NatNum modifier during Xcl import.
+ if (i == 0 && bConvertMode &&
+ sString[0] == 'T' &&
+ eTmpLnge == LANGUAGE_ENGLISH_US &&
+ MsLangId::getRealLanguage( eNewLnge) == LANGUAGE_THAI)
+ {
+ i = NF_KEY_THAI_T;
+ }
+ return i; // 0 => not found
+}
+
+/**
+ * Next_Symbol
+ *
+ * Splits up the input for further processing (by the Turing machine).
+ *
+ * Starting state = SsStar
+ *
+ * ---------------+-------------------+---------------------------+---------------
+ * Old state | Character read | Event | New state
+ * ---------------+-------------------+---------------------------+---------------
+ * SsStart | Character | Symbol = Character | SsGetWord
+ * | " | Type = String | SsGetString
+ * | \ | Type = String | SsGetChar
+ * | * | Type = Star | SsGetStar
+ * | _ | Type = Blank | SsGetBlank
+ * | @ # 0 ? / . , % [ | Symbol = Character; |
+ * | ] ' Blank | Type = Control character | SsStop
+ * | $ - + ( ) : | Type = String; |
+ * | Else | Symbol = Character | SsStop
+ * ---------------|-------------------+---------------------------+---------------
+ * SsGetChar | Else | Symbol = Character | SsStop
+ * ---------------+-------------------+---------------------------+---------------
+ * GetString | " | | SsStop
+ * | Else | Symbol += Character | GetString
+ * ---------------+-------------------+---------------------------+---------------
+ * SsGetWord | Character | Symbol += Character |
+ * | + - (E+ E-)| Symbol += Character | SsStop
+ * | / (AM/PM)| Symbol += Character |
+ * | Else | Pos--, if Key Type = Word | SsStop
+ * ---------------+-------------------+---------------------------+---------------
+ * SsGetStar | Else | Symbol += Character | SsStop
+ * | | Mark special case * |
+ * ---------------+-------------------+---------------------------+---------------
+ * SsGetBlank | Else | Symbol + =Character | SsStop
+ * | | Mark special case _ |
+ * ---------------------------------------------------------------+--------------
+ *
+ * If we recognize a keyword in the state SsGetWord (even as the symbol's start text)
+ * we write back the rest of the characters!
+ */
+
+namespace {
+
+enum ScanState
+{
+ SsStop = 0,
+ SsStart = 1,
+ SsGetChar = 2,
+ SsGetString = 3,
+ SsGetWord = 4,
+ SsGetStar = 5,
+ SsGetBlank = 6
+};
+
+}
+
+short ImpSvNumberformatScan::Next_Symbol( const OUString& rStr,
+ sal_Int32& nPos,
+ OUString& sSymbol ) const
+{
+ InitKeywords();
+ const CharClass* pChrCls = pFormatter->GetCharClass();
+ const LocaleDataWrapper* pLoc = pFormatter->GetLocaleData();
+ short eType = 0;
+ ScanState eState = SsStart;
+ OUStringBuffer sSymbolBuffer;
+ while ( nPos < rStr.getLength() && eState != SsStop )
+ {
+ sal_Unicode cToken = rStr[nPos++];
+ switch (eState)
+ {
+ case SsStart:
+ // Fetch any currency longer than one character and don't get
+ // confused later on by "E/" or other combinations of letters
+ // and meaningful symbols. Necessary for old automatic currency.
+ // #96158# But don't do it if we're starting a "[...]" section,
+ // for example a "[$...]" new currency symbol to not parse away
+ // "$U" (symbol) of "[$UYU]" (abbreviation).
+ if ( nCurrPos >= 0 && sCurString.getLength() > 1 &&
+ nPos-1 + sCurString.getLength() <= rStr.getLength() &&
+ (nPos <= 1 || rStr[nPos-2] != '[') )
+ {
+ OUString aTest = pChrCls->uppercase( rStr.copy( nPos-1, sCurString.getLength() ) );
+ if ( aTest == sCurString )
+ {
+ sSymbol = rStr.copy( --nPos, sCurString.getLength() );
+ nPos = nPos + sSymbol.getLength();
+ eType = NF_SYMBOLTYPE_STRING;
+ return eType;
+ }
+ }
+ switch (cToken)
+ {
+ case '#':
+ case '0':
+ case '?':
+ case '%':
+ case '@':
+ case '[':
+ case ']':
+ case ',':
+ case '.':
+ case '/':
+ case '\'':
+ case ' ':
+ case ':':
+ case '-':
+ eType = NF_SYMBOLTYPE_DEL;
+ sSymbolBuffer.append(OUStringChar(cToken));
+ eState = SsStop;
+ break;
+ case '*':
+ eType = NF_SYMBOLTYPE_STAR;
+ sSymbolBuffer.append(OUStringChar(cToken));
+ eState = SsGetStar;
+ break;
+ case '_':
+ eType = NF_SYMBOLTYPE_BLANK;
+ sSymbolBuffer.append(OUStringChar(cToken));
+ eState = SsGetBlank;
+ break;
+ case '"':
+ eType = NF_SYMBOLTYPE_STRING;
+ eState = SsGetString;
+ sSymbolBuffer.append(OUStringChar(cToken));
+ break;
+ case '\\':
+ eType = NF_SYMBOLTYPE_STRING;
+ eState = SsGetChar;
+ sSymbolBuffer.append(OUStringChar(cToken));
+ break;
+ case '$':
+ case '+':
+ case '(':
+ case ')':
+ eType = NF_SYMBOLTYPE_STRING;
+ eState = SsStop;
+ sSymbolBuffer.append(OUStringChar(cToken));
+ break;
+ default :
+ if (StringEqualsChar( pFormatter->GetNumDecimalSep(), cToken) ||
+ StringEqualsChar( pFormatter->GetNumThousandSep(), cToken) ||
+ StringEqualsChar( pFormatter->GetDateSep(), cToken) ||
+ StringEqualsChar( pLoc->getTimeSep(), cToken) ||
+ StringEqualsChar( pLoc->getTime100SecSep(), cToken))
+ {
+ // Another separator than pre-known ASCII
+ eType = NF_SYMBOLTYPE_DEL;
+ sSymbolBuffer.append(OUStringChar(cToken));
+ eState = SsStop;
+ }
+ else if ( pChrCls->isLetter( rStr, nPos-1 ) )
+ {
+ bool bFoundEnglish = false;
+ short nTmpType = GetKeyWord( rStr, nPos-1, bFoundEnglish);
+ if ( nTmpType )
+ {
+ bool bCurrency = false;
+ // "Automatic" currency may start with keyword,
+ // like "R" (Rand) and 'R' (era)
+ if ( nCurrPos >= 0 &&
+ nPos-1 + sCurString.getLength() <= rStr.getLength() &&
+ sCurString.startsWith( bFoundEnglish ? sEnglishKeyword[nTmpType] : sKeyword[nTmpType]))
+ {
+ OUString aTest = pChrCls->uppercase( rStr.copy( nPos-1, sCurString.getLength() ) );
+ if ( aTest == sCurString )
+ {
+ bCurrency = true;
+ }
+ }
+ if ( bCurrency )
+ {
+ eState = SsGetWord;
+ sSymbolBuffer.append(OUStringChar(cToken));
+ }
+ else
+ {
+ eType = nTmpType;
+ // The code to be advanced is the detected keyword,
+ // not necessarily the locale's keyword, but the
+ // symbol is to be the locale's keyword.
+ sal_Int32 nLen;
+ if (bFoundEnglish)
+ {
+ nLen = sEnglishKeyword[eType].getLength();
+ // Use the locale's General keyword name, not uppercase.
+ sSymbolBuffer = (eType == NF_KEY_GENERAL ? sNameStandardFormat : sKeyword[eType]);
+ }
+ else
+ {
+ nLen = sKeyword[eType].getLength();
+ // Preserve a locale's keyword's case as entered.
+ sSymbolBuffer = rStr.subView( nPos-1, nLen);
+ }
+ if ((eType == NF_KEY_E || IsAmbiguousE(eType)) && nPos < rStr.getLength())
+ {
+ sal_Unicode cNext = rStr[nPos];
+ switch ( cNext )
+ {
+ case '+' :
+ case '-' : // E+ E- combine to one symbol
+ sSymbolBuffer.append(OUStringChar(cNext));
+ eType = NF_KEY_E;
+ nPos++;
+ break;
+ case '0' :
+ case '#' : // scientific E without sign
+ eType = NF_KEY_E;
+ break;
+ }
+ }
+ nPos--;
+ nPos = nPos + nLen;
+ eState = SsStop;
+ }
+ }
+ else
+ {
+ eState = SsGetWord;
+ sSymbolBuffer.append(OUStringChar(cToken));
+ }
+ }
+ else
+ {
+ eType = NF_SYMBOLTYPE_STRING;
+ eState = SsStop;
+ sSymbolBuffer.append(OUStringChar(cToken));
+ }
+ break;
+ }
+ break;
+ case SsGetChar:
+ sSymbolBuffer.append(OUStringChar(cToken));
+ eState = SsStop;
+ break;
+ case SsGetString:
+ if (cToken == '"')
+ {
+ eState = SsStop;
+ }
+ sSymbolBuffer.append(OUStringChar(cToken));
+ break;
+ case SsGetWord:
+ if ( pChrCls->isLetter( rStr, nPos-1 ) )
+ {
+ bool bFoundEnglish = false;
+ short nTmpType = GetKeyWord( rStr, nPos-1, bFoundEnglish);
+ if ( nTmpType )
+ {
+ // beginning of keyword, stop scan and put back
+ eType = NF_SYMBOLTYPE_STRING;
+ eState = SsStop;
+ nPos--;
+ }
+ else
+ {
+ sSymbolBuffer.append(OUStringChar(cToken));
+ }
+ }
+ else
+ {
+ bool bDontStop = false;
+ sal_Unicode cNext;
+ switch (cToken)
+ {
+ case '/': // AM/PM, A/P
+ if (nPos < rStr.getLength())
+ {
+ cNext = rStr[nPos];
+ if ( cNext == 'P' || cNext == 'p' )
+ {
+ sal_Int32 nLen = sSymbolBuffer.getLength();
+ if ( 1 <= nLen &&
+ (sSymbolBuffer[0] == 'A' || sSymbolBuffer[0] == 'a') &&
+ (nLen == 1 ||
+ (nLen == 2 && (sSymbolBuffer[1] == 'M' || sSymbolBuffer[1] == 'm')
+ && (rStr[nPos + 1] == 'M' || rStr[nPos + 1] == 'm'))))
+ {
+ sSymbolBuffer.append(OUStringChar(cToken));
+ bDontStop = true;
+ }
+ }
+ }
+ break;
+ }
+ // anything not recognized will stop the scan
+ if (!bDontStop)
+ {
+ eState = SsStop;
+ nPos--;
+ eType = NF_SYMBOLTYPE_STRING;
+ }
+ }
+ break;
+ case SsGetStar:
+ eState = SsStop;
+ sSymbolBuffer.append(OUStringChar(cToken));
+ break;
+ case SsGetBlank:
+ eState = SsStop;
+ sSymbolBuffer.append(OUStringChar(cToken));
+ break;
+ default:
+ break;
+ } // of switch
+ } // of while
+ if (eState == SsGetWord)
+ {
+ eType = NF_SYMBOLTYPE_STRING;
+ }
+ sSymbol = sSymbolBuffer.makeStringAndClear();
+ return eType;
+}
+
+sal_Int32 ImpSvNumberformatScan::Symbol_Division(const OUString& rString)
+{
+ nCurrPos = -1;
+ // Do we have some sort of currency?
+ OUString sString = pFormatter->GetCharClass()->uppercase(rString);
+ sal_Int32 nCPos = 0;
+ while (nCPos >= 0 && nCPos < sString.getLength())
+ {
+ nCPos = sString.indexOf(GetCurString(),nCPos);
+ if (nCPos >= 0)
+ {
+ // In Quotes?
+ sal_Int32 nQ = SvNumberformat::GetQuoteEnd( sString, nCPos );
+ if ( nQ < 0 )
+ {
+ sal_Unicode c;
+ if ( nCPos == 0 ||
+ ((c = sString[nCPos-1]) != '"'
+ && c != '\\') ) // dm can be protected by "dm \d
+ {
+ nCurrPos = nCPos;
+ nCPos = -1;
+ }
+ else
+ {
+ nCPos++; // Continue search
+ }
+ }
+ else
+ {
+ nCPos = nQ + 1; // Continue search
+ }
+ }
+ }
+ nStringsCnt = 0;
+ bool bStar = false; // Is set on detecting '*'
+ Reset();
+
+ sal_Int32 nPos = 0;
+ const sal_Int32 nLen = rString.getLength();
+ while (nPos < nLen && nStringsCnt < NF_MAX_FORMAT_SYMBOLS)
+ {
+ nTypeArray[nStringsCnt] = Next_Symbol(rString, nPos, sStrArray[nStringsCnt]);
+ if (nTypeArray[nStringsCnt] == NF_SYMBOLTYPE_STAR)
+ { // Monitoring the '*'
+ if (bStar)
+ {
+ return nPos; // Error: double '*'
+ }
+ else
+ {
+ // Valid only if there is a character following, else we are
+ // at the end of a code that does not have a fill character
+ // (yet?).
+ if (sStrArray[nStringsCnt].getLength() < 2)
+ return nPos;
+ bStar = true;
+ }
+ }
+ nStringsCnt++;
+ }
+
+ return 0; // 0 => ok
+}
+
+void ImpSvNumberformatScan::SkipStrings(sal_uInt16& i, sal_Int32& nPos) const
+{
+ while (i < nStringsCnt && ( nTypeArray[i] == NF_SYMBOLTYPE_STRING
+ || nTypeArray[i] == NF_SYMBOLTYPE_BLANK
+ || nTypeArray[i] == NF_SYMBOLTYPE_STAR) )
+ {
+ nPos = nPos + sStrArray[i].getLength();
+ i++;
+ }
+}
+
+sal_uInt16 ImpSvNumberformatScan::PreviousKeyword(sal_uInt16 i) const
+{
+ short res = 0;
+ if (i > 0 && i < nStringsCnt)
+ {
+ i--;
+ while (i > 0 && nTypeArray[i] <= 0)
+ {
+ i--;
+ }
+ if (nTypeArray[i] > 0)
+ {
+ res = nTypeArray[i];
+ }
+ }
+ return res;
+}
+
+sal_uInt16 ImpSvNumberformatScan::NextKeyword(sal_uInt16 i) const
+{
+ short res = 0;
+ if (i < nStringsCnt-1)
+ {
+ i++;
+ while (i < nStringsCnt-1 && nTypeArray[i] <= 0)
+ {
+ i++;
+ }
+ if (nTypeArray[i] > 0)
+ {
+ res = nTypeArray[i];
+ }
+ }
+ return res;
+}
+
+short ImpSvNumberformatScan::PreviousType( sal_uInt16 i ) const
+{
+ if ( i > 0 && i < nStringsCnt )
+ {
+ do
+ {
+ i--;
+ }
+ while ( i > 0 && nTypeArray[i] == NF_SYMBOLTYPE_EMPTY );
+ return nTypeArray[i];
+ }
+ return 0;
+}
+
+sal_Unicode ImpSvNumberformatScan::PreviousChar(sal_uInt16 i) const
+{
+ sal_Unicode res = ' ';
+ if (i > 0 && i < nStringsCnt)
+ {
+ i--;
+ while (i > 0 &&
+ ( nTypeArray[i] == NF_SYMBOLTYPE_EMPTY ||
+ nTypeArray[i] == NF_SYMBOLTYPE_STRING ||
+ nTypeArray[i] == NF_SYMBOLTYPE_STAR ||
+ nTypeArray[i] == NF_SYMBOLTYPE_BLANK ))
+ {
+ i--;
+ }
+ if (sStrArray[i].getLength() > 0)
+ {
+ res = sStrArray[i][sStrArray[i].getLength()-1];
+ }
+ }
+ return res;
+}
+
+sal_Unicode ImpSvNumberformatScan::NextChar(sal_uInt16 i) const
+{
+ sal_Unicode res = ' ';
+ if (i < nStringsCnt-1)
+ {
+ i++;
+ while (i < nStringsCnt-1 &&
+ ( nTypeArray[i] == NF_SYMBOLTYPE_EMPTY ||
+ nTypeArray[i] == NF_SYMBOLTYPE_STRING ||
+ nTypeArray[i] == NF_SYMBOLTYPE_STAR ||
+ nTypeArray[i] == NF_SYMBOLTYPE_BLANK))
+ {
+ i++;
+ }
+ if (sStrArray[i].getLength() > 0)
+ {
+ res = sStrArray[i][0];
+ }
+ }
+ return res;
+}
+
+bool ImpSvNumberformatScan::IsLastBlankBeforeFrac(sal_uInt16 i) const
+{
+ bool res = true;
+ if (i < nStringsCnt-1)
+ {
+ bool bStop = false;
+ i++;
+ while (i < nStringsCnt-1 && !bStop)
+ {
+ i++;
+ if ( nTypeArray[i] == NF_SYMBOLTYPE_DEL &&
+ sStrArray[i][0] == '/')
+ {
+ bStop = true;
+ }
+ else if ( ( nTypeArray[i] == NF_SYMBOLTYPE_DEL &&
+ sStrArray[i][0] == ' ') ||
+ nTypeArray[i] == NF_SYMBOLTYPE_STRING ) // integer/fraction delimiter can also be a string
+ {
+ res = false;
+ }
+ }
+ if (!bStop) // no '/'{
+ {
+ res = false;
+ }
+ }
+ else
+ {
+ res = false; // no '/' any more
+ }
+ return res;
+}
+
+void ImpSvNumberformatScan::Reset()
+{
+ nStringsCnt = 0;
+ nResultStringsCnt = 0;
+ eScannedType = SvNumFormatType::UNDEFINED;
+ bExp = false;
+ bThousand = false;
+ nThousand = 0;
+ bDecSep = false;
+ nDecPos = sal_uInt16(-1);
+ nExpPos = sal_uInt16(-1);
+ nBlankPos = sal_uInt16(-1);
+ nCntPre = 0;
+ nCntPost = 0;
+ nCntExp = 0;
+ bFrac = false;
+ bBlank = false;
+ nNatNumModifier = 0;
+}
+
+bool ImpSvNumberformatScan::Is100SecZero( sal_uInt16 i, bool bHadDecSep ) const
+{
+ sal_uInt16 nIndexPre = PreviousKeyword( i );
+ return (nIndexPre == NF_KEY_S || nIndexPre == NF_KEY_SS) &&
+ (bHadDecSep ||
+ ( i > 0 && nTypeArray[i-1] == NF_SYMBOLTYPE_STRING));
+ // SS"any"00 take "any" as a valid decimal separator
+}
+
+sal_Int32 ImpSvNumberformatScan::ScanType()
+{
+ const LocaleDataWrapper* pLoc = pFormatter->GetLocaleData();
+
+ sal_Int32 nPos = 0;
+ sal_uInt16 i = 0;
+ SvNumFormatType eNewType;
+ bool bMatchBracket = false;
+ bool bHaveGeneral = false; // if General/Standard encountered
+ bool bIsTimeDetected =false; // hour or second found in format
+ bool bHaveMinute = false;
+
+ SkipStrings(i, nPos);
+ while (i < nStringsCnt)
+ {
+ if (nTypeArray[i] > 0)
+ { // keyword
+ sal_uInt16 nIndexPre;
+ sal_uInt16 nIndexNex;
+
+ switch (nTypeArray[i])
+ {
+ case NF_KEY_E: // E
+ eNewType = SvNumFormatType::SCIENTIFIC;
+ break;
+ case NF_KEY_H: // H
+ case NF_KEY_HH: // HH
+ bIsTimeDetected = true;
+ [[fallthrough]];
+ case NF_KEY_S: // S
+ case NF_KEY_SS: // SS
+ if ( !bHaveMinute )
+ bIsTimeDetected = true;
+ [[fallthrough]];
+ case NF_KEY_AMPM: // AM,A,PM,P
+ case NF_KEY_AP:
+ eNewType = SvNumFormatType::TIME;
+ break;
+ case NF_KEY_M: // M
+ case NF_KEY_MM: // MM
+ case NF_KEY_MI: // M minute detected in Finnish
+ case NF_KEY_MMI: // MM
+ /* Minute or month.
+ Minute if one of:
+ * preceded by time keyword H (ignoring separators)
+ * followed by time keyword S (ignoring separators)
+ * H or S was detected and this is the first M following
+ * preceded by '[' amount bracket
+ Else month.
+ That are the Excel rules. BUT, we break it because certainly
+ in something like {HH YYYY-MM-DD} the MM is NOT meant to be
+ minute, so not if MM is between YY and DD or DD and YY.
+ Actually not if any date specific keyword followed a time
+ setting keyword.
+ */
+ nIndexPre = PreviousKeyword(i);
+ nIndexNex = NextKeyword(i);
+ if (nIndexPre == NF_KEY_H || // H
+ nIndexPre == NF_KEY_HH || // HH
+ nIndexNex == NF_KEY_S || // S
+ nIndexNex == NF_KEY_SS || // SS
+ bIsTimeDetected || // tdf#101147
+ PreviousChar(i) == '[' ) // [M
+ {
+ eNewType = SvNumFormatType::TIME;
+ if ( nTypeArray[i] == NF_KEY_M || nTypeArray[i] == NF_KEY_MM )
+ {
+ nTypeArray[i] -= 2; // 6 -> 4, 7 -> 5
+ }
+ bIsTimeDetected = false; // next M should be month
+ bHaveMinute = true;
+ }
+ else
+ {
+ eNewType = SvNumFormatType::DATE;
+ if ( nTypeArray[i] == NF_KEY_MI || nTypeArray[i] == NF_KEY_MMI )
+ { // follow resolution of tdf#33689 for Finnish
+ nTypeArray[i] += 2; // 4 -> 6, 5 -> 7
+ }
+ }
+ break;
+ case NF_KEY_MMM: // MMM
+ case NF_KEY_MMMM: // MMMM
+ case NF_KEY_MMMMM: // MMMMM
+ case NF_KEY_Q: // Q
+ case NF_KEY_QQ: // QQ
+ case NF_KEY_D: // D
+ case NF_KEY_DD: // DD
+ case NF_KEY_DDD: // DDD
+ case NF_KEY_DDDD: // DDDD
+ case NF_KEY_YY: // YY
+ case NF_KEY_YYYY: // YYYY
+ case NF_KEY_NN: // NN
+ case NF_KEY_NNN: // NNN
+ case NF_KEY_NNNN: // NNNN
+ case NF_KEY_WW : // WW
+ case NF_KEY_AAA : // AAA
+ case NF_KEY_AAAA : // AAAA
+ case NF_KEY_EC : // E
+ case NF_KEY_EEC : // EE
+ case NF_KEY_G : // G
+ case NF_KEY_GG : // GG
+ case NF_KEY_GGG : // GGG
+ case NF_KEY_R : // R
+ case NF_KEY_RR : // RR
+ eNewType = SvNumFormatType::DATE;
+ bIsTimeDetected = false;
+ break;
+ case NF_KEY_CCC: // CCC
+ eNewType = SvNumFormatType::CURRENCY;
+ break;
+ case NF_KEY_BOOLEAN: // BOOLEAN
+ eNewType = SvNumFormatType::LOGICAL;
+ break;
+ case NF_KEY_GENERAL: // General
+ eNewType = SvNumFormatType::NUMBER;
+ bHaveGeneral = true;
+ break;
+ default:
+ eNewType = SvNumFormatType::UNDEFINED;
+ break;
+ }
+ }
+ else
+ { // control character
+ switch ( sStrArray[i][0] )
+ {
+ case '#':
+ case '?':
+ eNewType = SvNumFormatType::NUMBER;
+ break;
+ case '0':
+ if ( eScannedType & SvNumFormatType::TIME )
+ {
+ if ( Is100SecZero( i, bDecSep ) )
+ {
+ bDecSep = true; // subsequent 0's
+ eNewType = SvNumFormatType::TIME;
+ }
+ else
+ {
+ return nPos; // Error
+ }
+ }
+ else
+ {
+ eNewType = SvNumFormatType::NUMBER;
+ }
+ break;
+ case '%':
+ eNewType = SvNumFormatType::PERCENT;
+ break;
+ case '/':
+ eNewType = SvNumFormatType::FRACTION;
+ break;
+ case '[':
+ if ( i < nStringsCnt-1 &&
+ nTypeArray[i+1] == NF_SYMBOLTYPE_STRING &&
+ sStrArray[i+1][0] == '$' )
+ {
+ eNewType = SvNumFormatType::CURRENCY;
+ bMatchBracket = true;
+ }
+ else if ( i < nStringsCnt-1 &&
+ nTypeArray[i+1] == NF_SYMBOLTYPE_STRING &&
+ sStrArray[i+1][0] == '~' )
+ {
+ eNewType = SvNumFormatType::DATE;
+ bMatchBracket = true;
+ }
+ else
+ {
+ sal_uInt16 nIndexNex = NextKeyword(i);
+ if (nIndexNex == NF_KEY_H || // H
+ nIndexNex == NF_KEY_HH || // HH
+ nIndexNex == NF_KEY_M || // M
+ nIndexNex == NF_KEY_MM || // MM
+ nIndexNex == NF_KEY_S || // S
+ nIndexNex == NF_KEY_SS ) // SS
+ eNewType = SvNumFormatType::TIME;
+ else
+ {
+ return nPos; // Error
+ }
+ }
+ break;
+ case '@':
+ eNewType = SvNumFormatType::TEXT;
+ break;
+ default:
+ // Separator for SS,0
+ if ((eScannedType & SvNumFormatType::TIME)
+ && 0 < i && (nTypeArray[i-1] == NF_KEY_S || nTypeArray[i-1] == NF_KEY_SS))
+ {
+ // For ISO 8601 only YYYY-MM-DD"T"HH:MM:SS,0 accept both
+ // ',' and '.' regardless of locale's separator, and only
+ // those.
+ // XXX NOTE: this catches only known separators of
+ // NF_SYMBOLTYPE_DEL as all NF_SYMBOLTYPE_STRING are
+ // skipped during the loop. Meant to error out if the
+ // Time100SecSep or decimal separator differ and were used.
+ if ((eScannedType & SvNumFormatType::DATE)
+ && 11 <= i && i < nStringsCnt-1
+ && (nTypeArray[i-6] == NF_SYMBOLTYPE_STRING
+ && (sStrArray[i-6] == "\"T\"" || sStrArray[i-6] == "\\T"
+ || sStrArray[i-6] == "T"))
+ && (nTypeArray[i-11] == NF_KEY_YYYY)
+ && (nTypeArray[i-9] == NF_KEY_M || nTypeArray[i-9] == NF_KEY_MM)
+ && (nTypeArray[i-7] == NF_KEY_D || nTypeArray[i-7] == NF_KEY_DD)
+ && (nTypeArray[i-5] == NF_KEY_H || nTypeArray[i-5] == NF_KEY_HH)
+ && (nTypeArray[i-3] == NF_KEY_MI || nTypeArray[i-3] == NF_KEY_MMI)
+ && (nTypeArray[i+1] == NF_SYMBOLTYPE_DEL && sStrArray[i+1][0] == '0'))
+
+ {
+ if (sStrArray[i].getLength() == 1 && (sStrArray[i][0] == ',' || sStrArray[i][0] == '.'))
+ bDecSep = true;
+ else
+ return nPos; // Error
+ }
+ else if (pLoc->getTime100SecSep() == sStrArray[i])
+ bDecSep = true;
+ else if ( sStrArray[i][0] == ']' && i < nStringsCnt - 1 && pLoc->getTime100SecSep() == sStrArray[i+1] )
+ {
+ bDecSep = true;
+ i++;
+ }
+ }
+ eNewType = SvNumFormatType::UNDEFINED;
+ break;
+ }
+ }
+ if (eScannedType == SvNumFormatType::UNDEFINED)
+ {
+ eScannedType = eNewType;
+ }
+ else if (eScannedType == SvNumFormatType::TEXT || eNewType == SvNumFormatType::TEXT)
+ {
+ eScannedType = SvNumFormatType::TEXT; // Text always remains text
+ }
+ else if (eNewType == SvNumFormatType::UNDEFINED)
+ { // Remains as is
+ }
+ else if (eScannedType != eNewType)
+ {
+ switch (eScannedType)
+ {
+ case SvNumFormatType::DATE:
+ switch (eNewType)
+ {
+ case SvNumFormatType::TIME:
+ eScannedType = SvNumFormatType::DATETIME;
+ break;
+ case SvNumFormatType::FRACTION: // DD/MM
+ break;
+ default:
+ if (nCurrPos >= 0)
+ {
+ eScannedType = SvNumFormatType::UNDEFINED;
+ }
+ else if ( sStrArray[i] != pFormatter->GetDateSep() )
+ {
+ return nPos;
+ }
+ }
+ break;
+ case SvNumFormatType::TIME:
+ switch (eNewType)
+ {
+ case SvNumFormatType::DATE:
+ eScannedType = SvNumFormatType::DATETIME;
+ break;
+ case SvNumFormatType::FRACTION: // MM/SS
+ break;
+ default:
+ if (nCurrPos >= 0)
+ {
+ eScannedType = SvNumFormatType::UNDEFINED;
+ }
+ else if (pLoc->getTimeSep() != sStrArray[i])
+ {
+ return nPos;
+ }
+ break;
+ }
+ break;
+ case SvNumFormatType::DATETIME:
+ switch (eNewType)
+ {
+ case SvNumFormatType::TIME:
+ case SvNumFormatType::DATE:
+ break;
+ case SvNumFormatType::FRACTION: // DD/MM
+ break;
+ default:
+ if (nCurrPos >= 0)
+ {
+ eScannedType = SvNumFormatType::UNDEFINED;
+ }
+ else if ( pFormatter->GetDateSep() != sStrArray[i] &&
+ pLoc->getTimeSep() != sStrArray[i] )
+ {
+ return nPos;
+ }
+ }
+ break;
+ case SvNumFormatType::PERCENT:
+ switch (eNewType)
+ {
+ case SvNumFormatType::NUMBER: // Only number to percent
+ break;
+ default:
+ return nPos;
+ }
+ break;
+ case SvNumFormatType::SCIENTIFIC:
+ switch (eNewType)
+ {
+ case SvNumFormatType::NUMBER: // Only number to E
+ break;
+ default:
+ return nPos;
+ }
+ break;
+ case SvNumFormatType::NUMBER:
+ switch (eNewType)
+ {
+ case SvNumFormatType::SCIENTIFIC:
+ case SvNumFormatType::PERCENT:
+ case SvNumFormatType::FRACTION:
+ case SvNumFormatType::CURRENCY:
+ eScannedType = eNewType;
+ break;
+ default:
+ if (nCurrPos >= 0)
+ {
+ eScannedType = SvNumFormatType::UNDEFINED;
+ }
+ else
+ {
+ return nPos;
+ }
+ }
+ break;
+ case SvNumFormatType::FRACTION:
+ switch (eNewType)
+ {
+ case SvNumFormatType::NUMBER: // Only number to fraction
+ break;
+ default:
+ return nPos;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ nPos = nPos + sStrArray[i].getLength(); // Position of correction
+ i++;
+ if ( bMatchBracket )
+ { // no type detection inside of matching brackets if [$...], [~...]
+ while ( bMatchBracket && i < nStringsCnt )
+ {
+ if ( nTypeArray[i] == NF_SYMBOLTYPE_DEL
+ && sStrArray[i][0] == ']' )
+ {
+ bMatchBracket = false;
+ }
+ else
+ {
+ nTypeArray[i] = NF_SYMBOLTYPE_STRING;
+ }
+ nPos = nPos + sStrArray[i].getLength();
+ i++;
+ }
+ if ( bMatchBracket )
+ {
+ return nPos; // missing closing bracket at end of code
+ }
+ }
+ SkipStrings(i, nPos);
+ }
+
+ if ((eScannedType == SvNumFormatType::NUMBER ||
+ eScannedType == SvNumFormatType::UNDEFINED) &&
+ nCurrPos >= 0 && !bHaveGeneral)
+ {
+ eScannedType = SvNumFormatType::CURRENCY; // old "automatic" currency
+ }
+ if (eScannedType == SvNumFormatType::UNDEFINED)
+ {
+ eScannedType = SvNumFormatType::DEFINED;
+ }
+ return 0; // All is fine
+}
+
+bool ImpSvNumberformatScan::InsertSymbol( sal_uInt16 & nPos, svt::NfSymbolType eType, const OUString& rStr )
+{
+ if (nStringsCnt >= NF_MAX_FORMAT_SYMBOLS || nPos > nStringsCnt)
+ {
+ return false;
+ }
+ if (nPos > 0 && nTypeArray[nPos-1] == NF_SYMBOLTYPE_EMPTY)
+ {
+ --nPos; // reuse position
+ }
+ else
+ {
+ if (nStringsCnt >= NF_MAX_FORMAT_SYMBOLS - 1)
+ {
+ return false;
+ }
+ ++nStringsCnt;
+ for (size_t i = nStringsCnt; i > nPos; --i)
+ {
+ nTypeArray[i] = nTypeArray[i-1];
+ sStrArray[i] = sStrArray[i-1];
+ }
+ }
+ ++nResultStringsCnt;
+ nTypeArray[nPos] = static_cast<short>(eType);
+ sStrArray[nPos] = rStr;
+ return true;
+}
+
+int ImpSvNumberformatScan::FinalScanGetCalendar( sal_Int32& nPos, sal_uInt16& i,
+ sal_uInt16& rResultStringsCnt )
+{
+ if ( i < nStringsCnt-1 &&
+ sStrArray[i][0] == '[' &&
+ nTypeArray[i+1] == NF_SYMBOLTYPE_STRING &&
+ sStrArray[i+1][0] == '~' )
+ {
+ // [~calendarID]
+ nPos = nPos + sStrArray[i].getLength(); // [
+ nTypeArray[i] = NF_SYMBOLTYPE_CALDEL;
+ nPos = nPos + sStrArray[++i].getLength(); // ~
+ sStrArray[i-1] += sStrArray[i]; // [~
+ nTypeArray[i] = NF_SYMBOLTYPE_EMPTY;
+ rResultStringsCnt--;
+ if ( ++i >= nStringsCnt )
+ {
+ return -1; // error
+ }
+ nPos = nPos + sStrArray[i].getLength(); // calendarID
+ OUString& rStr = sStrArray[i];
+ nTypeArray[i] = NF_SYMBOLTYPE_CALENDAR; // convert
+ i++;
+ while ( i < nStringsCnt && sStrArray[i][0] != ']' )
+ {
+ nPos = nPos + sStrArray[i].getLength();
+ rStr += sStrArray[i];
+ nTypeArray[i] = NF_SYMBOLTYPE_EMPTY;
+ rResultStringsCnt--;
+ i++;
+ }
+ if ( rStr.getLength() && i < nStringsCnt &&
+ sStrArray[i][0] == ']' )
+ {
+ nTypeArray[i] = NF_SYMBOLTYPE_CALDEL;
+ nPos = nPos + sStrArray[i].getLength();
+ i++;
+ }
+ else
+ {
+ return -1; // error
+ }
+ return 1;
+ }
+ return 0;
+}
+
+bool ImpSvNumberformatScan::IsDateFragment( size_t nPos1, size_t nPos2 ) const
+{
+ return nPos2 - nPos1 == 2 && nTypeArray[nPos1+1] == NF_SYMBOLTYPE_DATESEP;
+}
+
+void ImpSvNumberformatScan::SwapArrayElements( size_t nPos1, size_t nPos2 )
+{
+ std::swap( nTypeArray[nPos1], nTypeArray[nPos2]);
+ std::swap( sStrArray[nPos1], sStrArray[nPos2]);
+}
+
+sal_Int32 ImpSvNumberformatScan::FinalScan( OUString& rString )
+{
+ const LocaleDataWrapper* pLoc = pFormatter->GetLocaleData();
+
+ // save values for convert mode
+ OUString sOldDecSep = pFormatter->GetNumDecimalSep();
+ OUString sOldThousandSep = pFormatter->GetNumThousandSep();
+ OUString sOldDateSep = pFormatter->GetDateSep();
+ OUString sOldTimeSep = pLoc->getTimeSep();
+ OUString sOldTime100SecSep= pLoc->getTime100SecSep();
+ OUString sOldCurSymbol = GetCurSymbol();
+ OUString sOldCurString = GetCurString();
+ sal_Unicode cOldKeyH = sKeyword[NF_KEY_H][0];
+ sal_Unicode cOldKeyMI = sKeyword[NF_KEY_MI][0];
+ sal_Unicode cOldKeyS = sKeyword[NF_KEY_S][0];
+ DateOrder eOldDateOrder = pLoc->getDateOrder();
+ sal_uInt16 nDayPos, nMonthPos, nYearPos;
+ nDayPos = nMonthPos = nYearPos = SAL_MAX_UINT16;
+
+ // If the group separator is a No-Break Space (French) continue with a
+ // normal space instead so queries on space work correctly.
+ // The same for Narrow No-Break Space just in case some locale uses it.
+ // The format string is adjusted to allow both.
+ // For output of the format code string the LocaleData characters are used.
+ if ( (sOldThousandSep[0] == cNoBreakSpace || sOldThousandSep[0] == cNarrowNoBreakSpace) &&
+ sOldThousandSep.getLength() == 1 )
+ {
+ sOldThousandSep = " ";
+ }
+ bool bNewDateOrder = false;
+ // change locale data et al
+ if (bConvertMode)
+ {
+ pFormatter->ChangeIntl(eNewLnge);
+ //! pointer may have changed
+ pLoc = pFormatter->GetLocaleData();
+ //! init new keywords
+ InitKeywords();
+ // Adapt date order to target locale, but Excel does not handle date
+ // particle re-ordering for the target locale when loading documents,
+ // though it does exchange separators, tdf#113889
+ bNewDateOrder = (mbConvertDateOrder && eOldDateOrder != pLoc->getDateOrder());
+ }
+ const CharClass* pChrCls = pFormatter->GetCharClass();
+
+ sal_Int32 nPos = 0; // error correction position
+ sal_uInt16 i = 0; // symbol loop counter
+ sal_uInt16 nCounter = 0; // counts digits
+ nResultStringsCnt = nStringsCnt; // counts remaining symbols
+ bDecSep = false; // reset in case already used in TypeCheck
+ bool bThaiT = false; // Thai T NatNum modifier present
+ bool bTimePart = false;
+ bool bDenomin = false; // Set when reading end of denominator
+
+ switch (eScannedType)
+ {
+ case SvNumFormatType::TEXT:
+ case SvNumFormatType::DEFINED:
+ while (i < nStringsCnt)
+ {
+ switch (nTypeArray[i])
+ {
+ case NF_SYMBOLTYPE_BLANK:
+ case NF_SYMBOLTYPE_STAR:
+ break;
+ case NF_KEY_GENERAL : // #77026# "General" is the same as "@"
+ break;
+ default:
+ if ( nTypeArray[i] != NF_SYMBOLTYPE_DEL ||
+ sStrArray[i][0] != '@' )
+ {
+ nTypeArray[i] = NF_SYMBOLTYPE_STRING;
+ }
+ break;
+ }
+ nPos = nPos + sStrArray[i].getLength();
+ i++;
+ } // of while
+ break;
+
+ case SvNumFormatType::NUMBER:
+ case SvNumFormatType::PERCENT:
+ case SvNumFormatType::CURRENCY:
+ case SvNumFormatType::SCIENTIFIC:
+ case SvNumFormatType::FRACTION:
+ while (i < nStringsCnt)
+ {
+ // TODO: rechecking eScannedType is unnecessary.
+ // This switch-case is for eScannedType == SvNumFormatType::FRACTION anyway
+ if (eScannedType == SvNumFormatType::FRACTION && // special case
+ nTypeArray[i] == NF_SYMBOLTYPE_DEL && // # ### #/#
+ StringEqualsChar( sOldThousandSep, ' ' ) && // e.g. France or Sweden
+ StringEqualsChar( sStrArray[i], ' ' ) &&
+ !bFrac &&
+ IsLastBlankBeforeFrac(i) )
+ {
+ nTypeArray[i] = NF_SYMBOLTYPE_STRING; // del->string
+ } // No thousands marker
+
+ if (nTypeArray[i] == NF_SYMBOLTYPE_BLANK ||
+ nTypeArray[i] == NF_SYMBOLTYPE_STAR ||
+ nTypeArray[i] == NF_KEY_CCC || // CCC
+ nTypeArray[i] == NF_KEY_GENERAL ) // Standard
+ {
+ if (nTypeArray[i] == NF_KEY_GENERAL)
+ {
+ nThousand = FLAG_STANDARD_IN_FORMAT;
+ if ( bConvertMode )
+ {
+ sStrArray[i] = sNameStandardFormat;
+ }
+ }
+ nPos = nPos + sStrArray[i].getLength();
+ i++;
+ }
+ else if (nTypeArray[i] == NF_SYMBOLTYPE_STRING || // No Strings or
+ nTypeArray[i] > 0) // Keywords
+ {
+ if (eScannedType == SvNumFormatType::SCIENTIFIC &&
+ nTypeArray[i] == NF_KEY_E) // E+
+ {
+ if (bExp) // Double
+ {
+ return nPos;
+ }
+ bExp = true;
+ nExpPos = i;
+ if (bDecSep)
+ {
+ nCntPost = nCounter;
+ }
+ else
+ {
+ nCntPre = nCounter;
+ }
+ nCounter = 0;
+ nTypeArray[i] = NF_SYMBOLTYPE_EXP;
+ }
+ else if (eScannedType == SvNumFormatType::FRACTION &&
+ (sStrArray[i][0] == ' ' || ( nTypeArray[i] == NF_SYMBOLTYPE_STRING && (sStrArray[i][0] < '0' || sStrArray[i][0] > '9') ) ) )
+ {
+ if (!bBlank && !bFrac) // Not double or after a /
+ {
+ if (bDecSep && nCounter > 0) // Decimal places
+ {
+ return nPos; // Error
+ }
+ if (sStrArray[i][0] == ' ' || nCounter > 0 ) // treat string as integer/fraction delimiter only if there is integer
+ {
+ bBlank = true;
+ nBlankPos = i;
+ nCntPre = nCounter;
+ nCounter = 0;
+ nTypeArray[i] = NF_SYMBOLTYPE_FRACBLANK;
+ }
+ }
+ else if ( sStrArray[i][0] == ' ' )
+ nTypeArray[i] = NF_SYMBOLTYPE_FRACBLANK;
+ else if ( bFrac && ( nCounter > 0 ) )
+ bDenomin = true; // following elements are no more part of denominator
+ }
+ else if (nTypeArray[i] == NF_KEY_THAI_T)
+ {
+ bThaiT = true;
+ sStrArray[i] = sKeyword[nTypeArray[i]];
+ }
+ else if (sStrArray[i][0] >= '0' &&
+ sStrArray[i][0] <= '9' && !bDenomin) // denominator was not yet found
+ {
+ OUString sDiv;
+ sal_uInt16 j = i;
+ while(j < nStringsCnt && sStrArray[j][0] >= '0' && sStrArray[j][0] <= '9')
+ {
+ sDiv += sStrArray[j++];
+ }
+ assert(j > 0 && "if i is 0, first iteration through loop is guaranteed by surrounding if condition");
+ if (std::u16string_view(OUString::number(sDiv.toInt32())) == sDiv)
+ {
+ // Found a Divisor
+ while (i < j)
+ {
+ nTypeArray[i++] = NF_SYMBOLTYPE_FRAC_FDIV;
+ }
+ i = j - 1; // Stop the loop
+ if (nCntPost)
+ {
+ nCounter = nCntPost;
+ }
+ else if (nCntPre)
+ {
+ nCounter = nCntPre;
+ }
+ // don't artificially increment nCntPre for forced denominator
+ if ( ( eScannedType != SvNumFormatType::FRACTION ) && (!nCntPre) )
+ {
+ nCntPre++;
+ }
+ if ( bFrac )
+ bDenomin = true; // next content should be treated as outside denominator
+ }
+ }
+ else
+ {
+ if ( bFrac && ( nCounter > 0 ) )
+ bDenomin = true; // next content should be treated as outside denominator
+ nTypeArray[i] = NF_SYMBOLTYPE_STRING;
+ }
+ nPos = nPos + sStrArray[i].getLength();
+ i++;
+ }
+ else if (nTypeArray[i] == NF_SYMBOLTYPE_DEL)
+ {
+ sal_Unicode cHere = sStrArray[i][0];
+ sal_Unicode cSaved = cHere;
+ // Handle not pre-known separators in switch.
+ sal_Unicode cSimplified;
+ if (StringEqualsChar( pFormatter->GetNumThousandSep(), cHere))
+ {
+ cSimplified = ',';
+ }
+ else if (StringEqualsChar( pFormatter->GetNumDecimalSep(), cHere))
+ {
+ cSimplified = '.';
+ }
+ else
+ {
+ cSimplified = cHere;
+ }
+
+ OUString& rStr = sStrArray[i];
+
+ switch ( cSimplified )
+ {
+ case '#':
+ case '0':
+ case '?':
+ if (nThousand > 0) // #... #
+ {
+ return nPos; // Error
+ }
+ if ( !bDenomin )
+ {
+ nTypeArray[i] = NF_SYMBOLTYPE_DIGIT;
+ nPos = nPos + rStr.getLength();
+ i++;
+ nCounter++;
+ while (i < nStringsCnt &&
+ (sStrArray[i][0] == '#' ||
+ sStrArray[i][0] == '0' ||
+ sStrArray[i][0] == '?'))
+ {
+ nTypeArray[i] = NF_SYMBOLTYPE_DIGIT;
+ nPos = nPos + sStrArray[i].getLength();
+ nCounter++;
+ i++;
+ }
+ }
+ else // after denominator, treat any character as text
+ {
+ nTypeArray[i] = NF_SYMBOLTYPE_STRING;
+ nPos = nPos + sStrArray[i].getLength();
+ }
+ break;
+ case '-':
+ if ( bDecSep && nDecPos+1 == i &&
+ nTypeArray[nDecPos] == NF_SYMBOLTYPE_DECSEP )
+ {
+ // "0.--"
+ nTypeArray[i] = NF_SYMBOLTYPE_DIGIT;
+ nPos = nPos + rStr.getLength();
+ i++;
+ nCounter++;
+ while (i < nStringsCnt &&
+ (sStrArray[i][0] == '-') )
+ {
+ // If more than two dashes are present in
+ // currency formats the last dash will be
+ // interpreted literally as a minus sign.
+ // Has to be this ugly. Period.
+ if ( eScannedType == SvNumFormatType::CURRENCY
+ && rStr.getLength() >= 2 &&
+ (i == nStringsCnt-1 ||
+ sStrArray[i+1][0] != '-') )
+ {
+ break;
+ }
+ rStr += sStrArray[i];
+ nPos = nPos + sStrArray[i].getLength();
+ nTypeArray[i] = NF_SYMBOLTYPE_EMPTY;
+ nResultStringsCnt--;
+ nCounter++;
+ i++;
+ }
+ }
+ else
+ {
+ nTypeArray[i] = NF_SYMBOLTYPE_STRING;
+ nPos = nPos + sStrArray[i].getLength();
+ i++;
+ }
+ break;
+ case '.':
+ case ',':
+ case '\'':
+ case ' ':
+ if ( StringEqualsChar( sOldThousandSep, cSaved ) )
+ {
+ // previous char with skip empty
+ sal_Unicode cPre = PreviousChar(i);
+ sal_Unicode cNext;
+ if (bExp || bBlank || bFrac)
+ {
+ // after E, / or ' '
+ if ( !StringEqualsChar( sOldThousandSep, ' ' ) )
+ {
+ nPos = nPos + sStrArray[i].getLength();
+ nTypeArray[i] = NF_SYMBOLTYPE_EMPTY;
+ nResultStringsCnt--;
+ i++; // eat it
+ }
+ else
+ {
+ nTypeArray[i] = NF_SYMBOLTYPE_STRING;
+ if ( bFrac && (nCounter > 0) )
+ bDenomin = true; // end of denominator
+ }
+ }
+ else if (i > 0 && i < nStringsCnt-1 &&
+ (cPre == '#' || cPre == '0' || cPre == '?') &&
+ ((cNext = NextChar(i)) == '#' || cNext == '0' || cNext == '?')) // #,#
+ {
+ nPos = nPos + sStrArray[i].getLength();
+ if (!bThousand) // only once
+ {
+ bThousand = true;
+ }
+ // Eat it, will be reinserted at proper grouping positions further down.
+ nTypeArray[i] = NF_SYMBOLTYPE_EMPTY;
+ nResultStringsCnt--;
+ i++;
+ }
+ else if (i > 0 && (cPre == '#' || cPre == '0' || cPre == '?')
+ && PreviousType(i) == NF_SYMBOLTYPE_DIGIT
+ && nThousand < FLAG_STANDARD_IN_FORMAT )
+ { // #,,,,
+ if ( StringEqualsChar( sOldThousandSep, ' ' ) )
+ {
+ // strange, those French...
+ bool bFirst = true;
+ // set a hard No-Break Space or ConvertMode
+ const OUString& rSepF = pFormatter->GetNumThousandSep();
+ while ( i < nStringsCnt &&
+ sStrArray[i] == sOldThousandSep &&
+ StringEqualsChar( sOldThousandSep, NextChar(i) ) )
+ { // last was a space or another space
+ // is following => separator
+ nPos = nPos + sStrArray[i].getLength();
+ if ( bFirst )
+ {
+ bFirst = false;
+ rStr = rSepF;
+ nTypeArray[i] = NF_SYMBOLTYPE_THSEP;
+ }
+ else
+ {
+ rStr += rSepF;
+ nTypeArray[i] = NF_SYMBOLTYPE_EMPTY;
+ nResultStringsCnt--;
+ }
+ nThousand++;
+ i++;
+ }
+ if ( i < nStringsCnt-1 &&
+ sStrArray[i] == sOldThousandSep )
+ {
+ // something following last space
+ // => space if currency contained,
+ // else separator
+ nPos = nPos + sStrArray[i].getLength();
+ if ( (nPos <= nCurrPos &&
+ nCurrPos < nPos + sStrArray[i+1].getLength()) ||
+ nTypeArray[i+1] == NF_KEY_CCC ||
+ (i < nStringsCnt-2 &&
+ sStrArray[i+1][0] == '[' &&
+ sStrArray[i+2][0] == '$') )
+ {
+ nTypeArray[i] = NF_SYMBOLTYPE_STRING;
+ }
+ else
+ {
+ if ( bFirst )
+ {
+ rStr = rSepF;
+ nTypeArray[i] = NF_SYMBOLTYPE_THSEP;
+ }
+ else
+ {
+ rStr += rSepF;
+ nTypeArray[i] = NF_SYMBOLTYPE_EMPTY;
+ nResultStringsCnt--;
+ }
+ nThousand++;
+ }
+ i++;
+ }
+ }
+ else
+ {
+ do
+ {
+ nThousand++;
+ nTypeArray[i] = NF_SYMBOLTYPE_THSEP;
+ nPos = nPos + sStrArray[i].getLength();
+ sStrArray[i] = pFormatter->GetNumThousandSep();
+ i++;
+ }
+ while (i < nStringsCnt && sStrArray[i] == sOldThousandSep);
+ }
+ }
+ else // any grsep
+ {
+ nTypeArray[i] = NF_SYMBOLTYPE_STRING;
+ nPos = nPos + rStr.getLength();
+ i++;
+ while ( i < nStringsCnt && sStrArray[i] == sOldThousandSep )
+ {
+ rStr += sStrArray[i];
+ nPos = nPos + sStrArray[i].getLength();
+ nTypeArray[i] = NF_SYMBOLTYPE_EMPTY;
+ nResultStringsCnt--;
+ i++;
+ }
+ }
+ }
+ else if ( StringEqualsChar( sOldDecSep, cSaved ) )
+ {
+ if (bBlank || bFrac) // . behind / or ' '
+ {
+ return nPos; // error
+ }
+ else if (bExp) // behind E
+ {
+ nPos = nPos + sStrArray[i].getLength();
+ nTypeArray[i] = NF_SYMBOLTYPE_EMPTY;
+ nResultStringsCnt--;
+ i++; // eat it
+ }
+ else if (bDecSep) // any .
+ {
+ nTypeArray[i] = NF_SYMBOLTYPE_STRING;
+ nPos = nPos + rStr.getLength();
+ i++;
+ while ( i < nStringsCnt && sStrArray[i] == sOldDecSep )
+ {
+ rStr += sStrArray[i];
+ nPos = nPos + sStrArray[i].getLength();
+ nTypeArray[i] = NF_SYMBOLTYPE_EMPTY;
+ nResultStringsCnt--;
+ i++;
+ }
+ }
+ else
+ {
+ nPos = nPos + sStrArray[i].getLength();
+ nTypeArray[i] = NF_SYMBOLTYPE_DECSEP;
+ sStrArray[i] = pFormatter->GetNumDecimalSep();
+ bDecSep = true;
+ nDecPos = i;
+ nCntPre = nCounter;
+ nCounter = 0;
+
+ i++;
+ }
+ } // of else = DecSep
+ else // . without meaning
+ {
+ if (cSaved == ' ' &&
+ eScannedType == SvNumFormatType::FRACTION &&
+ StringEqualsChar( sStrArray[i], ' ' ) )
+ {
+ if (!bBlank && !bFrac) // no dups
+ { // or behind /
+ if (bDecSep && nCounter > 0) // dec.
+ {
+ return nPos; // error
+ }
+ bBlank = true;
+ nBlankPos = i;
+ nCntPre = nCounter;
+ nCounter = 0;
+ }
+ if ( bFrac && (nCounter > 0) )
+ bDenomin = true; // next content is not part of denominator
+ nTypeArray[i] = NF_SYMBOLTYPE_STRING;
+ nPos = nPos + sStrArray[i].getLength();
+ }
+ else
+ {
+ nTypeArray[i] = NF_SYMBOLTYPE_STRING;
+ if ( bFrac && (nCounter > 0) )
+ bDenomin = true; // next content is not part of denominator
+ nPos = nPos + rStr.getLength();
+ i++;
+ while (i < nStringsCnt && StringEqualsChar( sStrArray[i], cSaved ) )
+ {
+ rStr += sStrArray[i];
+ nPos = nPos + sStrArray[i].getLength();
+ nTypeArray[i] = NF_SYMBOLTYPE_EMPTY;
+ nResultStringsCnt--;
+ i++;
+ }
+ }
+ }
+ break;
+ case '/':
+ if (eScannedType == SvNumFormatType::FRACTION)
+ {
+ if ( i == 0 ||
+ (nTypeArray[i-1] != NF_SYMBOLTYPE_DIGIT &&
+ nTypeArray[i-1] != NF_SYMBOLTYPE_EMPTY) )
+ {
+ return nPos ? nPos : 1; // /? not allowed
+ }
+ else if (!bFrac || (bDecSep && nCounter > 0))
+ {
+ bFrac = true;
+ nCntPost = nCounter;
+ nCounter = 0;
+ nTypeArray[i] = NF_SYMBOLTYPE_FRAC;
+ nPos = nPos + sStrArray[i].getLength();
+ i++;
+ }
+ else // / double or in , in the denominator
+ {
+ return nPos; // Error
+ }
+ }
+ else
+ {
+ nTypeArray[i] = NF_SYMBOLTYPE_STRING;
+ nPos = nPos + sStrArray[i].getLength();
+ i++;
+ }
+ break;
+ case '[' :
+ if ( eScannedType == SvNumFormatType::CURRENCY &&
+ i < nStringsCnt-1 &&
+ nTypeArray[i+1] == NF_SYMBOLTYPE_STRING &&
+ sStrArray[i+1][0] == '$' )
+ {
+ // [$DM-xxx]
+ nPos = nPos + sStrArray[i].getLength(); // [
+ nTypeArray[i] = NF_SYMBOLTYPE_CURRDEL;
+ nPos = nPos + sStrArray[++i].getLength(); // $
+ sStrArray[i-1] += sStrArray[i]; // [$
+ nTypeArray[i] = NF_SYMBOLTYPE_EMPTY;
+ nResultStringsCnt--;
+ if ( ++i >= nStringsCnt )
+ {
+ return nPos; // Error
+ }
+ nPos = nPos + sStrArray[i].getLength(); // DM
+ OUString* pStr = &sStrArray[i];
+ nTypeArray[i] = NF_SYMBOLTYPE_CURRENCY; // convert
+ bool bHadDash = false;
+ i++;
+ while ( i < nStringsCnt && sStrArray[i][0] != ']' )
+ {
+ nPos = nPos + sStrArray[i].getLength();
+ if ( bHadDash )
+ {
+ *pStr += sStrArray[i];
+ nTypeArray[i] = NF_SYMBOLTYPE_EMPTY;
+ nResultStringsCnt--;
+ }
+ else
+ {
+ if ( sStrArray[i][0] == '-' )
+ {
+ bHadDash = true;
+ pStr = &sStrArray[i];
+ nTypeArray[i] = NF_SYMBOLTYPE_CURREXT;
+ }
+ else
+ {
+ *pStr += sStrArray[i];
+ nTypeArray[i] = NF_SYMBOLTYPE_EMPTY;
+ nResultStringsCnt--;
+ }
+ }
+ i++;
+ }
+ if ( rStr.getLength() && i < nStringsCnt && sStrArray[i][0] == ']' )
+ {
+ nTypeArray[i] = NF_SYMBOLTYPE_CURRDEL;
+ nPos = nPos + sStrArray[i].getLength();
+ i++;
+ }
+ else
+ {
+ return nPos; // Error
+ }
+ }
+ else
+ {
+ nTypeArray[i] = NF_SYMBOLTYPE_STRING;
+ nPos = nPos + sStrArray[i].getLength();
+ i++;
+ }
+ break;
+ default: // Other Dels
+ if (eScannedType == SvNumFormatType::PERCENT && cHere == '%')
+ {
+ nTypeArray[i] = NF_SYMBOLTYPE_PERCENT;
+ }
+ else
+ {
+ nTypeArray[i] = NF_SYMBOLTYPE_STRING;
+ }
+ nPos = nPos + sStrArray[i].getLength();
+ i++;
+ break;
+ } // of switch (Del)
+ } // of else Del
+ else
+ {
+ SAL_WARN( "svl.numbers", "unknown NF_SYMBOLTYPE_..." );
+ nPos = nPos + sStrArray[i].getLength();
+ i++;
+ }
+ } // of while
+ if (eScannedType == SvNumFormatType::FRACTION)
+ {
+ if (bFrac)
+ {
+ nCntExp = nCounter;
+ }
+ else if (bBlank)
+ {
+ nCntPost = nCounter;
+ }
+ else
+ {
+ nCntPre = nCounter;
+ }
+ }
+ else
+ {
+ if (bExp)
+ {
+ nCntExp = nCounter;
+ }
+ else if (bDecSep)
+ {
+ nCntPost = nCounter;
+ }
+ else
+ {
+ nCntPre = nCounter;
+ }
+ }
+ if (bThousand) // Expansion of grouping separators
+ {
+ sal_uInt16 nMaxPos;
+ if (bFrac)
+ {
+ if (bBlank)
+ {
+ nMaxPos = nBlankPos;
+ }
+ else
+ {
+ nMaxPos = 0; // no grouping
+ }
+ }
+ else if (bDecSep) // decimal separator present
+ {
+ nMaxPos = nDecPos;
+ }
+ else if (bExp) // 'E' exponent present
+ {
+ nMaxPos = nExpPos;
+ }
+ else // up to end
+ {
+ nMaxPos = i;
+ }
+ // Insert separators at proper positions.
+ sal_Int32 nCount = 0;
+ utl::DigitGroupingIterator aGrouping( pLoc->getDigitGrouping());
+ size_t nFirstDigitSymbol = nMaxPos;
+ size_t nFirstGroupingSymbol = nMaxPos;
+ i = nMaxPos;
+ while (i-- > 0)
+ {
+ if (nTypeArray[i] == NF_SYMBOLTYPE_DIGIT)
+ {
+ nFirstDigitSymbol = i;
+ nCount = nCount + sStrArray[i].getLength(); // MSC converts += to int and then warns, so ...
+ // Insert separator only if not leftmost symbol.
+ if (i > 0 && nCount >= aGrouping.getPos())
+ {
+ DBG_ASSERT( sStrArray[i].getLength() == 1,
+ "ImpSvNumberformatScan::FinalScan: combined digits in group separator insertion");
+ if (!InsertSymbol( i, NF_SYMBOLTYPE_THSEP, pFormatter->GetNumThousandSep()))
+ {
+ // nPos isn't correct here, but signals error
+ return nPos;
+ }
+ // i may have been decremented by 1
+ nFirstDigitSymbol = i + 1;
+ nFirstGroupingSymbol = i;
+ aGrouping.advance();
+ }
+ }
+ }
+ // Generated something like "string",000; remove separator again.
+ if (nFirstGroupingSymbol < nFirstDigitSymbol)
+ {
+ nTypeArray[nFirstGroupingSymbol] = NF_SYMBOLTYPE_EMPTY;
+ nResultStringsCnt--;
+ }
+ }
+ // Combine digits into groups to save memory (Info will be copied
+ // later, taking only non-empty symbols).
+ for (i = 0; i < nStringsCnt; ++i)
+ {
+ if (nTypeArray[i] == NF_SYMBOLTYPE_DIGIT)
+ {
+ OUString& rStr = sStrArray[i];
+ while (++i < nStringsCnt && nTypeArray[i] == NF_SYMBOLTYPE_DIGIT)
+ {
+ rStr += sStrArray[i];
+ nTypeArray[i] = NF_SYMBOLTYPE_EMPTY;
+ nResultStringsCnt--;
+ }
+ }
+ }
+ break; // of SvNumFormatType::NUMBER
+ case SvNumFormatType::DATE:
+ while (i < nStringsCnt)
+ {
+ switch (nTypeArray[i])
+ {
+ case NF_SYMBOLTYPE_BLANK:
+ case NF_SYMBOLTYPE_STAR:
+ case NF_SYMBOLTYPE_STRING:
+ nPos = nPos + sStrArray[i].getLength();
+ i++;
+ break;
+ case NF_SYMBOLTYPE_DEL:
+ int nCalRet;
+ if (sStrArray[i] == sOldDateSep)
+ {
+ nTypeArray[i] = NF_SYMBOLTYPE_DATESEP;
+ nPos = nPos + sStrArray[i].getLength();
+ if (bConvertMode)
+ {
+ sStrArray[i] = pFormatter->GetDateSep();
+ }
+ i++;
+ }
+ else if ( (nCalRet = FinalScanGetCalendar( nPos, i, nResultStringsCnt )) != 0 )
+ {
+ if ( nCalRet < 0 )
+ {
+ return nPos; // error
+ }
+ }
+ else
+ {
+ nTypeArray[i] = NF_SYMBOLTYPE_STRING;
+ nPos = nPos + sStrArray[i].getLength();
+ i++;
+ }
+ break;
+ case NF_KEY_THAI_T :
+ bThaiT = true;
+ [[fallthrough]];
+ case NF_KEY_M: // M
+ case NF_KEY_MM: // MM
+ case NF_KEY_MMM: // MMM
+ case NF_KEY_MMMM: // MMMM
+ case NF_KEY_MMMMM: // MMMMM
+ case NF_KEY_Q: // Q
+ case NF_KEY_QQ: // QQ
+ case NF_KEY_D: // D
+ case NF_KEY_DD: // DD
+ case NF_KEY_DDD: // DDD
+ case NF_KEY_DDDD: // DDDD
+ case NF_KEY_YY: // YY
+ case NF_KEY_YYYY: // YYYY
+ case NF_KEY_NN: // NN
+ case NF_KEY_NNN: // NNN
+ case NF_KEY_NNNN: // NNNN
+ case NF_KEY_WW : // WW
+ case NF_KEY_AAA : // AAA
+ case NF_KEY_AAAA : // AAAA
+ case NF_KEY_EC : // E
+ case NF_KEY_EEC : // EE
+ case NF_KEY_G : // G
+ case NF_KEY_GG : // GG
+ case NF_KEY_GGG : // GGG
+ case NF_KEY_R : // R
+ case NF_KEY_RR : // RR
+ sStrArray[i] = sKeyword[nTypeArray[i]]; // tTtT -> TTTT
+ nPos = nPos + sStrArray[i].getLength();
+ if (bNewDateOrder)
+ {
+ // For simple numeric date formats record date order and
+ // later rearrange.
+ switch (nTypeArray[i])
+ {
+ case NF_KEY_M:
+ case NF_KEY_MM:
+ if (nMonthPos == SAL_MAX_UINT16)
+ nMonthPos = i;
+ else
+ bNewDateOrder = false;
+ break;
+ case NF_KEY_D:
+ case NF_KEY_DD:
+ if (nDayPos == SAL_MAX_UINT16)
+ nDayPos = i;
+ else
+ bNewDateOrder = false;
+ break;
+ case NF_KEY_YY:
+ case NF_KEY_YYYY:
+ if (nYearPos == SAL_MAX_UINT16)
+ nYearPos = i;
+ else
+ bNewDateOrder = false;
+ break;
+ default:
+ ; // nothing
+ }
+ }
+ i++;
+ break;
+ default: // Other keywords
+ nTypeArray[i] = NF_SYMBOLTYPE_STRING;
+ nPos = nPos + sStrArray[i].getLength();
+ i++;
+ break;
+ }
+ } // of while
+ break; // of SvNumFormatType::DATE
+ case SvNumFormatType::TIME:
+ while (i < nStringsCnt)
+ {
+ sal_Unicode cChar;
+
+ switch (nTypeArray[i])
+ {
+ case NF_SYMBOLTYPE_BLANK:
+ case NF_SYMBOLTYPE_STAR:
+ nPos = nPos + sStrArray[i].getLength();
+ i++;
+ break;
+ case NF_SYMBOLTYPE_DEL:
+ switch( sStrArray[i][0] )
+ {
+ case '0':
+ if ( Is100SecZero( i, bDecSep ) )
+ {
+ bDecSep = true;
+ nTypeArray[i] = NF_SYMBOLTYPE_DIGIT;
+ OUString& rStr = sStrArray[i];
+
+ nCounter++;
+ i++;
+ while (i < nStringsCnt &&
+ sStrArray[i][0] == '0')
+ {
+ rStr += sStrArray[i];
+ nTypeArray[i] = NF_SYMBOLTYPE_EMPTY;
+ nResultStringsCnt--;
+ nCounter++;
+ i++;
+ }
+ nPos += rStr.getLength();
+ }
+ else
+ {
+ return nPos;
+ }
+ break;
+ case '#':
+ case '?':
+ return nPos;
+ case '[':
+ if (bThousand) // Double
+ {
+ return nPos;
+ }
+ bThousand = true; // Empty for Time
+ cChar = pChrCls->uppercase(OUString(NextChar(i)))[0];
+ if ( cChar == cOldKeyH )
+ {
+ nThousand = 1; // H
+ }
+ else if ( cChar == cOldKeyMI )
+ {
+ nThousand = 2; // M
+ }
+ else if ( cChar == cOldKeyS )
+ {
+ nThousand = 3; // S
+ }
+ else
+ {
+ return nPos;
+ }
+ nPos = nPos + sStrArray[i].getLength();
+ i++;
+ break;
+ case ']':
+ if (!bThousand) // No preceding [
+ {
+ return nPos;
+ }
+ nPos = nPos + sStrArray[i].getLength();
+ i++;
+ break;
+ default:
+ nPos = nPos + sStrArray[i].getLength();
+ if ( sStrArray[i] == sOldTimeSep )
+ {
+ nTypeArray[i] = NF_SYMBOLTYPE_TIMESEP;
+ if ( bConvertMode )
+ {
+ sStrArray[i] = pLoc->getTimeSep();
+ }
+ }
+ else if ( sStrArray[i] == sOldTime100SecSep )
+ {
+ bDecSep = true;
+ nTypeArray[i] = NF_SYMBOLTYPE_TIME100SECSEP;
+ if ( bConvertMode )
+ {
+ sStrArray[i] = pLoc->getTime100SecSep();
+ }
+ }
+ else
+ {
+ nTypeArray[i] = NF_SYMBOLTYPE_STRING;
+ }
+ i++;
+ break;
+ }
+ break;
+ case NF_SYMBOLTYPE_STRING:
+ nPos = nPos + sStrArray[i].getLength();
+ i++;
+ break;
+ case NF_KEY_AMPM: // AM/PM
+ case NF_KEY_AP: // A/P
+ bExp = true; // Abuse for A/P
+ sStrArray[i] = sKeyword[nTypeArray[i]]; // tTtT -> TTTT
+ nPos = nPos + sStrArray[i].getLength();
+ i++;
+ break;
+ case NF_KEY_THAI_T :
+ bThaiT = true;
+ [[fallthrough]];
+ case NF_KEY_MI: // M
+ case NF_KEY_MMI: // MM
+ case NF_KEY_H: // H
+ case NF_KEY_HH: // HH
+ case NF_KEY_S: // S
+ case NF_KEY_SS: // SS
+ sStrArray[i] = sKeyword[nTypeArray[i]]; // tTtT -> TTTT
+ nPos = nPos + sStrArray[i].getLength();
+ i++;
+ break;
+ default: // Other keywords
+ nTypeArray[i] = NF_SYMBOLTYPE_STRING;
+ nPos = nPos + sStrArray[i].getLength();
+ i++;
+ break;
+ }
+ } // of while
+ nCntPost = nCounter; // Zero counter
+ if (bExp)
+ {
+ nCntExp = 1; // Remembers AM/PM
+ }
+ break; // of SvNumFormatType::TIME
+ case SvNumFormatType::DATETIME:
+ while (i < nStringsCnt)
+ {
+ int nCalRet;
+ switch (nTypeArray[i])
+ {
+ case NF_SYMBOLTYPE_BLANK:
+ case NF_SYMBOLTYPE_STAR:
+ case NF_SYMBOLTYPE_STRING:
+ nPos = nPos + sStrArray[i].getLength();
+ i++;
+ break;
+ case NF_SYMBOLTYPE_DEL:
+ if ( (nCalRet = FinalScanGetCalendar( nPos, i, nResultStringsCnt )) != 0 )
+ {
+ if ( nCalRet < 0 )
+ {
+ return nPos; // Error
+ }
+ }
+ else
+ {
+ switch( sStrArray[i][0] )
+ {
+ case '0':
+ if (bTimePart && Is100SecZero(i, bDecSep) && nCounter < MaxCntPost)
+ {
+ bDecSep = true;
+ nTypeArray[i] = NF_SYMBOLTYPE_DIGIT;
+ OUString& rStr = sStrArray[i];
+ nCounter++;
+ i++;
+ while (i < nStringsCnt &&
+ sStrArray[i][0] == '0' && nCounter < MaxCntPost)
+ {
+ rStr += sStrArray[i];
+ nTypeArray[i] = NF_SYMBOLTYPE_EMPTY;
+ nResultStringsCnt--;
+ nCounter++;
+ i++;
+ }
+ nPos += rStr.getLength();
+ }
+ else
+ {
+ return nPos;
+ }
+ break;
+ case '#':
+ case '?':
+ return nPos;
+ default:
+ nPos = nPos + sStrArray[i].getLength();
+ if (bTimePart)
+ {
+ if ( sStrArray[i] == sOldTimeSep )
+ {
+ nTypeArray[i] = NF_SYMBOLTYPE_TIMESEP;
+ if ( bConvertMode )
+ {
+ sStrArray[i] = pLoc->getTimeSep();
+ }
+ }
+ else if ( sStrArray[i] == sOldTime100SecSep )
+ {
+ bDecSep = true;
+ nTypeArray[i] = NF_SYMBOLTYPE_TIME100SECSEP;
+ if ( bConvertMode )
+ {
+ sStrArray[i] = pLoc->getTime100SecSep();
+ }
+ }
+ else
+ {
+ nTypeArray[i] = NF_SYMBOLTYPE_STRING;
+ }
+ }
+ else
+ {
+ if ( sStrArray[i] == sOldDateSep )
+ {
+ nTypeArray[i] = NF_SYMBOLTYPE_DATESEP;
+ if (bConvertMode)
+ sStrArray[i] = pFormatter->GetDateSep();
+ }
+ else
+ {
+ nTypeArray[i] = NF_SYMBOLTYPE_STRING;
+ }
+ }
+ i++;
+ break;
+ }
+ }
+ break;
+ case NF_KEY_AMPM: // AM/PM
+ case NF_KEY_AP: // A/P
+ bTimePart = true;
+ bExp = true; // Abuse for A/P
+ sStrArray[i] = sKeyword[nTypeArray[i]]; // tTtT -> TTTT
+ nPos = nPos + sStrArray[i].getLength();
+ i++;
+ break;
+ case NF_KEY_MI: // M
+ case NF_KEY_MMI: // MM
+ case NF_KEY_H: // H
+ case NF_KEY_HH: // HH
+ case NF_KEY_S: // S
+ case NF_KEY_SS: // SS
+ bTimePart = true;
+ sStrArray[i] = sKeyword[nTypeArray[i]]; // tTtT -> TTTT
+ nPos = nPos + sStrArray[i].getLength();
+ i++;
+ break;
+ case NF_KEY_M: // M
+ case NF_KEY_MM: // MM
+ case NF_KEY_MMM: // MMM
+ case NF_KEY_MMMM: // MMMM
+ case NF_KEY_MMMMM: // MMMMM
+ case NF_KEY_Q: // Q
+ case NF_KEY_QQ: // QQ
+ case NF_KEY_D: // D
+ case NF_KEY_DD: // DD
+ case NF_KEY_DDD: // DDD
+ case NF_KEY_DDDD: // DDDD
+ case NF_KEY_YY: // YY
+ case NF_KEY_YYYY: // YYYY
+ case NF_KEY_NN: // NN
+ case NF_KEY_NNN: // NNN
+ case NF_KEY_NNNN: // NNNN
+ case NF_KEY_WW : // WW
+ case NF_KEY_AAA : // AAA
+ case NF_KEY_AAAA : // AAAA
+ case NF_KEY_EC : // E
+ case NF_KEY_EEC : // EE
+ case NF_KEY_G : // G
+ case NF_KEY_GG : // GG
+ case NF_KEY_GGG : // GGG
+ case NF_KEY_R : // R
+ case NF_KEY_RR : // RR
+ bTimePart = false;
+ sStrArray[i] = sKeyword[nTypeArray[i]]; // tTtT -> TTTT
+ nPos = nPos + sStrArray[i].getLength();
+ if (bNewDateOrder)
+ {
+ // For simple numeric date formats record date order and
+ // later rearrange.
+ switch (nTypeArray[i])
+ {
+ case NF_KEY_M:
+ case NF_KEY_MM:
+ if (nMonthPos == SAL_MAX_UINT16)
+ nMonthPos = i;
+ else
+ bNewDateOrder = false;
+ break;
+ case NF_KEY_D:
+ case NF_KEY_DD:
+ if (nDayPos == SAL_MAX_UINT16)
+ nDayPos = i;
+ else
+ bNewDateOrder = false;
+ break;
+ case NF_KEY_YY:
+ case NF_KEY_YYYY:
+ if (nYearPos == SAL_MAX_UINT16)
+ nYearPos = i;
+ else
+ bNewDateOrder = false;
+ break;
+ default:
+ ; // nothing
+ }
+ }
+ i++;
+ break;
+ case NF_KEY_THAI_T :
+ bThaiT = true;
+ sStrArray[i] = sKeyword[nTypeArray[i]];
+ nPos = nPos + sStrArray[i].getLength();
+ i++;
+ break;
+ default: // Other keywords
+ nTypeArray[i] = NF_SYMBOLTYPE_STRING;
+ nPos = nPos + sStrArray[i].getLength();
+ i++;
+ break;
+ }
+ } // of while
+ nCntPost = nCounter; // decimals (100th seconds)
+ if (bExp)
+ {
+ nCntExp = 1; // Remembers AM/PM
+ }
+ break; // of SvNumFormatType::DATETIME
+ default:
+ break;
+ }
+ if (eScannedType == SvNumFormatType::SCIENTIFIC &&
+ (nCntPre + nCntPost == 0 || nCntExp == 0))
+ {
+ return nPos;
+ }
+ else if (eScannedType == SvNumFormatType::FRACTION && (nCntExp > 8 || nCntExp == 0))
+ {
+ return nPos;
+ }
+ if (bThaiT && !GetNatNumModifier())
+ {
+ SetNatNumModifier(1);
+ }
+ if ( bConvertMode )
+ {
+ if (bNewDateOrder && sOldDateSep == "-")
+ {
+ // Keep ISO formats Y-M-D, Y-M and M-D
+ if (IsDateFragment( nYearPos, nMonthPos))
+ {
+ nTypeArray[nYearPos+1] = NF_SYMBOLTYPE_STRING;
+ sStrArray[nYearPos+1] = sOldDateSep;
+ bNewDateOrder = false;
+ }
+ if (IsDateFragment( nMonthPos, nDayPos))
+ {
+ nTypeArray[nMonthPos+1] = NF_SYMBOLTYPE_STRING;
+ sStrArray[nMonthPos+1] = sOldDateSep;
+ bNewDateOrder = false;
+ }
+ }
+ if (bNewDateOrder)
+ {
+ // Rearrange date order to the target locale if the original order
+ // includes date separators and is adjacent.
+ /* TODO: for incomplete dates trailing separators need to be
+ * handled according to the locale's usage, e.g. en-US M/D should
+ * be converted to de-DE D.M. and vice versa. As is, it's
+ * M/D -> D.M and D.M. -> M/D/ where specifically the latter looks
+ * odd. Check accepted date patterns and append/remove? */
+ switch (eOldDateOrder)
+ {
+ case DateOrder::DMY:
+ switch (pLoc->getDateOrder())
+ {
+ case DateOrder::MDY:
+ // Convert only if the actual format is not of YDM
+ // order (which would be a completely unusual order
+ // anyway, but..), e.g. YYYY.DD.MM not to
+ // YYYY/MM/DD
+ if (IsDateFragment( nDayPos, nMonthPos) && !IsDateFragment( nYearPos, nDayPos))
+ SwapArrayElements( nDayPos, nMonthPos);
+ break;
+ case DateOrder::YMD:
+ if (nYearPos != SAL_MAX_UINT16)
+ {
+ if (IsDateFragment( nDayPos, nMonthPos) && IsDateFragment( nMonthPos, nYearPos))
+ SwapArrayElements( nDayPos, nYearPos);
+ }
+ else
+ {
+ if (IsDateFragment( nDayPos, nMonthPos))
+ SwapArrayElements( nDayPos, nMonthPos);
+ }
+ break;
+ default:
+ ; // nothing
+ }
+ break;
+ case DateOrder::MDY:
+ switch (pLoc->getDateOrder())
+ {
+ case DateOrder::DMY:
+ // Convert only if the actual format is not of YMD
+ // order, e.g. YYYY/MM/DD not to YYYY.DD.MM
+ /* TODO: convert such to DD.MM.YYYY instead? */
+ if (IsDateFragment( nMonthPos, nDayPos) && !IsDateFragment( nYearPos, nMonthPos))
+ SwapArrayElements( nMonthPos, nDayPos);
+ break;
+ case DateOrder::YMD:
+ if (nYearPos != SAL_MAX_UINT16)
+ {
+ if (IsDateFragment( nMonthPos, nDayPos) && IsDateFragment( nDayPos, nYearPos))
+ {
+ SwapArrayElements( nYearPos, nMonthPos); // YDM
+ SwapArrayElements( nYearPos, nDayPos); // YMD
+ }
+ }
+ break;
+ default:
+ ; // nothing
+ }
+ break;
+ case DateOrder::YMD:
+ switch (pLoc->getDateOrder())
+ {
+ case DateOrder::DMY:
+ if (nYearPos != SAL_MAX_UINT16)
+ {
+ if (IsDateFragment( nYearPos, nMonthPos) && IsDateFragment( nMonthPos, nDayPos))
+ SwapArrayElements( nYearPos, nDayPos);
+ }
+ else
+ {
+ if (IsDateFragment( nMonthPos, nDayPos))
+ SwapArrayElements( nMonthPos, nDayPos);
+ }
+ break;
+ case DateOrder::MDY:
+ if (nYearPos != SAL_MAX_UINT16)
+ {
+ if (IsDateFragment( nYearPos, nMonthPos) && IsDateFragment( nMonthPos, nDayPos))
+ {
+ SwapArrayElements( nYearPos, nDayPos); // DMY
+ SwapArrayElements( nYearPos, nMonthPos); // MDY
+ }
+ }
+ break;
+ default:
+ ; // nothing
+ }
+ break;
+ default:
+ ; // nothing
+ }
+ }
+ // strings containing keywords of the target locale must be quoted, so
+ // the user sees the difference and is able to edit the format string
+ for ( i=0; i < nStringsCnt; i++ )
+ {
+ if ( nTypeArray[i] == NF_SYMBOLTYPE_STRING &&
+ sStrArray[i][0] != '\"' )
+ {
+ if ( bConvertSystemToSystem && eScannedType == SvNumFormatType::CURRENCY )
+ {
+ // don't stringize automatic currency, will be converted
+ if ( sStrArray[i] == sOldCurSymbol )
+ {
+ continue; // for
+ }
+ // DM might be split into D and M
+ if ( sStrArray[i].getLength() < sOldCurSymbol.getLength() &&
+ pChrCls->uppercase( sStrArray[i], 0, 1 )[0] ==
+ sOldCurString[0] )
+ {
+ OUString aTmp( sStrArray[i] );
+ sal_uInt16 j = i + 1;
+ while ( aTmp.getLength() < sOldCurSymbol.getLength() &&
+ j < nStringsCnt &&
+ nTypeArray[j] == NF_SYMBOLTYPE_STRING )
+ {
+ aTmp += sStrArray[j++];
+ }
+ if ( pChrCls->uppercase( aTmp ) == sOldCurString )
+ {
+ sStrArray[i++] = aTmp;
+ for ( ; i<j; i++ )
+ {
+ nTypeArray[i] = NF_SYMBOLTYPE_EMPTY;
+ nResultStringsCnt--;
+ }
+ i = j - 1;
+ continue; // for
+ }
+ }
+ }
+ OUString& rStr = sStrArray[i];
+ sal_Int32 nLen = rStr.getLength();
+ for ( sal_Int32 j = 0; j < nLen; j++ )
+ {
+ bool bFoundEnglish = false;
+ if ( (j == 0 || rStr[j - 1] != '\\') && GetKeyWord( rStr, j, bFoundEnglish) )
+ {
+ rStr = "\"" + rStr + "\"";
+ break; // for
+ }
+ }
+ }
+ }
+ }
+ // Concatenate strings, remove quotes for output, and rebuild the format string
+ rString.clear();
+ i = 0;
+ while (i < nStringsCnt)
+ {
+ sal_Int32 nStringPos;
+ sal_Int32 nArrPos = 0;
+ sal_uInt16 iPos = i;
+ switch ( nTypeArray[i] )
+ {
+ case NF_SYMBOLTYPE_STRING :
+ case NF_SYMBOLTYPE_FRACBLANK :
+ nStringPos = rString.getLength();
+ do
+ {
+ if (sStrArray[i].getLength() == 2 &&
+ sStrArray[i][0] == '\\')
+ {
+ // Unescape some simple forms of symbols even in the UI
+ // visible string to prevent duplicates that differ
+ // only in notation, originating from import.
+ // e.g. YYYY-MM-DD and YYYY\-MM\-DD are identical,
+ // but 0\ 000 0 and 0 000 0 in a French locale are not.
+
+ sal_Unicode c = sStrArray[i][1];
+
+ switch (c)
+ {
+ case '+':
+ case '-':
+ rString += OUStringChar(c);
+ break;
+ case ' ':
+ case '.':
+ case '/':
+ if (!(eScannedType & SvNumFormatType::DATE) &&
+ (StringEqualsChar( pFormatter->GetNumThousandSep(), c) ||
+ StringEqualsChar( pFormatter->GetNumDecimalSep(), c) ||
+ (c == ' ' &&
+ (StringEqualsChar( pFormatter->GetNumThousandSep(), cNoBreakSpace) ||
+ StringEqualsChar( pFormatter->GetNumThousandSep(), cNarrowNoBreakSpace)))))
+ {
+ rString += sStrArray[i];
+ }
+ else if ((eScannedType & SvNumFormatType::DATE) &&
+ StringEqualsChar( pFormatter->GetDateSep(), c))
+ {
+ rString += sStrArray[i];
+ }
+ else if ((eScannedType & SvNumFormatType::TIME) &&
+ (StringEqualsChar( pLoc->getTimeSep(), c) ||
+ StringEqualsChar( pLoc->getTime100SecSep(), c)))
+ {
+ rString += sStrArray[i];
+ }
+ else if (eScannedType & SvNumFormatType::FRACTION)
+ {
+ rString += sStrArray[i];
+ }
+ else
+ {
+ rString += OUStringChar(c);
+ }
+ break;
+ default:
+ rString += sStrArray[i];
+ }
+ }
+ else
+ {
+ rString += sStrArray[i];
+ }
+ if ( RemoveQuotes( sStrArray[i] ) > 0 )
+ {
+ // update currency up to quoted string
+ if ( eScannedType == SvNumFormatType::CURRENCY )
+ {
+ // dM -> DM or DM -> $ in old automatic
+ // currency formats, oh my ..., why did we ever introduce them?
+ OUString aTmp( pChrCls->uppercase( sStrArray[iPos], nArrPos,
+ sStrArray[iPos].getLength()-nArrPos ) );
+ sal_Int32 nCPos = aTmp.indexOf( sOldCurString );
+ if ( nCPos >= 0 )
+ {
+ const OUString& rCur = bConvertMode && bConvertSystemToSystem ?
+ GetCurSymbol() : sOldCurSymbol;
+ sStrArray[iPos] = sStrArray[iPos].replaceAt( nArrPos + nCPos,
+ sOldCurString.getLength(),
+ rCur );
+ rString = rString.replaceAt( nStringPos + nCPos,
+ sOldCurString.getLength(),
+ rCur );
+ }
+ nStringPos = rString.getLength();
+ if ( iPos == i )
+ {
+ nArrPos = sStrArray[iPos].getLength();
+ }
+ else
+ {
+ nArrPos = sStrArray[iPos].getLength() + sStrArray[i].getLength();
+ }
+ }
+ }
+ if ( iPos != i )
+ {
+ sStrArray[iPos] += sStrArray[i];
+ nTypeArray[i] = NF_SYMBOLTYPE_EMPTY;
+ nResultStringsCnt--;
+ }
+ i++;
+ }
+ while ( i < nStringsCnt && nTypeArray[i] == NF_SYMBOLTYPE_STRING );
+
+ if ( i < nStringsCnt )
+ {
+ i--; // enter switch on next symbol again
+ }
+ if ( eScannedType == SvNumFormatType::CURRENCY && nStringPos < rString.getLength() )
+ {
+ // same as above, since last RemoveQuotes
+ OUString aTmp( pChrCls->uppercase( sStrArray[iPos], nArrPos,
+ sStrArray[iPos].getLength()-nArrPos ) );
+ sal_Int32 nCPos = aTmp.indexOf( sOldCurString );
+ if ( nCPos >= 0 )
+ {
+ const OUString& rCur = bConvertMode && bConvertSystemToSystem ?
+ GetCurSymbol() : sOldCurSymbol;
+ sStrArray[iPos] = sStrArray[iPos].replaceAt( nArrPos + nCPos,
+ sOldCurString.getLength(),
+ rCur );
+ rString = rString.replaceAt( nStringPos + nCPos,
+ sOldCurString.getLength(), rCur );
+ }
+ }
+ break;
+ case NF_SYMBOLTYPE_CURRENCY :
+ rString += sStrArray[i];
+ RemoveQuotes( sStrArray[i] );
+ break;
+ case NF_KEY_THAI_T:
+ if (bThaiT && GetNatNumModifier() == 1)
+ {
+ // Remove T from format code, will be replaced with a [NatNum1] prefix.
+ nTypeArray[i] = NF_SYMBOLTYPE_EMPTY;
+ nResultStringsCnt--;
+ }
+ else
+ {
+ rString += sStrArray[i];
+ }
+ break;
+ case NF_SYMBOLTYPE_EMPTY :
+ // nothing
+ break;
+ default:
+ rString += sStrArray[i];
+ }
+ i++;
+ }
+ return 0;
+}
+
+sal_Int32 ImpSvNumberformatScan::RemoveQuotes( OUString& rStr )
+{
+ if ( rStr.getLength() > 1 )
+ {
+ sal_Unicode c = rStr[0];
+ sal_Int32 n = rStr.getLength() - 1;
+ if ( c == '"' && rStr[n] == '"' )
+ {
+ rStr = rStr.copy( 1, n-1);
+ return 2;
+ }
+ else if ( c == '\\' )
+ {
+ rStr = rStr.copy(1);
+ return 1;
+ }
+ }
+ return 0;
+}
+
+sal_Int32 ImpSvNumberformatScan::ScanFormat( OUString& rString )
+{
+ sal_Int32 res = Symbol_Division(rString); // Lexical analysis
+ if (!res)
+ {
+ res = ScanType(); // Recognizing the Format type
+ }
+ if (!res)
+ {
+ res = FinalScan( rString ); // Type dependent final analysis
+ }
+ return res; // res = control position; res = 0 => Format ok
+}
+
+void ImpSvNumberformatScan::CopyInfo(ImpSvNumberformatInfo* pInfo, sal_uInt16 nCnt)
+{
+ size_t i,j;
+ j = 0;
+ i = 0;
+ while (i < nCnt && j < NF_MAX_FORMAT_SYMBOLS)
+ {
+ if (nTypeArray[j] != NF_SYMBOLTYPE_EMPTY)
+ {
+ pInfo->sStrArray[i] = sStrArray[j];
+ pInfo->nTypeArray[i] = nTypeArray[j];
+ i++;
+ }
+ j++;
+ }
+ pInfo->eScannedType = eScannedType;
+ pInfo->bThousand = bThousand;
+ pInfo->nThousand = nThousand;
+ pInfo->nCntPre = nCntPre;
+ pInfo->nCntPost = nCntPost;
+ pInfo->nCntExp = nCntExp;
+}
+
+bool ImpSvNumberformatScan::ReplaceBooleanEquivalent( OUString& rString )
+{
+ InitKeywords();
+ /* TODO: compare case insensitive? Or rather leave as is and case not
+ * matching indicates user supplied on purpose? Written to file / generated
+ * was always uppercase. */
+ if (rString == sBooleanEquivalent1 || rString == sBooleanEquivalent2)
+ {
+ rString = GetKeywords()[NF_KEY_BOOLEAN];
+ return true;
+ }
+ return false;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/numbers/zforscan.hxx b/svl/source/numbers/zforscan.hxx
new file mode 100644
index 0000000000..c160dd424e
--- /dev/null
+++ b/svl/source/numbers/zforscan.hxx
@@ -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 .
+ */
+#ifndef INCLUDED_SVL_SOURCE_NUMBERS_ZFORSCAN_HXX
+#define INCLUDED_SVL_SOURCE_NUMBERS_ZFORSCAN_HXX
+
+#include <i18nlangtag/lang.h>
+#include <rtl/ustring.hxx>
+#include <svl/nfkeytab.hxx>
+#include <svl/nfsymbol.hxx>
+#include <svl/numformat.hxx>
+#include <svl/zforlist.hxx>
+#include <tools/color.hxx>
+#include <tools/date.hxx>
+#include <unotools/localedatawrapper.hxx>
+
+class SvNumberFormatter;
+struct ImpSvNumberformatInfo;
+
+
+const size_t NF_MAX_DEFAULT_COLORS = 10;
+
+// Hack: nThousand==1000 => "Default" occurs in format string
+const sal_uInt16 FLAG_STANDARD_IN_FORMAT = 1000;
+
+class ImpSvNumberformatScan
+{
+public:
+
+ /** Specify what keyword localization is allowed when scanning the format code. */
+ enum class KeywordLocalization
+ {
+ LocaleLegacy, ///< unfortunately localized in few locales, otherwise English
+ EnglishOnly, ///< only English, no localized keywords
+ AllowEnglish ///< allow English keywords as well as localized keywords
+ };
+
+ explicit ImpSvNumberformatScan( SvNumberFormatter* pFormatter );
+ ~ImpSvNumberformatScan();
+ void ChangeIntl( KeywordLocalization eKeywordLocalization = KeywordLocalization::AllowEnglish ); // Replaces Keywords
+
+ void ChangeNullDate(sal_uInt16 nDay, sal_uInt16 nMonth, sal_Int16 nYear); // Replaces reference date
+ void ChangeStandardPrec(sal_uInt16 nPrec); // Replaces standard precision
+
+ sal_Int32 ScanFormat( OUString& rString ); // Call scan analysis
+
+ void CopyInfo(ImpSvNumberformatInfo* pInfo,
+ sal_uInt16 nCnt); // Copies the FormatInfo
+ sal_uInt16 GetResultStringsCnt() const { return nResultStringsCnt; }
+
+ const CharClass& GetChrCls() const { return *pFormatter->GetCharClass(); }
+ const LocaleDataWrapper& GetLoc() const { return *pFormatter->GetLocaleData(); }
+ CalendarWrapper& GetCal() const { return *pFormatter->GetCalendar(); }
+
+ const NfKeywordTable & GetKeywords() const
+ {
+ if ( bKeywordsNeedInit )
+ {
+ InitKeywords();
+ }
+ return sKeyword;
+ }
+
+ static const NfKeywordTable & GetEnglishKeywords()
+ {
+ return sEnglishKeyword;
+ }
+
+ // Keywords used in output like true and false
+ const OUString& GetSpecialKeyword( NfKeywordIndex eIdx ) const
+ {
+ if ( sKeyword[eIdx].isEmpty() )
+ {
+ InitSpecialKeyword( eIdx );
+ }
+ return sKeyword[eIdx];
+ }
+ const OUString& GetTrueString() const { return GetSpecialKeyword( NF_KEY_TRUE ); }
+ const OUString& GetFalseString() const { return GetSpecialKeyword( NF_KEY_FALSE ); }
+ const OUString& GetRedString() const { return GetKeywords()[NF_KEY_RED]; }
+ const OUString& GetBooleanString() const { return GetKeywords()[NF_KEY_BOOLEAN]; }
+ static const ::std::vector<Color> & GetStandardColors()
+ {
+ return StandardColor;
+ }
+ static size_t GetMaxDefaultColors()
+ {
+ return NF_MAX_DEFAULT_COLORS;
+ }
+
+ const Date& GetNullDate() const { return maNullDate; }
+ const OUString& GetStandardName() const
+ {
+ if ( bKeywordsNeedInit )
+ {
+ InitKeywords();
+ }
+ return sNameStandardFormat;
+ }
+ sal_uInt16 GetStandardPrec() const { return nStandardPrec; }
+ static const Color& GetRedColor() { return StandardColor[4]; }
+ const Color* GetColor(OUString& sStr) const; // Set main colors or defines colors
+
+ // the compatibility currency symbol for old automatic currency formats
+ const OUString& GetCurSymbol() const
+ {
+ if ( bCompatCurNeedInit )
+ {
+ InitCompatCur();
+ }
+ return sCurSymbol;
+ }
+
+ // the compatibility currency abbreviation for CCC format code
+ const OUString& GetCurAbbrev() const
+ {
+ if ( bCompatCurNeedInit )
+ {
+ InitCompatCur();
+ }
+ return sCurAbbrev;
+ }
+
+ // the compatibility currency symbol upper case for old automatic currency formats
+ const OUString& GetCurString() const
+ {
+ if ( bCompatCurNeedInit )
+ {
+ InitCompatCur();
+ }
+ return sCurString;
+ }
+
+ /// Replace Boolean equivalent format codes with proper Boolean format.
+ bool ReplaceBooleanEquivalent( OUString& rString );
+
+ void SetConvertMode(LanguageType eTmpLge, LanguageType eNewLge,
+ bool bSystemToSystem, bool bConvertDateOrder)
+ {
+ bConvertMode = true;
+ eNewLnge = eNewLge;
+ eTmpLnge = eTmpLge;
+ bConvertSystemToSystem = bSystemToSystem;
+ mbConvertDateOrder = bConvertDateOrder;
+ }
+ // Only changes the bool variable, in order to temporarily pause the convert mode
+ void SetConvertMode(bool bMode) { bConvertMode = bMode; }
+ bool GetConvertMode() const { return bConvertMode; }
+ LanguageType GetNewLnge() const { return eNewLnge; } // Read access on ConvertMode and convert country/language
+ LanguageType GetTmpLnge() const { return eTmpLnge; } // Read access on StartCountry/Language
+ void SetNewLnge( LanguageType e ) { eNewLnge = e; } // Set new convert country/language
+
+ /// get Thai T speciality
+ sal_uInt8 GetNatNumModifier() const { return nNatNumModifier; }
+ /// set Thai T speciality
+ void SetNatNumModifier( sal_uInt8 n ) { nNatNumModifier = n; }
+
+ SvNumberFormatter* GetNumberformatter() { return pFormatter; } // Access to formatter (for zformat.cxx)
+
+ /// Get type scanned (so far).
+ SvNumFormatType GetScannedType() const { return eScannedType; }
+
+ static constexpr OUString sErrStr = u"#FMT"_ustr; // String for error output
+
+private: // Private section
+ NfKeywordTable sKeyword; // Syntax keywords
+ static const NfKeywordTable sEnglishKeyword; // English Syntax keywords
+ static const ::std::vector<Color> StandardColor; // Standard color array
+ Date maNullDate; // 30Dec1899
+ OUString sNameStandardFormat; // "Standard"
+ sal_uInt16 nStandardPrec; // Default Precision for Standardformat
+ SvNumberFormatter* pFormatter; // Pointer to the FormatList
+ css::uno::Reference< css::i18n::XNumberFormatCode > xNFC;
+
+ OUString sStrArray[NF_MAX_FORMAT_SYMBOLS]; // Array of symbols
+ short nTypeArray[NF_MAX_FORMAT_SYMBOLS]; // Array of infos
+ // External Infos:
+ sal_uInt16 nResultStringsCnt; // Result symbol count
+ SvNumFormatType eScannedType; // Type according to scan
+ bool bThousand; // With thousands marker
+ sal_uInt16 nThousand; // Counts ... series
+ sal_uInt16 nCntPre; // Counts digits of integral part
+ sal_uInt16 nCntPost; // Counts digits of fractional part
+ sal_uInt16 nCntExp; // Counts exponent digits AM/PM
+ // Internal info:
+ sal_uInt16 nStringsCnt; // Symbol count
+ sal_uInt16 nExpPos; // Internal position of E
+ sal_uInt16 nBlankPos; // Internal position of the Blank
+ short nDecPos; // Internal position of the ,
+ bool bExp; // Set when reading E
+ bool bFrac; // Set when reading /
+ bool bBlank; // Set when reading ' ' (Fraction)
+ bool bDecSep; // Set on first ,
+ mutable bool bKeywordsNeedInit; // Locale dependent keywords need to be initialized
+ mutable bool bCompatCurNeedInit; // Locale dependent compatibility currency need to be initialized
+ OUString sCurSymbol; // Currency symbol for compatibility format codes
+ OUString sCurString; // Currency symbol in upper case
+ OUString sCurAbbrev; // Currency abbreviation
+ OUString sBooleanEquivalent1; // "TRUE";"TRUE";"FALSE"
+ OUString sBooleanEquivalent2; // [>0]"TRUE";[<0]"TRUE";"FALSE"
+
+ bool bConvertMode; // Set in the convert mode
+ bool mbConvertDateOrder; // Set in the convert mode whether to convert date particles order
+
+ LanguageType eNewLnge; // Language/country which the scanned string is converted to (for Excel filter)
+ LanguageType eTmpLnge; // Language/country which the scanned string is converted from (for Excel filter)
+
+ bool bConvertSystemToSystem; // Whether the conversion is from one system locale to another system locale
+ // (in this case the automatic currency symbol is converted too).
+
+ sal_Int32 nCurrPos; // Position of currency symbol
+
+ sal_uInt8 nNatNumModifier; // Thai T speciality
+
+ KeywordLocalization meKeywordLocalization; ///< which keywords localization to scan
+
+ // Copy assignment is forbidden and not implemented.
+ ImpSvNumberformatScan (const ImpSvNumberformatScan &) = delete;
+ ImpSvNumberformatScan & operator= (const ImpSvNumberformatScan &) = delete;
+
+ void InitKeywords() const;
+ void InitSpecialKeyword( NfKeywordIndex eIdx ) const;
+ void InitCompatCur() const;
+
+ void SetDependentKeywords();
+ // Sets the language dependent keywords
+ void SkipStrings(sal_uInt16& i, sal_Int32& nPos) const;// Skips StringSymbols
+ sal_uInt16 PreviousKeyword(sal_uInt16 i) const; // Returns index of the preceding one
+ // Keyword or 0
+ sal_uInt16 NextKeyword(sal_uInt16 i) const; // Returns index of the next one
+ // Keyword or 0
+ sal_Unicode PreviousChar(sal_uInt16 i) const; // Returns last char before index skips EMPTY, STRING, STAR, BLANK
+ sal_Unicode NextChar(sal_uInt16 i) const; // Returns first following char
+ short PreviousType( sal_uInt16 i ) const; // Returns type before position skips EMPTY
+ bool IsLastBlankBeforeFrac(sal_uInt16 i) const; // True <=> there won't be a ' ' until the '/'
+ void Reset(); // Reset all variables before starting the analysis
+
+ /** Determine keyword at nPos.
+ @param rbFoundEnglish set if English instead of locale's keyword
+ found, never cleared, thus init with false.
+ @return 0 if not found, else keyword enumeration.
+ */
+ short GetKeyWord( const OUString& sSymbol,
+ sal_Int32 nPos,
+ bool& rbFoundEnglish ) const;
+
+ bool IsAmbiguousE( short nKey ) const // whether nKey is ambiguous E of NF_KEY_E/NF_KEY_EC
+ {
+ return (nKey == NF_KEY_EC || nKey == NF_KEY_E) &&
+ (GetKeywords()[NF_KEY_EC] == GetKeywords()[NF_KEY_E]);
+ }
+
+ // if 0 at strArray[i] is of S,00 or SS,00 or SS"any"00 in ScanType() or FinalScan()
+ bool Is100SecZero( sal_uInt16 i, bool bHadDecSep ) const;
+
+ short Next_Symbol(const OUString& rStr,
+ sal_Int32& nPos,
+ OUString& sSymbol) const; // Next Symbol
+ sal_Int32 Symbol_Division(const OUString& rString);// Initial lexical scan
+ sal_Int32 ScanType(); // Analysis of the Format type
+ sal_Int32 FinalScan( OUString& rString ); // Final analysis with supplied type
+
+ // -1:= error, return nPos in FinalScan; 0:= no calendar, 1:= calendar found
+ int FinalScanGetCalendar( sal_Int32& nPos, sal_uInt16& i, sal_uInt16& nResultStringsCnt );
+
+ /** Insert symbol into nTypeArray and sStrArray, e.g. grouping separator.
+ If at nPos-1 a symbol type NF_SYMBOLTYPE_EMPTY is present, that is
+ reused instead of shifting all one up and nPos is decremented! */
+ bool InsertSymbol( sal_uInt16 & nPos, svt::NfSymbolType eType, const OUString& rStr );
+
+ /** Whether two key symbols are adjacent separated by date separator.
+ This can only be used at the end of FinalScan() after
+ NF_SYMBOLTYPE_DATESEP has already been set.
+ */
+ bool IsDateFragment( size_t nPos1, size_t nPos2 ) const;
+
+ /** Swap nTypeArray and sStrArray elements at positions. */
+ void SwapArrayElements( size_t nPos1, size_t nPos2 );
+
+ static bool StringEqualsChar( std::u16string_view rStr, sal_Unicode ch )
+ { return rStr.size() == 1 && rStr[0] == ch; }
+
+ // remove "..." and \... quotes from rStr, return how many chars removed
+ static sal_Int32 RemoveQuotes( OUString& rStr );
+};
+
+#endif // INCLUDED_SVL_SOURCE_NUMBERS_ZFORSCAN_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/passwordcontainer/passwordcontainer.component b/svl/source/passwordcontainer/passwordcontainer.component
new file mode 100644
index 0000000000..109f45c502
--- /dev/null
+++ b/svl/source/passwordcontainer/passwordcontainer.component
@@ -0,0 +1,27 @@
+<?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="stardiv.svl.PasswordContainer"
+ single-instance="true"
+ constructor="svl_PasswordContainer_get_implementation">
+ <service name="com.sun.star.task.PasswordContainer"/>
+ </implementation>
+</component>
diff --git a/svl/source/passwordcontainer/passwordcontainer.cxx b/svl/source/passwordcontainer/passwordcontainer.cxx
new file mode 100644
index 0000000000..333e2921b4
--- /dev/null
+++ b/svl/source/passwordcontainer/passwordcontainer.cxx
@@ -0,0 +1,1442 @@
+/* -*- 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 <string_view>
+
+#include "passwordcontainer.hxx"
+
+#include <cppuhelper/factory.hxx>
+#include <cppuhelper/supportsservice.hxx>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/propertyvalue.hxx>
+#include <comphelper/sequence.hxx>
+#include <o3tl/string_view.hxx>
+#include <com/sun/star/beans/PropertyValue.hpp>
+#include <com/sun/star/task/InteractionHandler.hpp>
+#include <com/sun/star/task/MasterPasswordRequest.hpp>
+#include <com/sun/star/task/NoMasterException.hpp>
+#include <com/sun/star/lang/XMultiServiceFactory.hpp>
+
+#include <osl/diagnose.h>
+#include <rtl/character.hxx>
+#include <rtl/cipher.h>
+#include <rtl/digest.h>
+#include <rtl/byteseq.hxx>
+#include <rtl/ustrbuf.hxx>
+
+using namespace osl;
+using namespace utl;
+using namespace com::sun::star;
+using namespace com::sun::star::uno;
+using namespace com::sun::star::registry;
+using namespace com::sun::star::lang;
+using namespace com::sun::star::task;
+using namespace com::sun::star::ucb;
+
+static OUString createIndex(const std::vector< OUString >& lines)
+{
+ OUStringBuffer aResult;
+
+ for( size_t i = 0; i < lines.size(); i++ )
+ {
+ if( i )
+ aResult.append("__");
+ OString line = OUStringToOString( lines[i], RTL_TEXTENCODING_UTF8 );
+ const char* pLine = line.getStr();
+
+ while( *pLine )
+ {
+ if (rtl::isAsciiAlphanumeric(static_cast<unsigned char>(*pLine)))
+ {
+ aResult.append(*pLine);
+ }
+ else
+ {
+ aResult.append("_" + OUString::number(*pLine, 16) );
+ }
+
+ pLine++;
+ }
+ }
+
+ return aResult.makeStringAndClear();
+}
+
+
+static std::vector< OUString > getInfoFromInd( std::u16string_view aInd )
+{
+ std::vector< OUString > aResult;
+ bool aStart = true;
+
+ OString line = OUStringToOString( aInd, RTL_TEXTENCODING_ASCII_US );
+ const char* pLine = line.getStr();
+ do
+ {
+ OUStringBuffer newItem;
+ if( !aStart )
+ pLine += 2;
+ else
+ aStart = false;
+
+ while( *pLine && ( pLine[0] != '_' || pLine[1] != '_' ))
+ if( *pLine != '_' )
+ {
+ newItem.append( *pLine );
+ pLine++;
+ }
+ else
+ {
+ OUString aNum;
+ for( int i = 1; i < 3; i++ )
+ {
+ if( !pLine[i]
+ || ( ( pLine[i] < '0' || pLine[i] > '9' )
+ && ( pLine[i] < 'a' || pLine[i] > 'f' )
+ && ( pLine[i] < 'A' || pLine[i] > 'F' ) ) )
+ {
+ OSL_FAIL( "Wrong index syntax!" );
+ return aResult;
+ }
+
+ aNum += OUStringChar( pLine[i] );
+ }
+
+ newItem.append( sal_Unicode( aNum.toUInt32( 16 ) ) );
+ pLine += 3;
+ }
+
+ aResult.push_back( newItem.makeStringAndClear() );
+ } while( pLine[0] == '_' && pLine[1] == '_' );
+
+ if( *pLine )
+ OSL_FAIL( "Wrong index syntax!" );
+
+ return aResult;
+}
+
+
+static bool shorterUrl( OUString& aURL )
+{
+ sal_Int32 aInd = aURL.lastIndexOf( '/' );
+ if( aInd > 0 && aURL.indexOf( "://" ) != aInd-2 )
+ {
+ aURL = aURL.copy( 0, aInd );
+ return true;
+ }
+
+ return false;
+}
+
+
+static OUString getAsciiLine( const ::rtl::ByteSequence& buf )
+{
+ OUString aResult;
+
+ ::rtl::ByteSequence outbuf( buf.getLength()*2+1 );
+
+ for( int ind = 0; ind < buf.getLength(); ind++ )
+ {
+ outbuf[ind*2] = ( static_cast<sal_uInt8>(buf[ind]) >> 4 ) + 'a';
+ outbuf[ind*2+1] = ( static_cast<sal_uInt8>(buf[ind]) & 0x0f ) + 'a';
+ }
+ outbuf[buf.getLength()*2] = '\0';
+
+ aResult = OUString::createFromAscii( reinterpret_cast<char*>(outbuf.getArray()) );
+
+ return aResult;
+}
+
+
+static ::rtl::ByteSequence getBufFromAsciiLine( std::u16string_view line )
+{
+ OSL_ENSURE( line.size() % 2 == 0, "Wrong syntax!" );
+ OString tmpLine = OUStringToOString( line, RTL_TEXTENCODING_ASCII_US );
+ ::rtl::ByteSequence aResult(line.size()/2);
+
+ for( int ind = 0; ind < tmpLine.getLength()/2; ind++ )
+ {
+ aResult[ind] = ( static_cast<sal_uInt8>( tmpLine[ind*2] - 'a' ) << 4 ) | static_cast<sal_uInt8>( tmpLine[ind*2+1] - 'a' );
+ }
+
+ return aResult;
+}
+
+
+PasswordMap StorageItem::getInfo()
+{
+ PasswordMap aResult;
+
+ const Sequence< OUString > aNodeNames = ConfigItem::GetNodeNames( "Store" );
+ sal_Int32 aNodeCount = aNodeNames.getLength();
+ Sequence< OUString > aPropNames( aNodeCount * 2);
+
+ std::transform(aNodeNames.begin(), aNodeNames.end(), aPropNames.getArray(),
+ [](const OUString& rName) -> OUString {
+ return "Store/Passwordstorage['" + rName + "']/Password"; });
+ std::transform(aNodeNames.begin(), aNodeNames.end(), aPropNames.getArray() + aNodeCount,
+ [](const OUString& rName) -> OUString {
+ return "Store/Passwordstorage['" + rName + "']/InitializationVector"; });
+
+ Sequence< Any > aPropertyValues = ConfigItem::GetProperties( aPropNames );
+
+ if( aPropertyValues.getLength() != aNodeCount * 2)
+ {
+ OSL_FAIL( "Problems during reading" );
+ return aResult;
+ }
+
+ for( sal_Int32 aNodeInd = 0; aNodeInd < aNodeCount; ++aNodeInd )
+ {
+ std::vector< OUString > aUrlUsr = getInfoFromInd( aNodeNames[aNodeInd] );
+
+ if( aUrlUsr.size() == 2 )
+ {
+ OUString aUrl = aUrlUsr[0];
+ OUString aName = aUrlUsr[1];
+
+ OUString aEPasswd;
+ OUString aIV;
+ aPropertyValues[aNodeInd] >>= aEPasswd;
+ aPropertyValues[aNodeInd + aNodeCount] >>= aIV;
+
+ PasswordMap::iterator aIter = aResult.find( aUrl );
+ if( aIter != aResult.end() )
+ aIter->second.emplace_back( aName, aEPasswd, aIV );
+ else
+ {
+ NamePasswordRecord aNewRecord( aName, aEPasswd, aIV );
+ std::vector< NamePasswordRecord > listToAdd( 1, aNewRecord );
+
+ aResult.insert( PairUrlRecord( aUrl, listToAdd ) );
+ }
+ }
+ else
+ OSL_FAIL( "Wrong index syntax!" );
+ }
+
+ return aResult;
+}
+
+
+void StorageItem::setUseStorage( bool bUse )
+{
+ ConfigItem::SetModified();
+ ConfigItem::PutProperties( { "UseStorage" }, { uno::Any(bUse) } );
+}
+
+
+bool StorageItem::useStorage()
+{
+ Sequence<OUString> aNodeNames { "UseStorage" };
+
+ Sequence< Any > aPropertyValues = ConfigItem::GetProperties( aNodeNames );
+
+ if( aPropertyValues.getLength() != aNodeNames.getLength() )
+ {
+ OSL_FAIL( "Problems during reading" );
+ return false;
+ }
+
+ bool aResult = false;
+ aPropertyValues[0] >>= aResult;
+
+ return aResult;
+}
+
+
+sal_Int32 StorageItem::getStorageVersion()
+{
+ Sequence<OUString> aNodeNames { "StorageVersion" };
+
+ Sequence< Any > aPropertyValues = ConfigItem::GetProperties( aNodeNames );
+
+ if( aPropertyValues.getLength() != aNodeNames.getLength() )
+ {
+ OSL_FAIL( "Problems during reading" );
+ return 0;
+ }
+
+ sal_Int32 nResult = 0;
+ aPropertyValues[0] >>= nResult;
+
+ return nResult;
+}
+
+bool StorageItem::getEncodedMasterPassword( OUString& aResult, OUString& aResultIV )
+{
+ if( hasEncoded )
+ {
+ aResult = mEncoded;
+ aResultIV = mEncodedIV;
+ return true;
+ }
+
+ Sequence< OUString > aNodeNames{ "HasMaster", "Master", "MasterInitializationVector" };
+
+ Sequence< Any > aPropertyValues = ConfigItem::GetProperties( aNodeNames );
+
+ if( aPropertyValues.getLength() != aNodeNames.getLength() )
+ {
+ OSL_FAIL( "Problems during reading" );
+ return false;
+ }
+
+ aPropertyValues[0] >>= hasEncoded;
+ aPropertyValues[1] >>= mEncoded;
+ aPropertyValues[2] >>= mEncodedIV;
+
+ aResult = mEncoded;
+ aResultIV = mEncodedIV;
+
+ return hasEncoded;
+}
+
+
+void StorageItem::setEncodedMasterPassword( const OUString& aEncoded, const OUString& aEncodedIV, bool bAcceptEmpty )
+{
+ bool bHasMaster = ( !aEncoded.isEmpty() || bAcceptEmpty );
+
+ ConfigItem::SetModified();
+ ConfigItem::PutProperties( { "HasMaster", "Master", "MasterInitializationVector", "StorageVersion" },
+ { uno::Any(bHasMaster), uno::Any(aEncoded),
+ uno::Any(aEncodedIV), uno::Any(nCurrentStorageVersion) } );
+
+ hasEncoded = bHasMaster;
+ mEncoded = aEncoded;
+ mEncodedIV = aEncodedIV;
+}
+
+
+void StorageItem::remove( const OUString& aURL, const OUString& aName )
+{
+ Sequence< OUString > sendSeq { createIndex( { aURL, aName } ) };
+
+ ConfigItem::ClearNodeElements( "Store", sendSeq );
+}
+
+
+void StorageItem::clear()
+{
+ ConfigItem::ClearNodeSet( "Store" );
+}
+
+
+void StorageItem::update( const OUString& aURL, const NamePasswordRecord& aRecord )
+{
+ if ( !aRecord.HasPasswords( PERSISTENT_RECORD ) )
+ {
+ OSL_FAIL( "Unexpected storing of a record!" );
+ return;
+ }
+
+ Sequence< beans::PropertyValue > sendSeq{ comphelper::makePropertyValue(
+ "Store/Passwordstorage['" + createIndex( { aURL, aRecord.GetUserName() } ) + "']/InitializationVector",
+ aRecord.GetPersistentIV()), comphelper::makePropertyValue(
+ "Store/Passwordstorage['" + createIndex( { aURL, aRecord.GetUserName() } ) + "']/Password",
+ aRecord.GetPersistentPasswords()) };
+
+ ConfigItem::SetModified();
+ ConfigItem::SetSetProperties( "Store", sendSeq );
+}
+
+
+void StorageItem::Notify( const Sequence< OUString >& )
+{
+ // this feature still should not be used
+ if( mainCont )
+ mainCont->Notify();
+}
+
+
+void StorageItem::ImplCommit()
+{
+ // Do nothing, we stored everything we want already
+}
+
+
+PasswordContainer::PasswordContainer( const Reference<XComponentContext>& rxContext )
+{
+ // m_pStorageFile->Notify() can be called
+ std::unique_lock aGuard( mMutex );
+
+ mComponent.set( rxContext->getServiceManager(), UNO_QUERY );
+ mComponent->addEventListener( this );
+
+ m_xStorageFile.emplace( this, "Office.Common/Passwords" );
+ if( m_xStorageFile->useStorage() )
+ m_aContainer = m_xStorageFile->getInfo();
+}
+
+
+PasswordContainer::~PasswordContainer()
+{
+ std::unique_lock aGuard( mMutex );
+
+ m_xStorageFile.reset();
+
+ if( mComponent.is() )
+ {
+ mComponent->removeEventListener(this);
+ mComponent.clear();
+ }
+}
+
+void SAL_CALL PasswordContainer::disposing( const EventObject& )
+{
+ std::unique_lock aGuard( mMutex );
+
+ m_xStorageFile.reset();
+
+ if( mComponent.is() )
+ {
+ //mComponent->removeEventListener(this);
+ mComponent.clear();
+ }
+}
+
+std::vector< OUString > PasswordContainer::DecodePasswords( std::u16string_view aLine, std::u16string_view aIV, std::u16string_view aMasterPasswd, css::task::PasswordRequestMode mode )
+{
+ if( !aMasterPasswd.empty() )
+ {
+ rtlCipher aDecoder = rtl_cipher_create (rtl_Cipher_AlgorithmBF, rtl_Cipher_ModeStream );
+ OSL_ENSURE( aDecoder, "Can't create decoder" );
+
+ if( aDecoder )
+ {
+ OSL_ENSURE( aMasterPasswd.size() == RTL_DIGEST_LENGTH_MD5 * 2, "Wrong master password format!" );
+
+ unsigned char code[RTL_DIGEST_LENGTH_MD5];
+ for( int ind = 0; ind < RTL_DIGEST_LENGTH_MD5; ind++ )
+ code[ ind ] = static_cast<char>(o3tl::toUInt32(aMasterPasswd.substr( ind*2, 2 ), 16));
+
+ unsigned char iv[RTL_DIGEST_LENGTH_MD5] = {0};
+ if (!aIV.empty())
+ {
+ for( int ind = 0; ind < RTL_DIGEST_LENGTH_MD5; ind++ )
+ {
+ auto tmp = aIV.substr( ind*2, 2 );
+ iv[ ind ] = static_cast<char>(rtl_ustr_toInt64_WithLength(tmp.data(), 16, tmp.size()));
+ }
+ }
+
+ rtlCipherError result = rtl_cipher_init (
+ aDecoder, rtl_Cipher_DirectionDecode,
+ code, RTL_DIGEST_LENGTH_MD5, iv, RTL_DIGEST_LENGTH_MD5 );
+
+ if( result == rtl_Cipher_E_None )
+ {
+ ::rtl::ByteSequence aSeq = getBufFromAsciiLine( aLine );
+
+ ::rtl::ByteSequence resSeq( aSeq.getLength() );
+
+ rtl_cipher_decode ( aDecoder, aSeq.getArray(), aSeq.getLength(),
+ reinterpret_cast<sal_uInt8*>(resSeq.getArray()), resSeq.getLength() );
+
+ OUString aPasswd( reinterpret_cast<char*>(resSeq.getArray()), resSeq.getLength(), RTL_TEXTENCODING_UTF8 );
+
+ rtl_cipher_destroy (aDecoder);
+
+ return getInfoFromInd( aPasswd );
+ }
+
+ rtl_cipher_destroy (aDecoder);
+ }
+ }
+ else
+ {
+ OSL_FAIL( "No master password provided!" );
+ // throw special exception
+ }
+
+ // problems with decoding
+ OSL_FAIL( "Problem with decoding" );
+ throw css::task::NoMasterException(
+ "Can't decode!", css::uno::Reference<css::uno::XInterface>(), mode);
+}
+
+OUString PasswordContainer::EncodePasswords(const std::vector< OUString >& lines, std::u16string_view aIV, std::u16string_view aMasterPasswd)
+{
+ if( !aMasterPasswd.empty() )
+ {
+ OString aSeq = OUStringToOString( createIndex( lines ), RTL_TEXTENCODING_UTF8 );
+
+ rtlCipher aEncoder = rtl_cipher_create (rtl_Cipher_AlgorithmBF, rtl_Cipher_ModeStream );
+ OSL_ENSURE( aEncoder, "Can't create encoder" );
+
+ if( aEncoder )
+ {
+ OSL_ENSURE( aMasterPasswd.size() == RTL_DIGEST_LENGTH_MD5 * 2, "Wrong master password format!" );
+
+ unsigned char code[RTL_DIGEST_LENGTH_MD5];
+ for( int ind = 0; ind < RTL_DIGEST_LENGTH_MD5; ind++ )
+ code[ ind ] = static_cast<char>(o3tl::toUInt32(aMasterPasswd.substr( ind*2, 2 ), 16));
+
+ unsigned char iv[RTL_DIGEST_LENGTH_MD5] = {0};
+ if (!aIV.empty())
+ {
+ for( int ind = 0; ind < RTL_DIGEST_LENGTH_MD5; ind++ )
+ {
+ auto tmp = aIV.substr( ind*2, 2 );
+ iv[ ind ] = static_cast<char>(rtl_ustr_toInt64_WithLength(tmp.data(), 16, tmp.size()));
+ }
+ }
+
+ rtlCipherError result = rtl_cipher_init (
+ aEncoder, rtl_Cipher_DirectionEncode,
+ code, RTL_DIGEST_LENGTH_MD5, iv, RTL_DIGEST_LENGTH_MD5 );
+
+ if( result == rtl_Cipher_E_None )
+ {
+ ::rtl::ByteSequence resSeq(aSeq.getLength()+1);
+
+ result = rtl_cipher_encode ( aEncoder, aSeq.getStr(), aSeq.getLength()+1,
+ reinterpret_cast<sal_uInt8*>(resSeq.getArray()), resSeq.getLength() );
+
+/*
+ //test
+ rtlCipherError result = rtl_cipher_init (
+ aEncoder, rtl_Cipher_DirectionDecode,
+ code, RTL_DIGEST_LENGTH_MD5, NULL, 0 );
+
+
+ if( result == rtl_Cipher_E_None )
+ {
+ OUString testOU = getAsciiLine( resSeq );
+ ::rtl::ByteSequence aSeq1 = getBufFromAsciiLine( testOU );
+
+ ::rtl::ByteSequence resSeq1( aSeq1.getLength() );
+
+ if( resSeq.getLength() == aSeq1.getLength() )
+ {
+ for( int ind = 0; ind < aSeq1.getLength(); ind++ )
+ if( resSeq[ind] != aSeq1[ind] )
+ testOU = "";
+ }
+
+ result = rtl_cipher_decode ( aEncoder, (sal_uInt8*)aSeq1.getArray(), aSeq1.getLength(),
+ (sal_uInt8*)resSeq1.getArray(), resSeq1.getLength() );
+
+ OUString aPasswd( ( char* )resSeq1.getArray(), resSeq1.getLength(), RTL_TEXTENCODING_UTF8 );
+ }
+*/
+
+ rtl_cipher_destroy (aEncoder);
+
+ if( result == rtl_Cipher_E_None )
+ return getAsciiLine( resSeq );
+
+ }
+
+ rtl_cipher_destroy (aEncoder);
+ }
+ }
+ else
+ {
+ OSL_FAIL( "No master password provided!" );
+ // throw special exception
+ }
+
+ // problems with encoding
+ OSL_FAIL( "Problem with encoding" );
+ throw RuntimeException("Can't encode!" );
+}
+
+void PasswordContainer::UpdateVector( const OUString& aURL, std::vector< NamePasswordRecord >& toUpdate, NamePasswordRecord const & aRecord, bool writeFile )
+{
+ for (auto & aNPIter : toUpdate)
+ if( aNPIter.GetUserName() == aRecord.GetUserName() )
+ {
+ if( aRecord.HasPasswords( MEMORY_RECORD ) )
+ aNPIter.SetMemoryPasswords( aRecord.GetMemoryPasswords() );
+
+ if( aRecord.HasPasswords( PERSISTENT_RECORD ) )
+ {
+ aNPIter.SetPersistentPasswords( aRecord.GetPersistentPasswords(), aRecord.GetPersistentIV() );
+
+ if( writeFile )
+ {
+ // the password must be already encoded
+ m_xStorageFile->update( aURL, aRecord ); // change existing ( aURL, aName ) record in the configfile
+ }
+ }
+
+ return;
+ }
+
+
+ if( aRecord.HasPasswords( PERSISTENT_RECORD ) && writeFile )
+ {
+ // the password must be already encoded
+ m_xStorageFile->update( aURL, aRecord ); // add new aName to the existing url
+ }
+
+ toUpdate.insert( toUpdate.begin(), aRecord );
+}
+
+
+UserRecord PasswordContainer::CopyToUserRecord( const NamePasswordRecord& aRecord, bool& io_bTryToDecode, const Reference< XInteractionHandler >& aHandler )
+{
+ ::std::vector< OUString > aPasswords;
+ if( aRecord.HasPasswords( MEMORY_RECORD ) )
+ aPasswords = aRecord.GetMemoryPasswords();
+
+ if( io_bTryToDecode && aRecord.HasPasswords( PERSISTENT_RECORD ) )
+ {
+ try
+ {
+ ::std::vector< OUString > aDecodedPasswords = DecodePasswords( aRecord.GetPersistentPasswords(), aRecord.GetPersistentIV(),
+ GetMasterPassword( aHandler ), css::task::PasswordRequestMode_PASSWORD_ENTER );
+ aPasswords.insert( aPasswords.end(), aDecodedPasswords.begin(), aDecodedPasswords.end() );
+ }
+ catch( NoMasterException& )
+ {
+ // if master password could not be detected the entry will be just ignored
+ io_bTryToDecode = false;
+ }
+ }
+
+ return UserRecord( aRecord.GetUserName(), comphelper::containerToSequence( aPasswords ) );
+}
+
+
+Sequence< UserRecord > PasswordContainer::CopyToUserRecordSequence( const std::vector< NamePasswordRecord >& original, const Reference< XInteractionHandler >& aHandler )
+{
+ Sequence< UserRecord > aResult( original.size() );
+ auto aResultRange = asNonConstRange(aResult);
+ sal_uInt32 nInd = 0;
+ bool bTryToDecode = true;
+
+ for (auto const& aNPIter : original)
+ {
+ aResultRange[nInd] = CopyToUserRecord( aNPIter, bTryToDecode, aHandler );
+ ++nInd;
+ }
+
+ return aResult;
+}
+
+
+void SAL_CALL PasswordContainer::add( const OUString& Url, const OUString& UserName, const Sequence< OUString >& Passwords, const Reference< XInteractionHandler >& aHandler )
+{
+ std::unique_lock aGuard( mMutex );
+
+ PrivateAdd( Url, UserName, Passwords, MEMORY_RECORD, aHandler );
+}
+
+
+void SAL_CALL PasswordContainer::addPersistent( const OUString& Url, const OUString& UserName, const Sequence< OUString >& Passwords, const Reference< XInteractionHandler >& aHandler )
+{
+ std::unique_lock aGuard( mMutex );
+
+ PrivateAdd( Url, UserName, Passwords, PERSISTENT_RECORD, aHandler );
+}
+
+OUString PasswordContainer::createIV()
+{
+ rtlRandomPool randomPool = mRandomPool.get();
+ unsigned char iv[RTL_DIGEST_LENGTH_MD5];
+ rtl_random_getBytes(randomPool, iv, RTL_DIGEST_LENGTH_MD5);
+ OUStringBuffer aBuffer;
+ for (sal_uInt8 i : iv)
+ {
+ aBuffer.append(OUString::number(i >> 4, 16) + OUString::number(i & 15, 16));
+ }
+ return aBuffer.makeStringAndClear();
+}
+
+void PasswordContainer::PrivateAdd( const OUString& Url, const OUString& UserName, const Sequence< OUString >& Passwords, char Mode, const Reference< XInteractionHandler >& aHandler )
+{
+ NamePasswordRecord aRecord( UserName );
+ ::std::vector< OUString > aStorePass = comphelper::sequenceToContainer< std::vector<OUString> >( Passwords );
+
+ if( Mode == PERSISTENT_RECORD )
+ {
+ OUString sIV = createIV();
+ OUString sEncodedPasswords = EncodePasswords(aStorePass, sIV, GetMasterPassword(aHandler));
+ aRecord.SetPersistentPasswords(sEncodedPasswords, sIV);
+ }
+ else if( Mode == MEMORY_RECORD )
+ aRecord.SetMemoryPasswords( std::move(aStorePass) );
+ else
+ {
+ OSL_FAIL( "Unexpected persistence status!" );
+ return;
+ }
+
+ if( !m_aContainer.empty() )
+ {
+ PasswordMap::iterator aIter = m_aContainer.find( Url );
+
+ if( aIter != m_aContainer.end() )
+ {
+ UpdateVector( aIter->first, aIter->second, aRecord, true );
+ return;
+ }
+ }
+
+ std::vector< NamePasswordRecord > listToAdd( 1, aRecord );
+ m_aContainer.insert( PairUrlRecord( Url, listToAdd ) );
+
+ if( Mode == PERSISTENT_RECORD && m_xStorageFile && m_xStorageFile->useStorage() )
+ m_xStorageFile->update( Url, aRecord );
+
+}
+
+
+UrlRecord SAL_CALL PasswordContainer::find( const OUString& aURL, const Reference< XInteractionHandler >& aHandler )
+{
+ return find( aURL, u"", false, aHandler );
+}
+
+
+UrlRecord SAL_CALL PasswordContainer::findForName( const OUString& aURL, const OUString& aName, const Reference< XInteractionHandler >& aHandler )
+{
+ return find( aURL, aName, true, aHandler );
+}
+
+
+Sequence< UserRecord > PasswordContainer::FindUsr( const std::vector< NamePasswordRecord >& userlist, std::u16string_view aName, const Reference< XInteractionHandler >& aHandler )
+{
+ for (auto const& aNPIter : userlist)
+ {
+ if( aNPIter.GetUserName() == aName )
+ {
+ bool bTryToDecode = true;
+ Sequence< UserRecord > aResult { CopyToUserRecord( aNPIter, bTryToDecode, aHandler ) };
+
+ return aResult;
+ }
+ }
+
+ return Sequence< UserRecord >();
+}
+
+
+bool PasswordContainer::createUrlRecord(
+ const PasswordMap::iterator & rIter,
+ bool bName,
+ std::u16string_view aName,
+ const Reference< XInteractionHandler >& aHandler,
+ UrlRecord & rRec )
+{
+ if ( bName )
+ {
+ Sequence< UserRecord > aUsrRec
+ = FindUsr( rIter->second, aName, aHandler );
+ if( aUsrRec.hasElements() )
+ {
+ rRec = UrlRecord( rIter->first, aUsrRec );
+ return true;
+ }
+ }
+ else
+ {
+ rRec = UrlRecord(
+ rIter->first,
+ CopyToUserRecordSequence( rIter->second, aHandler ) );
+ return true;
+ }
+ return false;
+}
+
+
+UrlRecord PasswordContainer::find(
+ const OUString& aURL,
+ std::u16string_view aName,
+ bool bName, // only needed to support empty user names
+ const Reference< XInteractionHandler >& aHandler )
+{
+ std::unique_lock aGuard( mMutex );
+
+ if( !m_aContainer.empty() && !aURL.isEmpty() )
+ {
+ OUString aUrl( aURL );
+
+ // each iteration remove last '/...' section from the aUrl
+ // while it's possible, up to the most left '://'
+ do
+ {
+ // first look for <url>/somename and then look for <url>/somename/...
+ PasswordMap::iterator aIter = m_aContainer.find( aUrl );
+ if( aIter != m_aContainer.end() )
+ {
+ UrlRecord aRec;
+ if ( createUrlRecord( aIter, bName, aName, aHandler, aRec ) )
+ return aRec;
+ }
+ else
+ {
+ OUString tmpUrl( aUrl );
+ if ( !tmpUrl.endsWith("/") )
+ tmpUrl += "/";
+
+ aIter = m_aContainer.lower_bound( tmpUrl );
+ if( aIter != m_aContainer.end() && aIter->first.match( tmpUrl ) )
+ {
+ UrlRecord aRec;
+ if ( createUrlRecord( aIter, bName, aName, aHandler, aRec ) )
+ return aRec;
+ }
+ }
+ }
+ while( shorterUrl( aUrl ) && !aUrl.isEmpty() );
+ }
+
+ return UrlRecord();
+}
+
+OUString PasswordContainer::GetDefaultMasterPassword()
+{
+ OUStringBuffer aResult;
+ for ( sal_Int32 nInd = 0; nInd < RTL_DIGEST_LENGTH_MD5; nInd++ )
+ aResult.append("aa");
+
+ return aResult.makeStringAndClear();
+}
+
+OUString PasswordContainer::RequestPasswordFromUser( PasswordRequestMode aRMode, const uno::Reference< task::XInteractionHandler >& xHandler )
+{
+ // empty string means that the call was cancelled or just failed
+ OUString aResult;
+
+ if ( xHandler.is() )
+ {
+ ::rtl::Reference< MasterPasswordRequest_Impl > xRequest = new MasterPasswordRequest_Impl( aRMode );
+
+ xHandler->handle( xRequest );
+
+ ::rtl::Reference< ucbhelper::InteractionContinuation > xSelection = xRequest->getSelection();
+
+ if ( xSelection.is() )
+ {
+ Reference< XInteractionAbort > xAbort( xSelection.get(), UNO_QUERY );
+ if ( !xAbort.is() )
+ {
+ const ::rtl::Reference< ucbhelper::InteractionSupplyAuthentication > & xSupp
+ = xRequest->getAuthenticationSupplier();
+
+ aResult = xSupp->getPassword();
+ }
+ }
+ }
+
+ return aResult;
+}
+
+// Mangle the key to match an old bug
+static OUString ReencodeAsOldHash(std::u16string_view rPass)
+{
+ OUStringBuffer aBuffer;
+ for (int ind = 0; ind < RTL_DIGEST_LENGTH_MD5; ++ind)
+ {
+ auto tmp = rPass.substr(ind * 2, 2);
+ unsigned char i = static_cast<char>(rtl_ustr_toInt64_WithLength(tmp.data(), 16, tmp.size()));
+ aBuffer.append(OUStringChar(static_cast< sal_Unicode >('a' + (i >> 4)))
+ + OUStringChar(static_cast< sal_Unicode >('a' + (i & 15))));
+ }
+ return aBuffer.makeStringAndClear();
+}
+
+OUString const & PasswordContainer::GetMasterPassword( const Reference< XInteractionHandler >& aHandler )
+{
+ PasswordRequestMode aRMode = PasswordRequestMode_PASSWORD_ENTER;
+ if( !m_xStorageFile || !m_xStorageFile->useStorage() )
+ throw NoMasterException("Password storing is not active!", Reference< XInterface >(), aRMode );
+
+ if( m_aMasterPassword.isEmpty() && aHandler.is() )
+ {
+ OUString aEncodedMP, aEncodedMPIV;
+ bool bDefaultPassword = false;
+
+ if( !m_xStorageFile->getEncodedMasterPassword( aEncodedMP, aEncodedMPIV ) )
+ aRMode = PasswordRequestMode_PASSWORD_CREATE;
+ else if ( aEncodedMP.isEmpty() )
+ {
+ m_aMasterPassword = GetDefaultMasterPassword();
+ bDefaultPassword = true;
+ }
+
+ if ( !bDefaultPassword )
+ {
+ bool bAskAgain = false;
+ do {
+ bAskAgain = false;
+
+ OUString aPass = RequestPasswordFromUser( aRMode, aHandler );
+ if ( !aPass.isEmpty() )
+ {
+ if( aRMode == PasswordRequestMode_PASSWORD_CREATE )
+ {
+ m_aMasterPassword = aPass;
+ std::vector< OUString > aMaster( 1, m_aMasterPassword );
+
+ OUString sIV = createIV();
+ m_xStorageFile->setEncodedMasterPassword(EncodePasswords(aMaster, sIV, m_aMasterPassword), sIV);
+ }
+ else
+ {
+ if (m_xStorageFile->getStorageVersion() == 0)
+ aPass = ReencodeAsOldHash(aPass);
+
+ std::vector< OUString > aRM( DecodePasswords( aEncodedMP, aEncodedMPIV, aPass, aRMode ) );
+ if( aRM.empty() || aPass != aRM[0] )
+ {
+ bAskAgain = true;
+ aRMode = PasswordRequestMode_PASSWORD_REENTER;
+ }
+ else
+ m_aMasterPassword = aPass;
+ }
+ }
+
+ } while( bAskAgain );
+ }
+ }
+
+ if ( m_aMasterPassword.isEmpty() )
+ throw NoMasterException("No master password!", Reference< XInterface >(), aRMode );
+
+ return m_aMasterPassword;
+}
+
+
+void SAL_CALL PasswordContainer::remove( const OUString& aURL, const OUString& aName )
+{
+ std::unique_lock aGuard( mMutex );
+
+ OUString aUrl( aURL );
+ if( m_aContainer.empty() )
+ return;
+
+ PasswordMap::iterator aIter = m_aContainer.find( aUrl );
+
+ if( aIter == m_aContainer.end() )
+ {
+ if( aUrl.endsWith("/") )
+ aUrl = aUrl.copy( 0, aUrl.getLength() - 1 );
+ else
+ aUrl += "/";
+
+ aIter = m_aContainer.find( aUrl );
+ }
+
+ if( aIter == m_aContainer.end() )
+ return;
+
+ auto aNPIter = std::find_if(aIter->second.begin(), aIter->second.end(),
+ [&aName](const NamePasswordRecord& rNPRecord) { return rNPRecord.GetUserName() == aName; });
+
+ if (aNPIter != aIter->second.end())
+ {
+ if( aNPIter->HasPasswords( PERSISTENT_RECORD ) && m_xStorageFile )
+ m_xStorageFile->remove( aURL, aName ); // remove record ( aURL, aName )
+
+ // the iterator will not be used any more so it can be removed directly
+ aIter->second.erase( aNPIter );
+
+ if( aIter->second.empty() )
+ m_aContainer.erase( aIter );
+ }
+}
+
+
+void SAL_CALL PasswordContainer::removePersistent( const OUString& aURL, const OUString& aName )
+{
+ std::unique_lock aGuard( mMutex );
+
+ OUString aUrl( aURL );
+ if( m_aContainer.empty() )
+ return;
+
+ PasswordMap::iterator aIter = m_aContainer.find( aUrl );
+
+ if( aIter == m_aContainer.end() )
+ {
+ if( aUrl.endsWith("/") )
+ aUrl = aUrl.copy( 0, aUrl.getLength() - 1 );
+ else
+ aUrl += "/";
+
+ aIter = m_aContainer.find( aUrl );
+ }
+
+ if( aIter == m_aContainer.end() )
+ return;
+
+ auto aNPIter = std::find_if(aIter->second.begin(), aIter->second.end(),
+ [&aName](const NamePasswordRecord& rNPRecord) { return rNPRecord.GetUserName() == aName; });
+
+ if (aNPIter == aIter->second.end())
+ return;
+
+ if( aNPIter->HasPasswords( PERSISTENT_RECORD ) )
+ {
+ // TODO/LATER: should the password be converted to MemoryPassword?
+ aNPIter->RemovePasswords( PERSISTENT_RECORD );
+
+ if ( m_xStorageFile )
+ m_xStorageFile->remove( aURL, aName ); // remove record ( aURL, aName )
+ }
+
+ if( !aNPIter->HasPasswords( MEMORY_RECORD ) )
+ aIter->second.erase( aNPIter );
+
+ if( aIter->second.empty() )
+ m_aContainer.erase( aIter );
+}
+
+void SAL_CALL PasswordContainer::removeAllPersistent()
+{
+ std::unique_lock aGuard(mMutex);
+ removeAllPersistent(aGuard);
+}
+
+void PasswordContainer::removeAllPersistent(std::unique_lock<std::mutex>& /*rGuard*/)
+{
+ if( m_xStorageFile )
+ m_xStorageFile->clear();
+
+ for( PasswordMap::iterator aIter = m_aContainer.begin(); aIter != m_aContainer.end(); )
+ {
+ for( std::vector< NamePasswordRecord >::iterator aNPIter = aIter->second.begin(); aNPIter != aIter->second.end(); )
+ {
+ if( aNPIter->HasPasswords( PERSISTENT_RECORD ) )
+ {
+ // TODO/LATER: should the password be converted to MemoryPassword?
+ aNPIter->RemovePasswords( PERSISTENT_RECORD );
+
+ if ( m_xStorageFile )
+ m_xStorageFile->remove( aIter->first, aNPIter->GetUserName() ); // remove record ( aURL, aName )
+ }
+
+ if( !aNPIter->HasPasswords( MEMORY_RECORD ) )
+ {
+ aNPIter = aIter->second.erase(aNPIter);
+ }
+ else
+ ++aNPIter;
+ }
+
+ if( aIter->second.empty() )
+ {
+ aIter = m_aContainer.erase(aIter);
+ }
+ else
+ ++aIter;
+ }
+}
+
+Sequence< UrlRecord > SAL_CALL PasswordContainer::getAllPersistent( const Reference< XInteractionHandler >& xHandler )
+{
+ std::unique_lock aGuard( mMutex );
+ return getAllPersistent(aGuard, xHandler);
+}
+
+Sequence< UrlRecord > PasswordContainer::getAllPersistent( std::unique_lock<std::mutex>& /*rGuard*/, const Reference< XInteractionHandler >& xHandler )
+{
+ Sequence< UrlRecord > aResult;
+
+ for( const auto& rEntry : m_aContainer )
+ {
+ Sequence< UserRecord > aUsers;
+ for (auto const& aNP : rEntry.second)
+ if( aNP.HasPasswords( PERSISTENT_RECORD ) )
+ {
+ sal_Int32 oldLen = aUsers.getLength();
+ aUsers.realloc( oldLen + 1 );
+ aUsers.getArray()[ oldLen ] = UserRecord( aNP.GetUserName(), comphelper::containerToSequence( DecodePasswords( aNP.GetPersistentPasswords(), aNP.GetPersistentIV(),
+ GetMasterPassword( xHandler ), css::task::PasswordRequestMode_PASSWORD_ENTER ) ) );
+ }
+
+ if( aUsers.hasElements() )
+ {
+ sal_Int32 oldLen = aResult.getLength();
+ aResult.realloc( oldLen + 1 );
+ aResult.getArray()[ oldLen ] = UrlRecord( rEntry.first, aUsers );
+ }
+ }
+
+ return aResult;
+}
+
+sal_Bool SAL_CALL PasswordContainer::authorizateWithMasterPassword( const uno::Reference< task::XInteractionHandler >& xHandler )
+{
+ std::unique_lock aGuard( mMutex );
+ return authorizateWithMasterPassword(aGuard, xHandler);
+}
+
+bool PasswordContainer::authorizateWithMasterPassword( std::unique_lock<std::mutex>& /*rGuard*/, const uno::Reference< task::XInteractionHandler >& xHandler )
+{
+ bool bResult = false;
+ OUString aEncodedMP, aEncodedMPIV;
+ uno::Reference< task::XInteractionHandler > xTmpHandler = xHandler;
+
+ // the method should fail if there is no master password
+ if( m_xStorageFile && m_xStorageFile->useStorage() && m_xStorageFile->getEncodedMasterPassword( aEncodedMP, aEncodedMPIV ) )
+ {
+ if ( aEncodedMP.isEmpty() )
+ {
+ // this is a default master password
+ // no UI is necessary
+ bResult = true;
+ }
+ else
+ {
+ if ( !xTmpHandler.is() )
+ {
+ uno::Reference< lang::XMultiServiceFactory > xFactory( mComponent, uno::UNO_QUERY_THROW );
+ uno::Reference< uno::XComponentContext > xContext( comphelper::getComponentContext(xFactory) );
+ xTmpHandler.set( InteractionHandler::createWithParent(xContext, nullptr), uno::UNO_QUERY_THROW );
+ }
+
+ if ( !m_aMasterPassword.isEmpty() )
+ {
+ // there is a password, it should be just rechecked
+ PasswordRequestMode aRMode = PasswordRequestMode_PASSWORD_ENTER;
+ OUString aPass;
+
+ do {
+ aPass = RequestPasswordFromUser( aRMode, xTmpHandler );
+
+ if (!aPass.isEmpty() && m_xStorageFile->getStorageVersion() == 0)
+ {
+ aPass = ReencodeAsOldHash(aPass);
+ }
+
+ bResult = ( !aPass.isEmpty() && aPass == m_aMasterPassword );
+ aRMode = PasswordRequestMode_PASSWORD_REENTER; // further questions with error notification
+ } while( !bResult && !aPass.isEmpty() );
+ }
+ else
+ {
+ try
+ {
+ // ask for the password, if user provide no correct password an exception will be thrown
+ bResult = !GetMasterPassword( xTmpHandler ).isEmpty();
+ }
+ catch( uno::Exception& )
+ {}
+ }
+ }
+ }
+
+ return bResult;
+}
+
+sal_Bool SAL_CALL PasswordContainer::changeMasterPassword( const uno::Reference< task::XInteractionHandler >& xHandler )
+{
+ bool bResult = false;
+ uno::Reference< task::XInteractionHandler > xTmpHandler = xHandler;
+ std::unique_lock aGuard( mMutex );
+
+ if ( m_xStorageFile && m_xStorageFile->useStorage() )
+ {
+ if ( !xTmpHandler.is() )
+ {
+ uno::Reference< lang::XMultiServiceFactory > xFactory( mComponent, uno::UNO_QUERY_THROW );
+ uno::Reference< uno::XComponentContext > xContext( comphelper::getComponentContext(xFactory) );
+ xTmpHandler.set( InteractionHandler::createWithParent(xContext, nullptr), uno::UNO_QUERY_THROW );
+ }
+
+ bool bCanChangePassword = true;
+ // if there is already a stored master password it should be entered by the user before the change happen
+ OUString aEncodedMP, aEncodedMPIV;
+ if( !m_aMasterPassword.isEmpty() || m_xStorageFile->getEncodedMasterPassword( aEncodedMP, aEncodedMPIV ) )
+ bCanChangePassword = authorizateWithMasterPassword( aGuard, xTmpHandler );
+
+ if ( bCanChangePassword )
+ {
+ // ask for the new password, but do not set it
+ OUString aPass = RequestPasswordFromUser( PasswordRequestMode_PASSWORD_CREATE, xTmpHandler );
+
+ if ( !aPass.isEmpty() )
+ {
+ // get all the persistent entries if it is possible
+ const Sequence< UrlRecord > aPersistent = getAllPersistent( aGuard, uno::Reference< task::XInteractionHandler >() );
+
+ // remove the master password and the entries persistence
+ removeMasterPassword(aGuard);
+
+ // store the new master password
+ m_aMasterPassword = aPass;
+ std::vector< OUString > aMaster( 1, m_aMasterPassword );
+ OUString aIV = createIV();
+ m_xStorageFile->setEncodedMasterPassword(EncodePasswords(aMaster, aIV, m_aMasterPassword), aIV);
+
+ // store all the entries with the new password
+ for ( const auto& rURL : aPersistent )
+ for ( const auto& rUser : rURL.UserList )
+ PrivateAdd( rURL.Url, rUser.UserName, rUser.Passwords, PERSISTENT_RECORD,
+ uno::Reference< task::XInteractionHandler >() );
+
+ bResult = true;
+ }
+ }
+ }
+
+ return bResult;
+}
+
+void SAL_CALL PasswordContainer::removeMasterPassword()
+{
+ std::unique_lock aGuard(mMutex);
+ removeMasterPassword(aGuard);
+}
+
+void PasswordContainer::removeMasterPassword(std::unique_lock<std::mutex>& rGuard)
+{
+ // remove all the stored passwords and the master password
+ removeAllPersistent(rGuard);
+
+ if ( m_xStorageFile )
+ {
+ m_aMasterPassword.clear();
+ m_xStorageFile->setEncodedMasterPassword( OUString(), OUString() ); // let the master password be removed from configuration
+ }
+}
+
+sal_Bool SAL_CALL PasswordContainer::hasMasterPassword( )
+{
+ std::unique_lock aGuard( mMutex );
+
+ if ( !m_xStorageFile )
+ throw uno::RuntimeException();
+
+ OUString aEncodedMP, aEncodedMPIV;
+ return ( m_xStorageFile->useStorage() && m_xStorageFile->getEncodedMasterPassword( aEncodedMP, aEncodedMPIV ) );
+}
+
+sal_Bool SAL_CALL PasswordContainer::allowPersistentStoring( sal_Bool bAllow )
+{
+ std::unique_lock aGuard( mMutex );
+
+ if ( !m_xStorageFile )
+ throw uno::RuntimeException();
+
+ if ( !bAllow )
+ removeMasterPassword(aGuard);
+
+ if (m_xStorageFile->useStorage() == static_cast<bool>(bAllow))
+ return bAllow;
+
+ m_xStorageFile->setUseStorage( bAllow );
+ return !bAllow;
+}
+
+sal_Bool SAL_CALL PasswordContainer::isPersistentStoringAllowed()
+{
+ std::unique_lock aGuard( mMutex );
+
+ if ( !m_xStorageFile )
+ throw uno::RuntimeException();
+
+ return m_xStorageFile->useStorage();
+}
+
+sal_Bool SAL_CALL PasswordContainer::useDefaultMasterPassword( const uno::Reference< task::XInteractionHandler >& xHandler )
+{
+ bool bResult = false;
+ uno::Reference< task::XInteractionHandler > xTmpHandler = xHandler;
+ std::unique_lock aGuard( mMutex );
+
+ if ( m_xStorageFile && m_xStorageFile->useStorage() )
+ {
+ if ( !xTmpHandler.is() )
+ {
+ uno::Reference< lang::XMultiServiceFactory > xFactory( mComponent, uno::UNO_QUERY_THROW );
+ uno::Reference< uno::XComponentContext > xContext( comphelper::getComponentContext(xFactory) );
+ xTmpHandler.set( InteractionHandler::createWithParent(xContext, nullptr), uno::UNO_QUERY_THROW );
+ }
+
+ bool bCanChangePassword = true;
+ // if there is already a stored nondefault master password it should be entered by the user before the change happen
+ OUString aEncodedMP, aEncodedMPIV;
+ if( m_xStorageFile->getEncodedMasterPassword( aEncodedMP, aEncodedMPIV ) && !aEncodedMP.isEmpty() )
+ bCanChangePassword = authorizateWithMasterPassword( aGuard, xTmpHandler );
+
+ if ( bCanChangePassword )
+ {
+ // generate the default password
+ OUString aPass = GetDefaultMasterPassword();
+ if ( !aPass.isEmpty() )
+ {
+ // get all the persistent entries if it is possible
+ const Sequence< UrlRecord > aPersistent = getAllPersistent( aGuard, uno::Reference< task::XInteractionHandler >() );
+
+ // remove the master password and the entries persistence
+ removeMasterPassword(aGuard);
+
+ // store the empty string to flag the default master password
+ m_aMasterPassword = aPass;
+ m_xStorageFile->setEncodedMasterPassword( OUString(), OUString(), true );
+
+ // store all the entries with the new password
+ for ( const auto& rURL : aPersistent )
+ for ( const auto& rUser : rURL.UserList )
+ PrivateAdd( rURL.Url, rUser.UserName, rUser.Passwords, PERSISTENT_RECORD,
+ uno::Reference< task::XInteractionHandler >() );
+
+ bResult = true;
+ }
+ }
+ }
+
+ return bResult;
+
+}
+
+sal_Bool SAL_CALL PasswordContainer::isDefaultMasterPasswordUsed()
+{
+ std::unique_lock aGuard( mMutex );
+
+ if ( !m_xStorageFile )
+ throw uno::RuntimeException();
+
+ OUString aEncodedMP, aEncodedMPIV;
+ return ( m_xStorageFile->useStorage() && m_xStorageFile->getEncodedMasterPassword( aEncodedMP, aEncodedMPIV ) && aEncodedMP.isEmpty() );
+}
+
+
+void SAL_CALL PasswordContainer::addUrl( const OUString& Url, sal_Bool MakePersistent )
+{
+ mUrlContainer.add( Url, MakePersistent );
+}
+
+OUString SAL_CALL PasswordContainer::findUrl( const OUString& Url )
+{
+ return mUrlContainer.find( Url );
+}
+
+void SAL_CALL PasswordContainer::removeUrl( const OUString& Url )
+{
+ mUrlContainer.remove( Url );
+}
+
+uno::Sequence< OUString > SAL_CALL PasswordContainer::getUrls( sal_Bool OnlyPersistent )
+{
+ return mUrlContainer.list( OnlyPersistent );
+}
+
+
+void PasswordContainer::Notify()
+{
+ std::unique_lock aGuard( mMutex );
+
+ // remove the cached persistent values in the memory
+ for( auto& rEntry : m_aContainer )
+ {
+ for( std::vector< NamePasswordRecord >::iterator aNPIter = rEntry.second.begin(); aNPIter != rEntry.second.end(); )
+ {
+ if( aNPIter->HasPasswords( PERSISTENT_RECORD ) )
+ {
+ aNPIter->RemovePasswords( PERSISTENT_RECORD );
+
+ if ( m_xStorageFile )
+ m_xStorageFile->remove( rEntry.first, aNPIter->GetUserName() ); // remove record ( aURL, aName )
+ }
+
+ if( !aNPIter->HasPasswords( MEMORY_RECORD ) )
+ {
+ aNPIter = rEntry.second.erase(aNPIter);
+ }
+ else
+ ++aNPIter;
+ }
+ }
+
+ PasswordMap addon;
+ if( m_xStorageFile )
+ addon = m_xStorageFile->getInfo();
+
+ for( const auto& rEntry : addon )
+ {
+ PasswordMap::iterator aSearchIter = m_aContainer.find( rEntry.first );
+ if( aSearchIter != m_aContainer.end() )
+ for (auto const& aNP : rEntry.second)
+ UpdateVector( aSearchIter->first, aSearchIter->second, aNP, false );
+ else
+ m_aContainer.insert( PairUrlRecord( rEntry.first, rEntry.second ) );
+ }
+}
+
+OUString SAL_CALL PasswordContainer::getImplementationName( )
+{
+ return "stardiv.svl.PasswordContainer";
+}
+
+sal_Bool SAL_CALL PasswordContainer::supportsService( const OUString& ServiceName )
+{
+ return cppu::supportsService( this, ServiceName );
+}
+
+Sequence< OUString > SAL_CALL PasswordContainer::getSupportedServiceNames( )
+{
+ return { "com.sun.star.task.PasswordContainer" };
+}
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
+svl_PasswordContainer_get_implementation(
+ css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const&)
+{
+ return cppu::acquire(new PasswordContainer(context));
+}
+
+
+MasterPasswordRequest_Impl::MasterPasswordRequest_Impl( PasswordRequestMode Mode )
+{
+ MasterPasswordRequest aRequest;
+
+ aRequest.Classification = InteractionClassification_ERROR;
+ aRequest.Mode = Mode;
+
+ setRequest( Any( aRequest ) );
+
+ // Fill continuations...
+ Sequence< RememberAuthentication > aRememberModes{ RememberAuthentication_NO };
+
+ m_xAuthSupplier
+ = new ::ucbhelper::InteractionSupplyAuthentication(
+ this,
+ false, // bCanSetRealm
+ false, // bCanSetUserName
+ true, // bCanSetPassword
+ false, // bCanSetAccount
+ aRememberModes, // rRememberPasswordModes
+ RememberAuthentication_NO, // eDefaultRememberPasswordMode
+ aRememberModes, // rRememberAccountModes
+ RememberAuthentication_NO, // eDefaultRememberAccountMode
+ false // bCanUseSystemCredentials
+ );
+
+ Sequence<
+ Reference< XInteractionContinuation > > aContinuations{
+ new ::ucbhelper::InteractionAbort( this ),
+ new ::ucbhelper::InteractionRetry( this ),
+ m_xAuthSupplier
+ };
+
+ setContinuations( aContinuations );
+}
+
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/passwordcontainer/passwordcontainer.hxx b/svl/source/passwordcontainer/passwordcontainer.hxx
new file mode 100644
index 0000000000..80fae574f1
--- /dev/null
+++ b/svl/source/passwordcontainer/passwordcontainer.hxx
@@ -0,0 +1,412 @@
+/* -*- 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 .
+ */
+#ifndef INCLUDED_SVL_SOURCE_PASSWORDCONTAINER_PASSWORDCONTAINER_HXX
+#define INCLUDED_SVL_SOURCE_PASSWORDCONTAINER_PASSWORDCONTAINER_HXX
+
+#include <utility>
+#include <vector>
+#include <map>
+#include <mutex>
+#include <optional>
+#include <com/sun/star/task/XPasswordContainer2.hpp>
+#include <com/sun/star/task/PasswordRequestMode.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/lang/XEventListener.hpp>
+#include <com/sun/star/lang/XComponent.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+#include <cppuhelper/implbase.hxx>
+
+#include <unotools/configitem.hxx>
+#include <ucbhelper/interactionrequest.hxx>
+
+#include <rtl/random.h>
+#include <rtl/ref.hxx>
+#include <osl/mutex.hxx>
+
+#include "syscreds.hxx"
+
+#define MEMORY_RECORD 0
+#define PERSISTENT_RECORD 1
+
+
+class NamePasswordRecord
+{
+ OUString m_aName;
+
+ // there are two lists of passwords, memory passwords and persistent passwords
+ bool m_bHasMemoryPasswords;
+ ::std::vector< OUString > m_aMemoryPasswords;
+
+ // persistent passwords are encrypted in one string
+ bool m_bHasPersistentPassword;
+ OUString m_aPersistentPassword;
+ OUString m_aPersistentIV;
+
+ void InitArrays( bool bHasMemoryList, ::std::vector< OUString >&& aMemoryList,
+ bool bHasPersistentList, const OUString& aPersistentList, const OUString& aPersistentIV )
+ {
+ m_bHasMemoryPasswords = bHasMemoryList;
+ if ( bHasMemoryList )
+ m_aMemoryPasswords = aMemoryList;
+
+ m_bHasPersistentPassword = bHasPersistentList;
+ if ( bHasPersistentList )
+ {
+ m_aPersistentPassword = aPersistentList;
+ m_aPersistentIV = aPersistentIV;
+ }
+ }
+
+public:
+
+ NamePasswordRecord( OUString aName )
+ : m_aName(std::move( aName ))
+ , m_bHasMemoryPasswords( false )
+ , m_bHasPersistentPassword( false )
+ {
+ }
+
+ NamePasswordRecord( OUString aName, OUString aPersistentList, OUString aPersistentIV )
+ : m_aName(std::move( aName ))
+ , m_bHasMemoryPasswords( false )
+ , m_bHasPersistentPassword( true )
+ , m_aPersistentPassword(std::move( aPersistentList ))
+ , m_aPersistentIV(std::move( aPersistentIV ))
+ {
+ }
+
+ NamePasswordRecord( const NamePasswordRecord& aRecord )
+ : m_aName( aRecord.m_aName )
+ , m_bHasMemoryPasswords( false )
+ , m_bHasPersistentPassword( false )
+ {
+ InitArrays( aRecord.m_bHasMemoryPasswords, std::vector(aRecord.m_aMemoryPasswords),
+ aRecord.m_bHasPersistentPassword, aRecord.m_aPersistentPassword, aRecord.m_aPersistentIV );
+ }
+
+ NamePasswordRecord& operator=( const NamePasswordRecord& aRecord )
+ {
+ if (this != &aRecord)
+ {
+ m_aName = aRecord.m_aName;
+
+ m_aMemoryPasswords.clear();
+ m_aPersistentPassword.clear();
+ m_aPersistentIV.clear();
+ InitArrays( aRecord.m_bHasMemoryPasswords, std::vector(aRecord.m_aMemoryPasswords),
+ aRecord.m_bHasPersistentPassword, aRecord.m_aPersistentPassword, aRecord.m_aPersistentIV );
+ }
+ return *this;
+ }
+
+ const OUString& GetUserName() const
+ {
+ return m_aName;
+ }
+
+ bool HasPasswords( sal_Int8 nStatus ) const
+ {
+ if ( nStatus == MEMORY_RECORD )
+ return m_bHasMemoryPasswords;
+ if ( nStatus == PERSISTENT_RECORD )
+ return m_bHasPersistentPassword;
+
+ return false;
+ }
+
+ ::std::vector< OUString > GetMemoryPasswords() const
+ {
+ if ( m_bHasMemoryPasswords )
+ return m_aMemoryPasswords;
+
+ return ::std::vector< OUString >();
+ }
+
+ OUString GetPersistentPasswords() const
+ {
+ if ( m_bHasPersistentPassword )
+ return m_aPersistentPassword;
+
+ return OUString();
+ }
+
+ OUString GetPersistentIV() const
+ {
+ if ( m_bHasPersistentPassword )
+ return m_aPersistentIV;
+
+ return OUString();
+ }
+
+ void SetMemoryPasswords( ::std::vector< OUString >&& aMemList )
+ {
+ m_aMemoryPasswords = std::move(aMemList);
+ m_bHasMemoryPasswords = true;
+ }
+
+ void SetPersistentPasswords( const OUString& aPersList, const OUString& aPersIV )
+ {
+ m_aPersistentPassword = aPersList;
+ m_aPersistentIV = aPersIV;
+ m_bHasPersistentPassword = true;
+ }
+
+ void RemovePasswords( sal_Int8 nStatus )
+ {
+ if ( nStatus == MEMORY_RECORD )
+ {
+ m_bHasMemoryPasswords = false;
+ m_aMemoryPasswords.clear();
+ }
+ else if ( nStatus == PERSISTENT_RECORD )
+ {
+ m_bHasPersistentPassword = false;
+ m_aPersistentPassword.clear();
+ m_aPersistentIV.clear();
+ }
+ }
+
+};
+
+
+typedef ::std::pair< const OUString, ::std::vector< NamePasswordRecord > > PairUrlRecord;
+typedef ::std::map< OUString, ::std::vector< NamePasswordRecord > > PasswordMap;
+
+// org.openoffice.Office.Common/Passwords/StorageVersion bump if details of
+// how password details are saved changes. Enables migration from previous
+// schemes.
+constexpr sal_Int32 nCurrentStorageVersion = 1;
+
+class PasswordContainer;
+
+class StorageItem
+ : public ::utl::ConfigItem
+{
+private:
+ PasswordContainer* mainCont;
+ bool hasEncoded;
+ OUString mEncoded;
+ OUString mEncodedIV;
+
+ virtual void ImplCommit() override;
+
+public:
+ StorageItem( PasswordContainer* point, const OUString& path ) :
+ ConfigItem( path, ConfigItemMode::NONE ),
+ mainCont( point ),
+ hasEncoded( false )
+ {
+ css::uno::Sequence< OUString > aNode { path + "/Store" };
+ EnableNotification( aNode );
+ }
+
+ PasswordMap getInfo();
+ void update( const OUString& url, const NamePasswordRecord& rec );
+ void remove( const OUString& url, const OUString& rec );
+ void clear();
+
+ sal_Int32 getStorageVersion();
+
+ bool getEncodedMasterPassword( OUString& aResult, OUString& aResultIV );
+ void setEncodedMasterPassword( const OUString& aResult, const OUString& aResultIV, bool bAcceptEmpty = false );
+ void setUseStorage( bool bUse );
+ bool useStorage();
+
+ virtual void Notify( const css::uno::Sequence< OUString >& aPropertyNames ) override;
+};
+
+
+class PasswordContainer : public ::cppu::WeakImplHelper<
+ css::task::XPasswordContainer2,
+ css::lang::XServiceInfo,
+ css::lang::XEventListener >
+{
+private:
+ PasswordMap m_aContainer;
+ std::optional<StorageItem> m_xStorageFile;
+ std::mutex mMutex;
+ OUString m_aMasterPassword; // master password is set when the string is not empty
+ css::uno::Reference< css::lang::XComponent > mComponent;
+ SysCredentialsConfig mUrlContainer;
+
+ class RandomPool
+ {
+ private:
+ rtlRandomPool m_aRandomPool;
+ public:
+ RandomPool() : m_aRandomPool(rtl_random_createPool())
+ {
+ }
+ rtlRandomPool get()
+ {
+ return m_aRandomPool;
+ }
+ ~RandomPool()
+ {
+ // Clean up random pool memory
+ rtl_random_destroyPool(m_aRandomPool);
+ }
+ };
+
+ RandomPool mRandomPool;
+
+ OUString createIV();
+
+ /// @throws css::uno::RuntimeException
+ css::uno::Sequence< css::task::UserRecord > CopyToUserRecordSequence(
+ const ::std::vector< NamePasswordRecord >& original,
+ const css::uno::Reference< css::task::XInteractionHandler >& Handler );
+
+ css::task::UserRecord CopyToUserRecord(
+ const NamePasswordRecord& aRecord,
+ bool& io_bTryToDecode,
+ const css::uno::Reference< css::task::XInteractionHandler >& aHandler );
+
+ /// @throws css::uno::RuntimeException
+ css::uno::Sequence< css::task::UserRecord > FindUsr(
+ const ::std::vector< NamePasswordRecord >& userlist,
+ std::u16string_view name,
+ const css::uno::Reference< css::task::XInteractionHandler >& Handler );
+ /// @throws css::uno::RuntimeException
+ bool createUrlRecord(
+ const PasswordMap::iterator & rIter,
+ bool bName,
+ std::u16string_view aName,
+ const css::uno::Reference< css::task::XInteractionHandler >& aHandler,
+ css::task::UrlRecord & rRec );
+
+ /// @throws css::uno::RuntimeException
+ css::task::UrlRecord find(
+ const OUString& aURL,
+ std::u16string_view aName,
+ bool bName, // only needed to support empty user names
+ const css::uno::Reference< css::task::XInteractionHandler >& aHandler );
+
+ static OUString GetDefaultMasterPassword();
+
+ static OUString RequestPasswordFromUser(
+ css::task::PasswordRequestMode aRMode,
+ const css::uno::Reference< css::task::XInteractionHandler >& xHandler );
+
+ /// @throws css::uno::RuntimeException
+ OUString const & GetMasterPassword( const css::uno::Reference< css::task::XInteractionHandler >& Handler );
+
+ /// @throws css::uno::RuntimeException
+ void UpdateVector( const OUString& url, ::std::vector< NamePasswordRecord >& toUpdate, NamePasswordRecord const & rec, bool writeFile );
+
+ /// @throws css::uno::RuntimeException
+ void PrivateAdd( const OUString& aUrl,
+ const OUString& aUserName,
+ const css::uno::Sequence< OUString >& aPasswords,
+ char aMode,
+ const css::uno::Reference< css::task::XInteractionHandler >& Handler );
+
+ /// @throws css::uno::RuntimeException
+ static ::std::vector< OUString > DecodePasswords( std::u16string_view aLine, std::u16string_view aIV, std::u16string_view aMasterPassword, css::task::PasswordRequestMode mode );
+
+ /// @throws css::uno::RuntimeException
+ static OUString EncodePasswords(const std::vector< OUString >& lines, std::u16string_view aIV, std::u16string_view aMasterPassword );
+
+public:
+ PasswordContainer( const css::uno::Reference< css::uno::XComponentContext >& );
+ virtual ~PasswordContainer() override;
+
+ virtual void SAL_CALL add( const OUString& aUrl,
+ const OUString& aUserName,
+ const css::uno::Sequence< OUString >& aPasswords,
+ const css::uno::Reference< css::task::XInteractionHandler >& Handler ) override;
+
+ virtual void SAL_CALL addPersistent( const OUString& aUrl,
+ const OUString& aUserName,
+ const css::uno::Sequence< OUString >& aPasswords,
+ const css::uno::Reference< css::task::XInteractionHandler >& Handler ) override;
+
+ virtual css::task::UrlRecord SAL_CALL
+ find( const OUString& aUrl,
+ const css::uno::Reference< css::task::XInteractionHandler >& Handler ) override;
+
+ virtual css::task::UrlRecord SAL_CALL
+ findForName( const OUString& aUrl,
+ const OUString& aUserName,
+ const css::uno::Reference< css::task::XInteractionHandler >& Handler ) override;
+
+ virtual void SAL_CALL remove( const OUString& aUrl,
+ const OUString& aUserName ) override;
+
+ virtual void SAL_CALL removePersistent( const OUString& aUrl,
+ const OUString& aUserName ) override;
+
+ virtual void SAL_CALL removeAllPersistent() override;
+
+ virtual css::uno::Sequence< css::task::UrlRecord > SAL_CALL
+ getAllPersistent( const css::uno::Reference< css::task::XInteractionHandler >& Handler ) override;
+
+ // XServiceInfo
+ virtual OUString SAL_CALL getImplementationName( ) override;
+ virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override;
+
+ virtual css::uno::Sequence< OUString > SAL_CALL
+ getSupportedServiceNames( ) override;
+
+ // XEventListener
+ virtual void SAL_CALL disposing( const css::lang::EventObject& Source ) override;
+
+ // XMasterPasswordHandling
+ virtual sal_Bool SAL_CALL authorizateWithMasterPassword( const css::uno::Reference< css::task::XInteractionHandler >& xHandler ) override;
+ virtual sal_Bool SAL_CALL changeMasterPassword( const css::uno::Reference< css::task::XInteractionHandler >& xHandler ) override;
+ virtual void SAL_CALL removeMasterPassword() override;
+ virtual sal_Bool SAL_CALL hasMasterPassword( ) override;
+ virtual sal_Bool SAL_CALL allowPersistentStoring( sal_Bool bAllow ) override;
+ virtual sal_Bool SAL_CALL isPersistentStoringAllowed( ) override;
+
+ // XMasterPasswordHandling2
+ virtual sal_Bool SAL_CALL useDefaultMasterPassword( const css::uno::Reference< css::task::XInteractionHandler >& xHandler ) override;
+ virtual sal_Bool SAL_CALL isDefaultMasterPasswordUsed( ) override;
+
+ // XUrlContainer
+ virtual void SAL_CALL addUrl( const OUString& Url, sal_Bool MakePersistent ) override;
+ virtual OUString SAL_CALL findUrl( const OUString& Url ) override;
+ virtual void SAL_CALL removeUrl( const OUString& Url ) override;
+ virtual css::uno::Sequence< OUString > SAL_CALL getUrls( sal_Bool OnlyPersistent ) override;
+
+ void Notify();
+private:
+ bool authorizateWithMasterPassword( std::unique_lock<std::mutex>& rGuard, const css::uno::Reference< css::task::XInteractionHandler >& xHandler );
+ css::uno::Sequence< css::task::UrlRecord > getAllPersistent( std::unique_lock<std::mutex>& rGuard, const css::uno::Reference< css::task::XInteractionHandler >& Handler );
+ void removeAllPersistent(std::unique_lock<std::mutex>& rGuard);
+ void removeMasterPassword(std::unique_lock<std::mutex>& rGuard);
+};
+
+
+class MasterPasswordRequest_Impl : public ucbhelper::InteractionRequest
+{
+ ::rtl::Reference< ucbhelper::InteractionSupplyAuthentication > m_xAuthSupplier;
+
+public:
+ MasterPasswordRequest_Impl( css::task::PasswordRequestMode Mode );
+
+ const ::rtl::Reference< ucbhelper::InteractionSupplyAuthentication > &
+ getAuthenticationSupplier() const { return m_xAuthSupplier; }
+
+};
+
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/passwordcontainer/syscreds.cxx b/svl/source/passwordcontainer/syscreds.cxx
new file mode 100644
index 0000000000..8de4a82f0d
--- /dev/null
+++ b/svl/source/passwordcontainer/syscreds.cxx
@@ -0,0 +1,269 @@
+/* -*- 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 "syscreds.hxx"
+#include <osl/diagnose.h>
+#include <comphelper/sequence.hxx>
+
+using namespace com::sun::star;
+
+SysCredentialsConfigItem::SysCredentialsConfigItem(
+ SysCredentialsConfig * pOwner )
+: utl::ConfigItem( "Office.Common/Passwords", ConfigItemMode::NONE ),
+ m_bInited( false ),
+ m_pOwner( pOwner )
+{
+ uno::Sequence<OUString> aNode { "Office.Common/Passwords/AuthenticateUsingSystemCredentials" };
+ EnableNotification( aNode );
+}
+
+//virtual
+void SysCredentialsConfigItem::Notify(
+ const uno::Sequence< OUString > & /*seqPropertyNames*/ )
+{
+ {
+ std::unique_lock aGuard( m_aMutex );
+ m_bInited = false;
+ // rebuild m_seqURLs
+ getSystemCredentialsURLs(aGuard);
+ }
+ m_pOwner->persistentConfigChanged();
+}
+
+void SysCredentialsConfigItem::ImplCommit()
+{
+ // does nothing
+}
+
+uno::Sequence< OUString >
+SysCredentialsConfigItem::getSystemCredentialsURLs()
+{
+ std::unique_lock aGuard(m_aMutex);
+ return getSystemCredentialsURLs(aGuard);
+}
+
+uno::Sequence< OUString >
+SysCredentialsConfigItem::getSystemCredentialsURLs(std::unique_lock<std::mutex>& /*rGuard*/)
+{
+ if ( !m_bInited )
+ {
+ // read config item
+ uno::Sequence<OUString> aPropNames { "AuthenticateUsingSystemCredentials" };
+ uno::Sequence< uno::Any > aAnyValues(
+ utl::ConfigItem::GetProperties( aPropNames ) );
+
+ OSL_ENSURE(
+ aAnyValues.getLength() == 1,
+ "SysCredentialsConfigItem::getSystemCredentialsURLs: "
+ "Error reading config item!" );
+
+ uno::Sequence< OUString > aValues;
+ if ( ( aAnyValues[ 0 ] >>= aValues ) ||
+ ( !aAnyValues[ 0 ].hasValue() ) )
+ {
+ m_seqURLs = aValues;
+ m_bInited = true;
+ }
+ }
+ return m_seqURLs;
+}
+
+void SysCredentialsConfigItem::setSystemCredentialsURLs(
+ const uno::Sequence< OUString > & seqURLList )
+{
+ // write config item.
+ uno::Sequence< OUString > aPropNames{ "AuthenticateUsingSystemCredentials" };
+ uno::Sequence< uno::Any > aPropValues{ uno::Any(seqURLList) };
+
+ utl::ConfigItem::SetModified();
+ utl::ConfigItem::PutProperties( aPropNames, aPropValues );
+
+ std::unique_lock aGuard( m_aMutex );
+
+ m_seqURLs = seqURLList;
+ m_bInited = true;
+}
+
+
+namespace
+{
+ // TODO: This code is actually copied from svl/source/passwordcontainer.cxx
+ bool removeLastSegment( OUString & aURL )
+ {
+ sal_Int32 aInd = aURL.lastIndexOf( '/' );
+
+ if( aInd > 0 )
+ {
+ sal_Int32 aPrevInd = aURL.lastIndexOf( '/', aInd );
+ if ( aURL.indexOf( "://" ) != aPrevInd - 2 ||
+ aInd != aURL.getLength() - 1 )
+ {
+ aURL = aURL.copy( 0, aInd );
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ bool findURL( std::set<OUString> const & rContainer, OUString const & aURL, OUString & aResult )
+ {
+ // TODO: This code is actually copied from svl/source/passwordcontainer.cxx
+ if( !rContainer.empty() && !aURL.isEmpty() )
+ {
+ OUString aUrl( aURL );
+
+ // each iteration remove last '/...' section from the aUrl
+ // while it's possible, up to the most left '://'
+ do
+ {
+ // first look for <url>/somename and then look for <url>/somename/...
+ auto aIter = rContainer.find( aUrl );
+ if( aIter != rContainer.end() )
+ {
+ aResult = *aIter;
+ return true;
+ }
+ else
+ {
+ OUString tmpUrl( aUrl );
+ if ( !tmpUrl.endsWith("/") )
+ tmpUrl += "/";
+
+ aIter = rContainer.lower_bound( tmpUrl );
+ if( aIter != rContainer.end() && aIter->match( tmpUrl ) )
+ {
+ aResult = *aIter;
+ return true;
+ }
+ }
+ }
+ while( removeLastSegment( aUrl ) && !aUrl.isEmpty() );
+ }
+ aResult.clear();
+ return false;
+ }
+
+} // namespace
+
+SysCredentialsConfig::SysCredentialsConfig()
+: m_aConfigItem( this ),
+ m_bCfgInited( false )
+{
+}
+
+void SysCredentialsConfig::initCfg(std::unique_lock<std::mutex>& /*rGuard*/)
+{
+ if ( !m_bCfgInited )
+ {
+ const uno::Sequence< OUString > aURLs(
+ m_aConfigItem.getSystemCredentialsURLs() );
+ m_aCfgContainer.insert( aURLs.begin(), aURLs.end() );
+ m_bCfgInited = true;
+ }
+}
+
+void SysCredentialsConfig::writeCfg(std::unique_lock<std::mutex>& /*rGuard*/)
+{
+ OSL_ENSURE( m_bCfgInited, "SysCredentialsConfig::writeCfg : not initialized!" );
+
+ m_aConfigItem.setSystemCredentialsURLs( comphelper::containerToSequence(m_aCfgContainer) );
+}
+
+OUString SysCredentialsConfig::find( OUString const & aURL )
+{
+ std::unique_lock aGuard( m_aMutex );
+ OUString aResult;
+ if ( findURL( m_aMemContainer, aURL, aResult ) )
+ return aResult;
+
+ initCfg(aGuard);
+ if ( findURL( m_aCfgContainer, aURL, aResult ) )
+ return aResult;
+
+ return OUString();
+}
+
+void SysCredentialsConfig::add( OUString const & rURL, bool bPersistent )
+{
+ std::unique_lock aGuard( m_aMutex );
+
+ if ( bPersistent )
+ {
+ m_aMemContainer.erase( rURL );
+
+ initCfg(aGuard);
+ m_aCfgContainer.insert( rURL );
+ writeCfg(aGuard);
+ }
+ else
+ {
+ initCfg(aGuard);
+ if ( m_aCfgContainer.erase( rURL ) > 0 )
+ writeCfg(aGuard);
+
+ m_aMemContainer.insert( rURL );
+ }
+}
+
+void SysCredentialsConfig::remove( OUString const & rURL )
+{
+ std::unique_lock aGuard(m_aMutex);
+
+ m_aMemContainer.erase( rURL );
+
+ initCfg(aGuard);
+ if ( m_aCfgContainer.erase( rURL ) > 0 )
+ writeCfg(aGuard);
+}
+
+uno::Sequence< OUString > SysCredentialsConfig::list( bool bOnlyPersistent )
+{
+ std::unique_lock aGuard(m_aMutex);
+ initCfg(aGuard);
+ sal_Int32 nCount = m_aCfgContainer.size()
+ + ( bOnlyPersistent ? 0 : m_aMemContainer.size() );
+ uno::Sequence< OUString > aResult( nCount );
+ auto aResultRange = asNonConstRange(aResult);
+ sal_Int32 n = 0;
+
+ for ( const auto& rItem : m_aCfgContainer )
+ {
+ aResultRange[ n ] = rItem;
+ ++n;
+ }
+
+ if ( !bOnlyPersistent )
+ {
+ for ( const auto& rItem : m_aMemContainer )
+ {
+ aResultRange[ n ] = rItem;
+ ++n;
+ }
+ }
+ return aResult;
+}
+
+void SysCredentialsConfig::persistentConfigChanged()
+{
+ std::unique_lock aGuard( m_aMutex );
+ m_bCfgInited = false; // re-init on demand.
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/passwordcontainer/syscreds.hxx b/svl/source/passwordcontainer/syscreds.hxx
new file mode 100644
index 0000000000..75241c4e79
--- /dev/null
+++ b/svl/source/passwordcontainer/syscreds.hxx
@@ -0,0 +1,81 @@
+/* -*- 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 .
+ */
+
+#ifndef INCLUDED_SVL_SOURCE_PASSWORDCONTAINER_SYSCREDS_HXX
+#define INCLUDED_SVL_SOURCE_PASSWORDCONTAINER_SYSCREDS_HXX
+
+#include <memory>
+#include <mutex>
+#include <set>
+#include <rtl/ustring.hxx>
+#include <com/sun/star/uno/Sequence.hxx>
+#include <unotools/configitem.hxx>
+
+class SysCredentialsConfig;
+
+class SysCredentialsConfigItem : public utl::ConfigItem
+{
+ public:
+ explicit SysCredentialsConfigItem( SysCredentialsConfig * pOwner );
+ //virtual ~SysCredentialsConfigItem();
+
+ virtual void Notify( const css::uno::Sequence< OUString > & seqPropertyNames ) override;
+
+ css::uno::Sequence< OUString > getSystemCredentialsURLs();
+
+ void setSystemCredentialsURLs( const css::uno::Sequence< OUString > & seqURLList );
+
+ //bool isSystemCredentialsURL( const OUString & rURL ) const;
+
+ private:
+ css::uno::Sequence< OUString > getSystemCredentialsURLs(std::unique_lock<std::mutex>& rGuard);
+ virtual void ImplCommit() override;
+
+ std::mutex m_aMutex;
+ bool m_bInited;
+ css::uno::Sequence< OUString > m_seqURLs;
+ SysCredentialsConfig * m_pOwner;
+};
+
+class SysCredentialsConfig
+{
+ public:
+ SysCredentialsConfig();
+
+ OUString find( OUString const & rURL );
+ void add( OUString const & rURL, bool bPersistent );
+ void remove( OUString const & rURL );
+ css::uno::Sequence< OUString > list( bool bOnlyPersistent );
+
+ void persistentConfigChanged();
+
+ private:
+ void initCfg(std::unique_lock<std::mutex>& rGuard);
+ void writeCfg(std::unique_lock<std::mutex>& rGuard);
+
+ std::mutex m_aMutex;
+ std::set< OUString > m_aMemContainer;
+ std::set< OUString > m_aCfgContainer;
+ SysCredentialsConfigItem m_aConfigItem;
+ bool m_bCfgInited;
+};
+
+#endif // INCLUDED_SVL_SOURCE_PASSWORDCONTAINER_SYSCREDS_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/svdde/ddecli.cxx b/svl/source/svdde/ddecli.cxx
new file mode 100644
index 0000000000..835bdf2269
--- /dev/null
+++ b/svl/source/svdde/ddecli.cxx
@@ -0,0 +1,391 @@
+/* -*- 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 <string.h>
+#include <algorithm>
+#include "ddeimp.hxx"
+#include <svl/svdde.hxx>
+#include <officecfg/Office/Common.hxx>
+#include <osl/thread.h>
+#include <comphelper/solarmutex.hxx>
+
+namespace {
+
+DdeInstData * theDdeInstData;
+
+}
+
+DdeInstData* ImpGetInstData()
+{
+ return theDdeInstData;
+}
+
+DdeInstData* ImpInitInstData()
+{
+ theDdeInstData = new DdeInstData;
+ return theDdeInstData;
+}
+
+void ImpDeinitInstData()
+{
+ delete theDdeInstData;
+ theDdeInstData = nullptr;
+}
+
+
+struct DdeImp
+{
+ HCONV hConv;
+ UINT nStatus;
+};
+
+HDDEDATA CALLBACK DdeInternal::CliCallback( UINT nCode, UINT nCbType,
+ HCONV hConv, HSZ, HSZ hText2,
+ HDDEDATA hData, ULONG_PTR nInfo1, ULONG_PTR )
+{
+ HDDEDATA nRet = DDE_FNOTPROCESSED;
+ const std::vector<DdeConnection*> &rAll = DdeConnection::GetConnections();
+ DdeConnection* self = nullptr;
+
+ DdeInstData* pInst = ImpGetInstData();
+ assert(pInst);
+
+ for ( size_t i = 0; i < rAll.size(); ++i)
+ {
+ self = rAll[i];
+
+ if ( self->pImp->hConv == hConv )
+ break;
+ }
+
+ if( self )
+ {
+ bool bFound = false;
+ std::vector<DdeTransaction*>::iterator iter;
+ for( iter = self->aTransactions.begin(); iter != self->aTransactions.end(); ++iter )
+ {
+ switch( nCode )
+ {
+ case XTYP_XACT_COMPLETE:
+ if( static_cast<DWORD>((*iter)->nId) == nInfo1 )
+ {
+ nCode = (*iter)->nType & (XCLASS_MASK | XTYP_MASK);
+ (*iter)->bBusy = false;
+ (*iter)->Done( nullptr != hData );
+ bFound = true;
+ }
+ break;
+
+ case XTYP_DISCONNECT:
+ self->pImp->hConv = DdeReconnect( hConv );
+ self->pImp->nStatus = self->pImp->hConv
+ ? DMLERR_NO_ERROR
+ : DdeGetLastError( pInst->hDdeInstCli );
+ iter = self->aTransactions.end();
+ nRet = nullptr;
+ bFound = true;
+ break;
+
+ case XTYP_ADVDATA:
+ bFound = *(*iter)->pName == hText2;
+ break;
+ }
+ if( bFound )
+ break;
+ }
+
+ if( iter != self->aTransactions.end() )
+ {
+ switch( nCode )
+ {
+ case XTYP_ADVDATA:
+ if( !hData )
+ {
+ static_cast<DdeLink*>(*iter)->Notify();
+ nRet = reinterpret_cast<HDDEDATA>(DDE_FACK);
+ break;
+ }
+ [[fallthrough]];
+
+ case XTYP_REQUEST:
+ DdeData d;
+ d.xImp->hData = hData;
+ d.xImp->nFmt = DdeData::GetInternalFormat( nCbType );
+ d.Lock();
+ (*iter)->Data( &d );
+ nRet = reinterpret_cast<HDDEDATA>(DDE_FACK);
+ break;
+ }
+ }
+ }
+ return nRet;
+}
+
+DdeConnection::DdeConnection( const OUString& rService, const OUString& rTopic ):
+ pImp(std::make_unique<DdeImp>())
+{
+ pImp->nStatus = DMLERR_NO_ERROR;
+ pImp->hConv = nullptr;
+
+ DdeInstData* pInst = ImpGetInstData();
+ if( !pInst )
+ pInst = ImpInitInstData();
+ pInst->nRefCount++;
+ pInst->nInstanceCli++;
+ if ( !pInst->hDdeInstCli )
+ {
+ pImp->nStatus = DMLERR_SYS_ERROR;
+ if ( !officecfg::Office::Common::Security::Scripting::DisableActiveContent::get() )
+ {
+ pImp->nStatus = DdeInitializeW( &pInst->hDdeInstCli,
+ DdeInternal::CliCallback,
+ APPCLASS_STANDARD | APPCMD_CLIENTONLY |
+ CBF_FAIL_ALLSVRXACTIONS |
+ CBF_SKIP_REGISTRATIONS |
+ CBF_SKIP_UNREGISTRATIONS, 0L );
+ }
+ }
+
+ pService = new DdeString( pInst->hDdeInstCli, rService );
+ pTopic = new DdeString( pInst->hDdeInstCli, rTopic );
+
+ if ( pImp->nStatus == DMLERR_NO_ERROR )
+ {
+ pImp->hConv = DdeConnect( pInst->hDdeInstCli,pService->getHSZ(),pTopic->getHSZ(), nullptr);
+ if( !pImp->hConv )
+ pImp->nStatus = DdeGetLastError( pInst->hDdeInstCli );
+ }
+
+ pInst->aConnections.push_back( this );
+}
+
+DdeConnection::~DdeConnection()
+{
+ if ( pImp->hConv )
+ DdeDisconnect( pImp->hConv );
+
+ delete pService;
+ delete pTopic;
+
+ DdeInstData* pInst = ImpGetInstData();
+ assert(pInst);
+
+ std::vector<DdeConnection*>::iterator it(std::find(pInst->aConnections.begin(),
+ pInst->aConnections.end(),
+ this));
+ if (it != pInst->aConnections.end())
+ pInst->aConnections.erase(it);
+
+ pInst->nInstanceCli--;
+ pInst->nRefCount--;
+ if ( !pInst->nInstanceCli && pInst->hDdeInstCli )
+ {
+ if( DdeUninitialize( pInst->hDdeInstCli ) )
+ {
+ pInst->hDdeInstCli = 0;
+ if( pInst->nRefCount == 0 )
+ ImpDeinitInstData();
+ }
+ }
+}
+
+bool DdeConnection::IsConnected()
+{
+ CONVINFO c;
+ c.cb = sizeof( c );
+ if ( DdeQueryConvInfo( pImp->hConv, QID_SYNC, &c ) )
+ return true;
+ else
+ {
+ DdeInstData* pInst = ImpGetInstData();
+ pImp->hConv = DdeReconnect( pImp->hConv );
+ pImp->nStatus = pImp->hConv ? DMLERR_NO_ERROR : DdeGetLastError( pInst->hDdeInstCli );
+ return pImp->nStatus == DMLERR_NO_ERROR;
+ }
+}
+
+OUString DdeConnection::GetServiceName() const
+{
+ return pService->toOUString();
+}
+
+ OUString DdeConnection::GetTopicName() const
+{
+ return pTopic->toOUString();
+}
+
+const std::vector<DdeConnection*>& DdeConnection::GetConnections()
+{
+ DdeInstData* pInst = ImpGetInstData();
+ assert(pInst);
+ return pInst->aConnections;
+}
+
+DdeTransaction::DdeTransaction( DdeConnection& d, const OUString& rItemName,
+ tools::Long n )
+ : rDde( d )
+{
+ DdeInstData* pInst = ImpGetInstData();
+ pName = new DdeString( pInst->hDdeInstCli, rItemName );
+ nTime = n;
+ nId = 0;
+ nType = 0;
+ bBusy = false;
+
+ rDde.aTransactions.push_back( this );
+}
+
+DdeTransaction::~DdeTransaction()
+{
+ if ( nId && rDde.pImp->hConv )
+ {
+ DdeInstData* pInst = ImpGetInstData();
+ DdeAbandonTransaction( pInst->hDdeInstCli, rDde.pImp->hConv, nId );
+ }
+
+ delete pName;
+ std::erase(rDde.aTransactions,this);
+}
+
+void DdeTransaction::Execute()
+{
+ HSZ hItem = pName->getHSZ();
+ void const * pData = aDdeData.getData();
+ DWORD nData = static_cast<DWORD>(aDdeData.getSize());
+ SotClipboardFormatId nIntFmt = aDdeData.xImp->nFmt;
+ UINT nExtFmt = DdeData::GetExternalFormat( nIntFmt );
+ DdeInstData* pInst = ImpGetInstData();
+
+ if ( nType == XTYP_EXECUTE )
+ hItem = nullptr;
+ if ( nType != XTYP_EXECUTE && nType != XTYP_POKE )
+ {
+ pData = nullptr;
+ nData = 0;
+ }
+ if ( nTime )
+ {
+ HDDEDATA hData = DdeClientTransaction( static_cast<LPBYTE>(const_cast<void *>(pData)),
+ nData, rDde.pImp->hConv,
+ hItem, nExtFmt, static_cast<UINT>(nType),
+ static_cast<DWORD>(nTime), nullptr );
+
+ rDde.pImp->nStatus = DdeGetLastError( pInst->hDdeInstCli );
+ if( hData && nType == XTYP_REQUEST )
+ {
+ {
+ DdeData d;
+ d.xImp->hData = hData;
+ d.xImp->nFmt = nIntFmt;
+ d.Lock();
+ Data( &d );
+ }
+ DdeFreeDataHandle( hData );
+ }
+ }
+ else
+ {
+ if ( nId && rDde.pImp->hConv )
+ DdeAbandonTransaction( pInst->hDdeInstCli, rDde.pImp->hConv, nId);
+ nId = 0;
+ bBusy = true;
+ DWORD result;
+ HDDEDATA hRet = DdeClientTransaction( static_cast<LPBYTE>(const_cast<void *>(pData)), nData,
+ rDde.pImp->hConv, hItem, nExtFmt,
+ static_cast<UINT>(nType), TIMEOUT_ASYNC,
+ &result );
+ nId = result;
+ rDde.pImp->nStatus = hRet ? DMLERR_NO_ERROR
+ : DdeGetLastError( pInst->hDdeInstCli );
+ }
+}
+
+OUString DdeTransaction::GetName() const
+{
+ return pName->toOUString();
+}
+
+void DdeTransaction::Data( const DdeData* p )
+{
+ comphelper::SolarMutex *pSolarMutex = comphelper::SolarMutex::get();
+ if ( pSolarMutex )
+ {
+ pSolarMutex->acquire();
+ aData.Call( p );
+ pSolarMutex = comphelper::SolarMutex::get();
+ if ( pSolarMutex )
+ pSolarMutex->release();
+ }
+}
+
+void DdeTransaction::Done( bool bDataValid )
+{
+ aDone.Call( bDataValid );
+}
+
+DdeLink::DdeLink( DdeConnection& d, const OUString& aItemName, tools::Long n )
+ : DdeTransaction (d, aItemName, n)
+{
+}
+
+DdeLink::~DdeLink()
+{
+ nType = sal_uInt16(XTYP_ADVSTOP);
+ nTime = 0;
+}
+
+void DdeLink::Notify()
+{
+ aNotify.Call( nullptr );
+}
+
+DdeRequest::DdeRequest( DdeConnection& d, const OUString& i, tools::Long n )
+ : DdeTransaction( d, i, n )
+{
+ nType = XTYP_REQUEST;
+}
+
+DdeHotLink::DdeHotLink( DdeConnection& d, const OUString& i )
+ : DdeLink( d, i, 0 )
+{
+ nType = XTYP_ADVSTART;
+}
+
+DdePoke::DdePoke( DdeConnection& d, const OUString& i, const DdeData& rData,
+ tools::Long n )
+ : DdeTransaction( d, i, n )
+{
+ aDdeData = rData;
+ nType = XTYP_POKE;
+}
+
+DdeExecute::DdeExecute( DdeConnection& d, const OUString& rData, tools::Long n )
+ : DdeTransaction( d, OUString(), n )
+{
+ aDdeData = DdeData( rData.getStr(), sizeof(sal_Unicode) * (rData.getLength() + 1), SotClipboardFormatId::STRING );
+ nType = XTYP_EXECUTE;
+}
+
+tools::Long DdeConnection::GetError() const
+{
+ return pImp->nStatus;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/svdde/ddedata.cxx b/svl/source/svdde/ddedata.cxx
new file mode 100644
index 0000000000..d8e1e15792
--- /dev/null
+++ b/svl/source/svdde/ddedata.cxx
@@ -0,0 +1,167 @@
+/* -*- 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 .
+ */
+
+
+// ATTENTION: We assume StarView Clipboard format numbers and Windows
+// Format numbers to be the same! If that's not the case, we need to
+// adapt the code here. The implementation uses the conversions here.
+
+#include <string.h>
+#include "ddeimp.hxx"
+#include <svl/svdde.hxx>
+#include <o3tl/char16_t2wchar_t.hxx>
+
+#include <osl/thread.h>
+#include <sot/exchange.hxx>
+
+DdeData::DdeData()
+{
+ xImp.reset(new DdeDataImp);
+ xImp->hData = nullptr;
+ xImp->nData = 0;
+ xImp->pData = nullptr;
+ xImp->nFmt = SotClipboardFormatId::STRING;
+}
+
+DdeData::DdeData(const void* p, tools::Long n, SotClipboardFormatId f)
+{
+ xImp.reset(new DdeDataImp);
+ xImp->hData = nullptr;
+ xImp->pData = p;
+ xImp->nData = n;
+ xImp->nFmt = f;
+}
+
+DdeData::DdeData( const OUString& s )
+{
+ xImp.reset(new DdeDataImp);
+ xImp->hData = nullptr;
+ xImp->pData = s.getStr();
+ xImp->nData = s.getLength()+1;
+ xImp->nFmt = SotClipboardFormatId::STRING;
+}
+
+DdeData::DdeData(const DdeData& rData)
+{
+ xImp.reset(new DdeDataImp);
+ xImp->hData = rData.xImp->hData;
+ xImp->nData = rData.xImp->nData;
+ xImp->pData = rData.xImp->pData;
+ xImp->nFmt = rData.xImp->nFmt;
+ Lock();
+}
+
+DdeData::DdeData(DdeData&& rData) noexcept
+ : xImp(std::move(rData.xImp))
+{
+}
+
+DdeData::~DdeData()
+{
+ if (xImp && xImp->hData)
+ DdeUnaccessData(xImp->hData);
+}
+
+void DdeData::Lock()
+{
+ if (xImp->hData)
+ xImp->pData = DdeAccessData(xImp->hData, &xImp->nData);
+}
+
+SotClipboardFormatId DdeData::GetFormat() const
+{
+ return xImp->nFmt;
+}
+
+void DdeData::SetFormat(SotClipboardFormatId nFmt)
+{
+ xImp->nFmt = nFmt;
+}
+
+void const * DdeData::getData() const
+{
+ return xImp->pData;
+}
+
+tools::Long DdeData::getSize() const
+{
+ return xImp->nData;
+}
+
+DdeData& DdeData::operator=(const DdeData& rData)
+{
+ if ( &rData != this )
+ {
+ DdeData tmp(rData);
+ xImp = std::move(tmp.xImp);
+ }
+
+ return *this;
+}
+
+DdeData& DdeData::operator=(DdeData&& rData) noexcept
+{
+ xImp = std::move(rData.xImp);
+ return *this;
+}
+
+sal_uInt32 DdeData::GetExternalFormat(SotClipboardFormatId nFmt)
+{
+ switch( nFmt )
+ {
+ case SotClipboardFormatId::STRING:
+ return CF_TEXT;
+ case SotClipboardFormatId::BITMAP:
+ return CF_BITMAP;
+ case SotClipboardFormatId::GDIMETAFILE:
+ return CF_METAFILEPICT;
+ default:
+ {
+ OUString aName( SotExchange::GetFormatName( nFmt ) );
+ if( !aName.isEmpty() )
+ return RegisterClipboardFormatW( o3tl::toW(aName.getStr()) );
+ }
+ }
+ return static_cast<sal_uInt32>(nFmt);
+}
+
+SotClipboardFormatId DdeData::GetInternalFormat(sal_uLong nFmt)
+{
+ switch( nFmt )
+ {
+ case CF_TEXT:
+ return SotClipboardFormatId::STRING;
+ case CF_BITMAP:
+ return SotClipboardFormatId::BITMAP;
+ case CF_METAFILEPICT:
+ return SotClipboardFormatId::GDIMETAFILE;
+ default:
+ if( nFmt >= CF_MAX )
+ {
+ WCHAR szName[ 256 ];
+
+ if(GetClipboardFormatNameW( nFmt, szName, SAL_N_ELEMENTS(szName) ))
+ return SotExchange::RegisterFormatName( OUString(o3tl::toU(szName)) );
+ }
+ break;
+ }
+ return static_cast<SotClipboardFormatId>(nFmt);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/svdde/ddeimp.hxx b/svl/source/svdde/ddeimp.hxx
new file mode 100644
index 0000000000..060711bbf3
--- /dev/null
+++ b/svl/source/svdde/ddeimp.hxx
@@ -0,0 +1,117 @@
+/* -*- 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 .
+ */
+
+#ifndef INCLUDED_SVL_SOURCE_SVDDE_DDEIMP_HXX
+#define INCLUDED_SVL_SOURCE_SVDDE_DDEIMP_HXX
+
+#if !defined WIN32_LEAN_AND_MEAN
+# define WIN32_LEAN_AND_MEAN
+#endif
+#include <windows.h>
+#include <ddeml.h>
+
+#include <rtl/ustring.hxx>
+#include <svl/svdde.hxx>
+#include <vector>
+
+
+struct Conversation
+{
+ HCONV hConv;
+ DdeTopic* pTopic;
+};
+
+
+class DdeInternal
+{
+public:
+ static HDDEDATA CALLBACK CliCallback
+ ( UINT, UINT, HCONV, HSZ, HSZ, HDDEDATA, ULONG_PTR, ULONG_PTR );
+ static HDDEDATA CALLBACK SvrCallback
+ ( UINT, UINT, HCONV, HSZ, HSZ, HDDEDATA, ULONG_PTR, ULONG_PTR );
+ static DdeService* FindService( HSZ );
+ static DdeTopic* FindTopic( DdeService&, HSZ );
+ static DdeItem* FindItem( DdeTopic&, HSZ );
+ static void DisconnectTopic(DdeTopic &, HCONV);
+ static void IncMonitor(DdeItem *pItem, HCONV);
+ static void DecMonitor(DdeItem *pItem, HCONV);
+};
+
+
+class DdeString
+{
+private:
+ OUString m_aString;
+protected:
+ HSZ hString;
+ DWORD hInst;
+
+public:
+ DdeString( DWORD, const OUString& );
+ ~DdeString();
+
+ bool operator==( HSZ ) const;
+ HSZ getHSZ();
+ const OUString & toOUString() const { return m_aString; }
+};
+
+
+struct DdeDataImp
+{
+ HDDEDATA hData;
+ void const * pData;
+ DWORD nData;
+ SotClipboardFormatId nFmt;
+};
+
+class DdeConnection;
+
+class DdeInstData
+{
+public:
+ sal_uInt16 nRefCount;
+ std::vector<DdeConnection*> aConnections;
+ // Server
+ DWORD hDdeInstSvr;
+ short nInstanceSvr;
+ DdeServices* pServicesSvr;
+ // Client
+ DWORD hDdeInstCli;
+ short nInstanceCli;
+
+ DdeInstData()
+ : nRefCount(0)
+ , hDdeInstSvr(0)
+ , nInstanceSvr(0)
+ , pServicesSvr(nullptr)
+ , hDdeInstCli(0)
+ , nInstanceCli(0)
+ {
+ }
+ DdeInstData(const DdeInstData&) = delete;
+ DdeInstData& operator=(const DdeInstData&) = delete;
+};
+
+DdeInstData* ImpGetInstData();
+DdeInstData* ImpInitInstData();
+void ImpDeinitInstData();
+
+#endif // INCLUDED_SVL_SOURCE_SVDDE_DDEIMP_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/svdde/ddestrg.cxx b/svl/source/svdde/ddestrg.cxx
new file mode 100644
index 0000000000..6ddade45b9
--- /dev/null
+++ b/svl/source/svdde/ddestrg.cxx
@@ -0,0 +1,47 @@
+/* -*- 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 "ddeimp.hxx"
+#include <svl/svdde.hxx>
+#include <o3tl/char16_t2wchar_t.hxx>
+
+DdeString::DdeString( DWORD hDdeInst, const OUString& r)
+ : m_aString(r), hString(DdeCreateStringHandleW( hDdeInst, o3tl::toW(r.getStr()), CP_WINUNICODE )),
+ hInst(hDdeInst)
+{
+}
+
+DdeString::~DdeString()
+{
+ if ( hString )
+ DdeFreeStringHandle( hInst, hString );
+}
+
+bool DdeString::operator==( HSZ h ) const
+{
+ return( !DdeCmpStringHandles( hString, h ) );
+}
+
+HSZ DdeString::getHSZ()
+{
+ return hString;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/svdde/ddesvr.cxx b/svl/source/svdde/ddesvr.cxx
new file mode 100644
index 0000000000..3df8b5a570
--- /dev/null
+++ b/svl/source/svdde/ddesvr.cxx
@@ -0,0 +1,820 @@
+/* -*- 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 "ddeimp.hxx"
+#include <algorithm>
+#include <memory>
+#include <comphelper/string.hxx>
+#include <rtl/ustring.hxx>
+#include <svl/svdde.hxx>
+#include <osl/thread.h>
+#include <o3tl/sorted_vector.hxx>
+#include <o3tl/char16_t2wchar_t.hxx>
+#include <officecfg/Office/Common.hxx>
+
+namespace {
+
+enum DdeItemType
+{
+ DDEITEM,
+ DDEGETPUTITEM
+};
+
+}
+
+struct DdeItemImpData
+{
+ HCONV nHCnv;
+ sal_uInt16 nCnt;
+
+ explicit DdeItemImpData( HCONV nH ) : nHCnv( nH ), nCnt( 1 ) {}
+};
+
+HDDEDATA CALLBACK DdeInternal::SvrCallback(
+ UINT nCode, UINT nCbType, HCONV hConv, HSZ hText1, HSZ hText2,
+ HDDEDATA hData, ULONG_PTR, ULONG_PTR )
+{
+ DdeInstData* pInst = ImpGetInstData();
+ assert(pInst);
+
+ switch( nCode )
+ {
+ case XTYP_WILDCONNECT:
+ {
+ std::vector<HSZPAIR> aPairs;
+
+ WCHAR chTopicBuf[256];
+ if( hText1 )
+ DdeQueryStringW( pInst->hDdeInstSvr, hText1, chTopicBuf,
+ SAL_N_ELEMENTS(chTopicBuf), CP_WINUNICODE );
+
+ for (auto& pService : DdeService::GetServices())
+ {
+ if (hText2 && !(*pService->pName == hText2))
+ continue;
+
+ OUString sTopics(pService->Topics().replaceAll("\n", "").replaceAll("\r", ""));
+ if (sTopics.isEmpty())
+ continue;
+
+ for (sal_Int32 n = 0; -1 != n;)
+ {
+ OUString s(sTopics.getToken(0, '\t', n));
+ if (hText1 && s != o3tl::toU(chTopicBuf))
+ continue;
+
+ DdeString aDStr(pInst->hDdeInstSvr, s);
+ if (auto pTopic = FindTopic(*pService, aDStr.getHSZ()))
+ {
+ auto& pair = aPairs.emplace_back();
+ pair.hszSvc = pService->pName->getHSZ();
+ pair.hszTopic = pTopic->pName->getHSZ();
+ }
+ }
+ }
+
+ if (aPairs.empty())
+ return nullptr;
+ aPairs.emplace_back(); // trailing zero
+
+ HDDEDATA h = DdeCreateDataHandle(
+ pInst->hDdeInstSvr,
+ reinterpret_cast<LPBYTE>(aPairs.data()),
+ sizeof(HSZPAIR) * aPairs.size(),
+ 0, nullptr, nCbType, 0);
+ return h;
+ }
+
+ case XTYP_CONNECT:
+ if (auto pService = FindService(hText2))
+ if (FindTopic(*pService, hText1))
+ return reinterpret_cast<HDDEDATA>(DDE_FACK);
+ return nullptr;
+
+ case XTYP_CONNECT_CONFIRM:
+ if (auto pService = FindService(hText2))
+ {
+ if (auto pTopic = FindTopic(*pService, hText1))
+ {
+ auto pC = new Conversation;
+ pC->hConv = hConv;
+ pC->pTopic = pTopic;
+ pService->m_vConv.emplace_back( pC );
+ }
+ }
+ return nullptr;
+ }
+
+ DdeService* pService = nullptr;
+ Conversation* pC = nullptr;
+ for (auto& rpService : DdeService::GetServices())
+ {
+ for ( size_t i = 0, n = rpService->m_vConv.size(); i < n; ++i )
+ {
+ pC = rpService->m_vConv[ i ].get();
+ if ( pC->hConv == hConv )
+ pService = rpService;
+ }
+ }
+
+ if (!pService)
+ return reinterpret_cast<HDDEDATA>(DDE_FNOTPROCESSED);
+ assert(pC);
+
+ if ( nCode == XTYP_DISCONNECT)
+ {
+ DisconnectTopic(*pC->pTopic, hConv);
+ auto it = std::find_if(pService->m_vConv.begin(), pService->m_vConv.end(),
+ [&pC](const std::unique_ptr<Conversation>& rxConv) { return rxConv.get() == pC; });
+ if (it != pService->m_vConv.end())
+ pService->m_vConv.erase( it );
+ return nullptr;
+ }
+
+ bool bExec = nCode == XTYP_EXECUTE;
+ DdeTopic* pTopic = pC->pTopic;
+ DdeItem* pItem;
+ if (pTopic && !bExec && pService->HasCbFormat(nCbType))
+ pItem = FindItem( *pTopic, hText2 );
+ else
+ pItem = nullptr;
+
+ if ( !pItem && !bExec )
+ return static_cast<HDDEDATA>(DDE_FNOTPROCESSED);
+ if ( pItem )
+ pTopic->aItem = pItem->GetName();
+ else
+ pTopic->aItem.clear();
+
+ bool bRes = false;
+ switch( nCode )
+ {
+ case XTYP_REQUEST:
+ case XTYP_ADVREQ:
+ {
+ OUString aRes; // Must be free not until the end!
+ DdeData* pData;
+ if ( pTopic->IsSystemTopic() )
+ {
+ if ( pTopic->aItem == SZDDESYS_ITEM_TOPICS )
+ aRes = pService->Topics();
+ else if ( pTopic->aItem == SZDDESYS_ITEM_SYSITEMS )
+ aRes = pService->SysItems();
+ else if ( pTopic->aItem == SZDDESYS_ITEM_STATUS )
+ aRes = pService->Status();
+ else if ( pTopic->aItem == SZDDESYS_ITEM_FORMATS )
+ aRes = pService->Formats();
+ else if ( pTopic->aItem == SZDDESYS_ITEM_HELP )
+ aRes = OUString();
+ else
+ aRes = OUString();
+
+ if ( !aRes.isEmpty() )
+ pData = new DdeData( aRes );
+ else
+ pData = nullptr;
+ }
+ else if( DDEGETPUTITEM == pItem->nType )
+ {
+ pData = static_cast<DdeGetPutItem*>(pItem)->Get( DdeData::GetInternalFormat( nCbType ) );
+ }
+ else
+ {
+ pData = pTopic->Get( DdeData::GetInternalFormat( nCbType ));
+ }
+
+ if ( pData )
+ {
+ return DdeCreateDataHandle( pInst->hDdeInstSvr,
+ static_cast<LPBYTE>(const_cast<void *>(pData->xImp->pData)),
+ pData->xImp->nData,
+ 0, hText2,
+ DdeData::GetExternalFormat(
+ pData->xImp->nFmt ),
+ 0 );
+ }
+ }
+ break;
+
+ case XTYP_POKE:
+ if ( !pTopic->IsSystemTopic() )
+ {
+ DdeData d;
+ d.xImp->hData = hData;
+ d.xImp->nFmt = DdeData::GetInternalFormat( nCbType );
+ d.Lock();
+ if( DDEGETPUTITEM == pItem->nType )
+ bRes = static_cast<DdeGetPutItem*>(pItem)->Put( &d );
+ else
+ bRes = pTopic->Put( &d );
+ }
+ if ( bRes )
+ return reinterpret_cast<HDDEDATA>(DDE_FACK);
+ else
+ return reinterpret_cast<HDDEDATA>(DDE_FNOTPROCESSED);
+
+ case XTYP_ADVSTART:
+ {
+ // Is the Item turning into a HotLink for the first time?
+ if( !pItem->pImpData && pTopic->StartAdviseLoop() )
+ {
+ // Then the Item has been exchanged
+ std::vector<DdeItem*>::iterator it(std::find(pTopic->aItems.begin(),
+ pTopic->aItems.end(),
+ pItem));
+ if (it != pTopic->aItems.end())
+ pTopic->aItems.erase(it);
+
+ std::vector<DdeItem*>::iterator iter;
+ iter = std::find_if(pTopic->aItems.begin(), pTopic->aItems.end(),
+ [&hText2](const DdeItem* pDdeItem) { return *pDdeItem->pName == hText2; });
+ if (iter != pTopic->aItems.end())
+ {
+ // It was exchanged indeed
+ delete pItem;
+ pItem = nullptr;
+ }
+
+ if( pItem )
+ // It was not exchange, so back in
+ pTopic->aItems.push_back(pItem);
+ else
+ pItem = iter != pTopic->aItems.end() ? *iter : nullptr;
+ }
+
+ if (pItem)
+ {
+ IncMonitor(pItem, hConv);
+ }
+ }
+ return reinterpret_cast<HDDEDATA>(TRUE);
+
+ case XTYP_ADVSTOP:
+ DecMonitor(pItem, hConv);
+ return reinterpret_cast<HDDEDATA>(TRUE);
+
+ case XTYP_EXECUTE:
+ {
+ DdeData aExec;
+ aExec.xImp->hData = hData;
+ aExec.xImp->nFmt = DdeData::GetInternalFormat( nCbType );
+ aExec.Lock();
+ OUString aName;
+
+ aName = static_cast<const sal_Unicode *>(aExec.xImp->pData);
+
+ if( pTopic->IsSystemTopic() )
+ bRes = false;
+ else
+ bRes = pTopic->Execute( &aName );
+ }
+ if ( bRes )
+ return reinterpret_cast<HDDEDATA>(DDE_FACK);
+ else
+ return reinterpret_cast<HDDEDATA>(DDE_FNOTPROCESSED);
+ }
+
+ return nullptr;
+}
+
+DdeService* DdeInternal::FindService( HSZ hService )
+{
+ DdeServices& rSvc = DdeService::GetServices();
+ auto aI = std::find_if(rSvc.begin(), rSvc.end(),
+ [&hService](const DdeService* s) { return *s->pName == hService; });
+ if (aI != rSvc.end())
+ return *aI;
+
+ return nullptr;
+}
+
+DdeTopic* DdeInternal::FindTopic( DdeService& rService, HSZ hTopic )
+{
+ std::vector<DdeTopic*> &rTopics = rService.aTopics;
+
+ auto iter = std::find_if(rTopics.begin(), rTopics.end(),
+ [&hTopic](const DdeTopic* pTopic) { return *pTopic->pName == hTopic; });
+ if (iter != rTopics.end())
+ return *iter;
+
+ return nullptr;
+}
+
+DdeItem* DdeInternal::FindItem( DdeTopic& rTopic, HSZ hItem )
+{
+ std::vector<DdeItem*>::iterator iter;
+ std::vector<DdeItem*> &rItems = rTopic.aItems;
+ DdeInstData* pInst = ImpGetInstData();
+ assert(pInst);
+ bool bContinue = false;
+
+ do
+ { // middle check loop
+ iter = std::find_if(rItems.begin(), rItems.end(),
+ [&hItem](const DdeItem* pItem) { return *pItem->pName == hItem; });
+ if (iter != rItems.end())
+ return *iter;
+ bContinue = !bContinue;
+ if( !bContinue )
+ break;
+
+ // Let's query our subclass
+ WCHAR chBuf[250];
+ DdeQueryStringW(pInst->hDdeInstSvr,hItem,chBuf,SAL_N_ELEMENTS(chBuf),CP_WINUNICODE );
+ bContinue = rTopic.MakeItem( OUString(o3tl::toU(chBuf)) );
+ // We need to search again
+ }
+ while( bContinue );
+
+ return nullptr;
+}
+
+DdeService::DdeService( const OUString& rService )
+{
+ DdeInstData* pInst = ImpGetInstData();
+ if( !pInst )
+ pInst = ImpInitInstData();
+ pInst->nRefCount++;
+ pInst->nInstanceSvr++;
+
+ if ( !pInst->hDdeInstSvr )
+ {
+ nStatus = DMLERR_SYS_ERROR;
+ if ( !officecfg::Office::Common::Security::Scripting::DisableActiveContent::get() )
+ {
+ nStatus = sal::static_int_cast< short >(
+ DdeInitializeW( &pInst->hDdeInstSvr,
+ DdeInternal::SvrCallback,
+ APPCLASS_STANDARD |
+ CBF_SKIP_REGISTRATIONS |
+ CBF_SKIP_UNREGISTRATIONS, 0 ) );
+ }
+ pInst->pServicesSvr = new DdeServices;
+ }
+ else
+ nStatus = DMLERR_NO_ERROR;
+
+ if ( pInst->pServicesSvr )
+ pInst->pServicesSvr->push_back( this );
+
+ pName = new DdeString( pInst->hDdeInstSvr, rService );
+ if ( nStatus == DMLERR_NO_ERROR )
+ {
+ if ( !DdeNameService( pInst->hDdeInstSvr, pName->getHSZ(), nullptr,
+ DNS_REGISTER | DNS_FILTEROFF ) )
+ {
+ nStatus = DMLERR_SYS_ERROR;
+ }
+ }
+ AddFormat( SotClipboardFormatId::STRING );
+ pSysTopic = new DdeTopic( SZDDESYS_TOPIC );
+ pSysTopic->AddItem( DdeItem( SZDDESYS_ITEM_TOPICS ) );
+ pSysTopic->AddItem( DdeItem( SZDDESYS_ITEM_SYSITEMS ) );
+ pSysTopic->AddItem( DdeItem( SZDDESYS_ITEM_STATUS ) );
+ pSysTopic->AddItem( DdeItem( SZDDESYS_ITEM_FORMATS ) );
+ pSysTopic->AddItem( DdeItem( SZDDESYS_ITEM_HELP ) );
+ AddTopic( *pSysTopic );
+}
+
+DdeService::~DdeService()
+{
+ DdeInstData* pInst = ImpGetInstData();
+ assert(pInst);
+ if ( pInst->pServicesSvr )
+ std::erase(*pInst->pServicesSvr, this);
+
+ delete pSysTopic;
+ delete pName;
+
+ pInst->nInstanceSvr--;
+ pInst->nRefCount--;
+ if ( !pInst->nInstanceSvr && pInst->hDdeInstSvr )
+ {
+ if( DdeUninitialize( pInst->hDdeInstSvr ) )
+ {
+ pInst->hDdeInstSvr = 0;
+ delete pInst->pServicesSvr;
+ pInst->pServicesSvr = nullptr;
+ if( pInst->nRefCount == 0)
+ ImpDeinitInstData();
+ }
+ }
+}
+
+OUString DdeService::GetName() const
+{
+ return pName->toOUString();
+}
+
+DdeServices& DdeService::GetServices()
+{
+ DdeInstData* pInst = ImpGetInstData();
+ assert(pInst);
+ return *(pInst->pServicesSvr);
+}
+
+void DdeService::AddTopic( const DdeTopic& rTopic )
+{
+ RemoveTopic( rTopic );
+ aTopics.push_back(const_cast<DdeTopic *>(&rTopic));
+}
+
+void DdeService::RemoveTopic( const DdeTopic& rTopic )
+{
+ auto iter = std::find_if(aTopics.begin(), aTopics.end(),
+ [&rTopic](const DdeTopic* pTopic) { return DdeCmpStringHandles(pTopic->pName->getHSZ(), rTopic.pName->getHSZ()) == 0; });
+ if (iter != aTopics.end())
+ {
+ aTopics.erase(iter);
+ // Delete all conversions!
+ // Or else we work on deleted topics!
+ for( size_t n = m_vConv.size(); n; )
+ {
+ auto const& pC = m_vConv[ --n ];
+ if( pC->pTopic == &rTopic )
+ m_vConv.erase( m_vConv.begin() + n );
+ }
+ }
+}
+
+bool DdeService::HasCbFormat( sal_uInt32 nFmt )
+{
+ return std::find(aFormats.begin(), aFormats.end(), nFmt) != aFormats.end();
+}
+
+bool DdeService::HasFormat(SotClipboardFormatId nFmt)
+{
+ return HasCbFormat( DdeData::GetExternalFormat( nFmt ));
+}
+
+void DdeService::AddFormat(SotClipboardFormatId nFmt)
+{
+ sal_uInt32 nExternalFmt = DdeData::GetExternalFormat( nFmt );
+ if (HasCbFormat(nExternalFmt))
+ return;
+ aFormats.push_back( nExternalFmt );
+}
+
+void DdeService::RemoveFormat(SotClipboardFormatId nFmt)
+{
+ sal_uInt32 nExternalFmt = DdeData::GetExternalFormat( nFmt );
+ auto it = std::find(aFormats.begin(), aFormats.end(), nExternalFmt);
+ if (it != aFormats.end())
+ aFormats.erase( it );
+}
+
+DdeTopic::DdeTopic( const OUString& rName )
+{
+ DdeInstData* pInst = ImpGetInstData();
+ assert(pInst);
+ pName = new DdeString( pInst->hDdeInstSvr, rName );
+}
+
+DdeTopic::~DdeTopic()
+{
+ for (auto& rpItem : aItems)
+ {
+ rpItem->pMyTopic = nullptr;
+ delete rpItem;
+ }
+
+ delete pName;
+}
+
+OUString DdeTopic::GetName() const
+{
+ return pName->toOUString();
+}
+
+bool DdeTopic::IsSystemTopic()
+{
+ return GetName() == SZDDESYS_TOPIC;
+}
+
+DdeItem* DdeTopic::AddItem( const DdeItem& r )
+{
+ DdeItem* s;
+ if( DDEGETPUTITEM == r.nType )
+ s = new DdeGetPutItem( r );
+ else
+ s = new DdeItem( r );
+
+ aItems.push_back( s );
+ s->pMyTopic = this;
+ return s;
+}
+
+void DdeTopic::InsertItem( DdeItem* pNew )
+{
+ if( pNew )
+ {
+ aItems.push_back( pNew );
+ pNew->pMyTopic = this;
+ }
+}
+
+void DdeTopic::RemoveItem( const DdeItem& r )
+{
+ auto iter = std::find_if(aItems.begin(), aItems.end(),
+ [&r](const DdeItem* pItem) { return DdeCmpStringHandles(pItem->pName->getHSZ(), r.pName->getHSZ()) == 0; });
+
+ if ( iter != aItems.end() )
+ {
+ (*iter)->pMyTopic = nullptr;
+ delete *iter;
+ aItems.erase(iter);
+ }
+}
+
+void DdeTopic::NotifyClient( const OUString& rItem )
+{
+ DdeInstData* pInst = ImpGetInstData();
+ assert(pInst);
+ auto iter = std::find_if(aItems.begin(), aItems.end(),
+ [&rItem](const DdeItem* pItem) { return pItem->GetName().equals(rItem) && pItem->pImpData; });
+ if (iter != aItems.end())
+ DdePostAdvise( pInst->hDdeInstSvr, pName->getHSZ(), (*iter)->pName->getHSZ() );
+}
+
+void DdeInternal::DisconnectTopic(DdeTopic & rTopic, HCONV nId)
+{
+ for (const auto& rpItem : rTopic.aItems)
+ {
+ DecMonitor(rpItem, nId);
+ }
+}
+
+DdeData* DdeTopic::Get(SotClipboardFormatId /*nFmt*/)
+{
+ return nullptr;
+}
+
+bool DdeTopic::Put( const DdeData* )
+{
+ return false;
+}
+
+bool DdeTopic::Execute( const OUString* )
+{
+ return false;
+}
+
+bool DdeTopic::StartAdviseLoop()
+{
+ return false;
+}
+
+DdeItem::DdeItem( const sal_Unicode* p )
+{
+ DdeInstData* pInst = ImpGetInstData();
+ assert(pInst);
+ pName = new DdeString( pInst->hDdeInstSvr, OUString(p) );
+ nType = DDEITEM;
+ pMyTopic = nullptr;
+ pImpData = nullptr;
+}
+
+DdeItem::DdeItem( const OUString& r)
+{
+ DdeInstData* pInst = ImpGetInstData();
+ assert(pInst);
+ pName = new DdeString( pInst->hDdeInstSvr, r );
+ nType = DDEITEM;
+ pMyTopic = nullptr;
+ pImpData = nullptr;
+}
+
+DdeItem::DdeItem( const DdeItem& r)
+{
+ DdeInstData* pInst = ImpGetInstData();
+ assert(pInst);
+ pName = new DdeString( pInst->hDdeInstSvr, r.pName->toOUString() );
+ nType = DDEITEM;
+ pMyTopic = nullptr;
+ pImpData = nullptr;
+}
+
+DdeItem::~DdeItem()
+{
+ if( pMyTopic )
+ std::erase(pMyTopic->aItems, this);
+ delete pName;
+ delete pImpData;
+}
+
+OUString DdeItem::GetName() const
+{
+ return pName->toOUString();
+}
+
+void DdeItem::NotifyClient()
+{
+ if( pMyTopic && pImpData )
+ {
+ DdeInstData* pInst = ImpGetInstData();
+ assert(pInst);
+ DdePostAdvise( pInst->hDdeInstSvr, pMyTopic->pName->getHSZ(), pName->getHSZ() );
+ }
+}
+
+void DdeInternal::IncMonitor(DdeItem *const pItem, HCONV nHCnv)
+{
+ if (!pItem->pImpData)
+ {
+ pItem->pImpData = new std::vector<DdeItemImpData>;
+ if (DDEGETPUTITEM == pItem->nType)
+ {
+ static_cast<DdeGetPutItem*>(pItem)->AdviseLoop( true );
+ }
+ }
+ else
+ {
+ for (size_t n = pItem->pImpData->size(); n; )
+ {
+ if ((*pItem->pImpData)[ --n ].nHCnv == nHCnv)
+ {
+ ++(*pItem->pImpData)[ n ].nHCnv;
+ return ;
+ }
+ }
+ }
+
+ pItem->pImpData->push_back( DdeItemImpData( nHCnv ) );
+}
+
+void DdeInternal::DecMonitor(DdeItem *const pItem, HCONV nHCnv)
+{
+ if (pItem->pImpData)
+ {
+ for( size_t n = 0; n < pItem->pImpData->size(); ++n )
+ {
+ DdeItemImpData* pData = &(*pItem->pImpData)[n];
+ if( pData->nHCnv == nHCnv )
+ {
+ if( !pData->nCnt || !--pData->nCnt )
+ {
+ if (1 < pItem->pImpData->size())
+ {
+ pItem->pImpData->erase(pItem->pImpData->begin() + n);
+ }
+ else
+ {
+ delete pItem->pImpData;
+ pItem->pImpData = nullptr;
+ if (DDEGETPUTITEM == pItem->nType)
+ {
+ static_cast<DdeGetPutItem*>(pItem)->AdviseLoop(false);
+ }
+ }
+ }
+ return ;
+ }
+ }
+ }
+}
+
+short DdeItem::GetLinks()
+{
+ short nCnt = 0;
+ if( pImpData )
+ {
+ for (const auto& rData : *pImpData)
+ {
+ nCnt += rData.nCnt;
+ }
+ }
+ return nCnt;
+}
+
+DdeGetPutItem::DdeGetPutItem( const sal_Unicode* p )
+ : DdeItem( p )
+{
+ nType = DDEGETPUTITEM;
+}
+
+DdeGetPutItem::DdeGetPutItem( const OUString& rStr )
+ : DdeItem( rStr )
+{
+ nType = DDEGETPUTITEM;
+}
+
+DdeGetPutItem::DdeGetPutItem( const DdeItem& rItem )
+ : DdeItem( rItem )
+{
+ nType = DDEGETPUTITEM;
+}
+
+DdeData* DdeGetPutItem::Get(SotClipboardFormatId)
+{
+ return nullptr;
+}
+
+bool DdeGetPutItem::Put( const DdeData* )
+{
+ return false;
+}
+
+void DdeGetPutItem::AdviseLoop( bool )
+{
+}
+
+OUString DdeService::SysItems()
+{
+ OUString s;
+ for ( const auto& rpTopic : aTopics )
+ {
+ if ( rpTopic->GetName() == SZDDESYS_TOPIC )
+ {
+ short n = 0;
+ for ( const auto& rpItem : rpTopic->aItems )
+ {
+ if ( n )
+ s += "\t";
+ s += rpItem->GetName();
+ n++;
+ }
+ s += "\r\n";
+ }
+ }
+
+ return s;
+}
+
+OUString DdeService::Topics()
+{
+ OUString s;
+ short n = 0;
+
+ for ( const auto& rpTopic : aTopics )
+ {
+ if ( n )
+ s += "\t";
+ s += rpTopic->GetName();
+ n++;
+ }
+ s += "\r\n";
+
+ return s;
+}
+
+OUString DdeService::Formats()
+{
+ OUString s;
+ short n = 0;
+
+ for (size_t i = 0; i < aFormats.size(); ++i, ++n)
+ {
+ sal_uInt32 f = aFormats[ i ];
+ if ( n )
+ s += "\t";
+
+ switch( f )
+ {
+ case CF_TEXT:
+ s += "TEXT";
+ break;
+ case CF_BITMAP:
+ s += "BITMAP";
+ break;
+ default:
+ {
+ WCHAR buf[128];
+ GetClipboardFormatNameW( f, buf, SAL_N_ELEMENTS(buf) );
+ s += o3tl::toU(buf);
+ }
+ break;
+ }
+
+ }
+ s += "\r\n";
+
+ return s;
+}
+
+OUString DdeService::Status()
+{
+ return "Ready\r\n";
+}
+
+bool DdeTopic::MakeItem( const OUString& )
+{
+ return false;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/svsql/converter.cxx b/svl/source/svsql/converter.cxx
new file mode 100644
index 0000000000..7115cd8bea
--- /dev/null
+++ b/svl/source/svsql/converter.cxx
@@ -0,0 +1,31 @@
+/* -*- 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 <svl/converter.hxx>
+
+sal_Int32 SvDbaseConverter::ConvertPrecisionToDbase(sal_Int32 _nLen, sal_Int32 _nScale)
+{
+ return _nScale ? _nLen + 2 : _nLen + 1;
+}
+sal_Int32 SvDbaseConverter::ConvertPrecisionToOdbc(sal_Int32 _nLen, sal_Int32 _nScale)
+{
+ return _nScale ? _nLen - 2 : _nLen - 1;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/undo/undo.cxx b/svl/source/undo/undo.cxx
new file mode 100644
index 0000000000..9b90495d59
--- /dev/null
+++ b/svl/source/undo/undo.cxx
@@ -0,0 +1,1391 @@
+/* -*- 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 <svl/undo.hxx>
+
+#include <osl/mutex.hxx>
+#include <sal/log.hxx>
+#include <comphelper/flagguard.hxx>
+#include <comphelper/diagnose_ex.hxx>
+#include <tools/long.hxx>
+#include <libxml/xmlwriter.h>
+#include <boost/property_tree/json_parser.hpp>
+#include <unotools/datetime.hxx>
+
+#include <memory>
+#include <utility>
+#include <vector>
+#include <limits.h>
+#include <algorithm>
+
+
+SfxRepeatTarget::~SfxRepeatTarget()
+{
+}
+
+
+SfxUndoContext::~SfxUndoContext()
+{
+}
+
+
+SfxUndoAction::~SfxUndoAction() COVERITY_NOEXCEPT_FALSE
+{
+}
+
+
+SfxUndoAction::SfxUndoAction()
+ : m_aDateTime(DateTime::SYSTEM)
+{
+ m_aDateTime.ConvertToUTC();
+}
+
+
+bool SfxUndoAction::Merge( SfxUndoAction * )
+{
+ return false;
+}
+
+
+OUString SfxUndoAction::GetComment() const
+{
+ return OUString();
+}
+
+
+ViewShellId SfxUndoAction::GetViewShellId() const
+{
+ return ViewShellId(-1);
+}
+
+const DateTime& SfxUndoAction::GetDateTime() const
+{
+ return m_aDateTime;
+}
+
+OUString SfxUndoAction::GetRepeatComment(SfxRepeatTarget&) const
+{
+ return GetComment();
+}
+
+
+void SfxUndoAction::Undo()
+{
+ // These are only conceptually pure virtual
+ assert(!"pure virtual function called: SfxUndoAction::Undo()");
+}
+
+
+void SfxUndoAction::UndoWithContext( SfxUndoContext& )
+{
+ Undo();
+}
+
+
+void SfxUndoAction::Redo()
+{
+ // These are only conceptually pure virtual
+ assert(!"pure virtual function called: SfxUndoAction::Redo()");
+}
+
+
+void SfxUndoAction::RedoWithContext( SfxUndoContext& )
+{
+ Redo();
+}
+
+
+void SfxUndoAction::Repeat(SfxRepeatTarget&)
+{
+ // These are only conceptually pure virtual
+ assert(!"pure virtual function called: SfxUndoAction::Repeat()");
+}
+
+
+bool SfxUndoAction::CanRepeat(SfxRepeatTarget&) const
+{
+ return true;
+}
+
+void SfxUndoAction::dumpAsXml(xmlTextWriterPtr pWriter) const
+{
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SfxUndoAction"));
+ (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this);
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("symbol"), BAD_CAST(typeid(*this).name()));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("comment"), BAD_CAST(GetComment().toUtf8().getStr()));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("viewShellId"), BAD_CAST(OString::number(static_cast<sal_Int32>(GetViewShellId())).getStr()));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("dateTime"), BAD_CAST(utl::toISO8601(m_aDateTime.GetUNODateTime()).toUtf8().getStr()));
+ (void)xmlTextWriterEndElement(pWriter);
+}
+
+std::unique_ptr<SfxUndoAction> SfxUndoArray::Remove(int idx)
+{
+ auto ret = std::move(maUndoActions[idx].pAction);
+ maUndoActions.erase(maUndoActions.begin() + idx);
+ return ret;
+}
+
+void SfxUndoArray::Remove( size_t i_pos, size_t i_count )
+{
+ maUndoActions.erase(maUndoActions.begin() + i_pos, maUndoActions.begin() + i_pos + i_count);
+}
+
+void SfxUndoArray::Insert( std::unique_ptr<SfxUndoAction> i_action, size_t i_pos )
+{
+ maUndoActions.insert( maUndoActions.begin() + i_pos, MarkedUndoAction(std::move(i_action)) );
+}
+
+typedef ::std::vector< SfxUndoListener* > UndoListeners;
+
+struct SfxUndoManager_Data
+{
+ ::osl::Mutex aMutex;
+ SfxUndoArray maUndoArray;
+ SfxUndoArray* pActUndoArray;
+
+ sal_Int32 mnMarks;
+ sal_Int32 mnEmptyMark;
+ bool mbUndoEnabled;
+ bool mbDoing;
+ bool mbClearUntilTopLevel;
+ bool mbEmptyActions;
+
+ UndoListeners aListeners;
+
+ explicit SfxUndoManager_Data( size_t i_nMaxUndoActionCount )
+ :maUndoArray( i_nMaxUndoActionCount )
+ ,pActUndoArray( nullptr )
+ ,mnMarks( 0 )
+ ,mnEmptyMark(MARK_INVALID)
+ ,mbUndoEnabled( true )
+ ,mbDoing( false )
+ ,mbClearUntilTopLevel( false )
+ ,mbEmptyActions( true )
+ {
+ pActUndoArray = &maUndoArray;
+ }
+
+ // Copy assignment is forbidden and not implemented.
+ SfxUndoManager_Data (const SfxUndoManager_Data &) = delete;
+ SfxUndoManager_Data & operator= (const SfxUndoManager_Data &) = delete;
+};
+
+namespace svl::undo::impl
+{
+ class LockGuard
+ {
+ public:
+ explicit LockGuard( SfxUndoManager& i_manager )
+ :m_manager( i_manager )
+ {
+ m_manager.ImplEnableUndo_Lock( false );
+ }
+
+ ~LockGuard()
+ {
+ m_manager.ImplEnableUndo_Lock( true );
+ }
+
+ private:
+ SfxUndoManager& m_manager;
+ };
+
+ typedef void ( SfxUndoListener::*UndoListenerVoidMethod )();
+ typedef void ( SfxUndoListener::*UndoListenerStringMethod )( const OUString& );
+
+ namespace {
+
+ struct NotifyUndoListener
+ {
+ explicit NotifyUndoListener( UndoListenerVoidMethod i_notificationMethod )
+ :m_notificationMethod( i_notificationMethod )
+ ,m_altNotificationMethod( nullptr )
+ {
+ }
+
+ NotifyUndoListener( UndoListenerStringMethod i_notificationMethod, OUString i_actionComment )
+ :m_notificationMethod( nullptr )
+ ,m_altNotificationMethod( i_notificationMethod )
+ ,m_sActionComment(std::move( i_actionComment ))
+ {
+ }
+
+ bool is() const
+ {
+ return ( m_notificationMethod != nullptr ) || ( m_altNotificationMethod != nullptr );
+ }
+
+ void operator()( SfxUndoListener* i_listener ) const
+ {
+ assert( is() && "NotifyUndoListener: this will crash!" );
+ if ( m_altNotificationMethod != nullptr )
+ {
+ ( i_listener->*m_altNotificationMethod )( m_sActionComment );
+ }
+ else
+ {
+ ( i_listener->*m_notificationMethod )();
+ }
+ }
+
+ private:
+ UndoListenerVoidMethod m_notificationMethod;
+ UndoListenerStringMethod m_altNotificationMethod;
+ OUString m_sActionComment;
+ };
+
+ }
+
+ class UndoManagerGuard
+ {
+ public:
+ explicit UndoManagerGuard( SfxUndoManager_Data& i_managerData )
+ :m_rManagerData( i_managerData )
+ ,m_aGuard( i_managerData.aMutex )
+ {
+ }
+
+ ~UndoManagerGuard();
+
+ struct ResetGuard {
+ ResetGuard(osl::ResettableMutexGuard& r) : rGuard(r) {}
+ ~ResetGuard() { rGuard.reset(); }
+ osl::ResettableMutexGuard& rGuard;
+ };
+ ResetGuard clear()
+ {
+ m_aGuard.clear();
+ return ResetGuard(m_aGuard);
+ }
+
+ void cancelNotifications()
+ {
+ m_notifiers.clear();
+ }
+
+ /** marks the given Undo action for deletion
+
+ The Undo action will be put into a list, whose members will be deleted from within the destructor of the
+ UndoManagerGuard. This deletion will happen without the UndoManager's mutex locked.
+ */
+ void markForDeletion( std::unique_ptr<SfxUndoAction> i_action )
+ {
+ // remember
+ assert ( i_action );
+ m_aUndoActionsCleanup.emplace_back( std::move(i_action) );
+ }
+
+ /** schedules the given SfxUndoListener method to be called for all registered listeners.
+
+ The notification will happen after the Undo manager's mutex has been released, and after all pending
+ deletions of Undo actions are done.
+ */
+ void scheduleNotification( UndoListenerVoidMethod i_notificationMethod )
+ {
+ m_notifiers.emplace_back( i_notificationMethod );
+ }
+
+ void scheduleNotification( UndoListenerStringMethod i_notificationMethod, const OUString& i_actionComment )
+ {
+ m_notifiers.emplace_back( i_notificationMethod, i_actionComment );
+ }
+
+ private:
+ SfxUndoManager_Data& m_rManagerData;
+ ::osl::ResettableMutexGuard m_aGuard;
+ ::std::vector< std::unique_ptr<SfxUndoAction> > m_aUndoActionsCleanup;
+ ::std::vector< NotifyUndoListener > m_notifiers;
+ };
+
+ UndoManagerGuard::~UndoManagerGuard()
+ {
+ // copy members
+ UndoListeners aListenersCopy( m_rManagerData.aListeners );
+
+ // release mutex
+ m_aGuard.clear();
+
+ // delete all actions
+ m_aUndoActionsCleanup.clear();
+
+ // handle scheduled notification
+ for (auto const& notifier : m_notifiers)
+ {
+ if ( notifier.is() )
+ ::std::for_each( aListenersCopy.begin(), aListenersCopy.end(), notifier );
+ }
+ }
+}
+
+using namespace ::svl::undo::impl;
+
+
+SfxUndoManager::SfxUndoManager( size_t nMaxUndoActionCount )
+ :m_xData( new SfxUndoManager_Data( nMaxUndoActionCount ) )
+{
+ m_xData->mbEmptyActions = !ImplIsEmptyActions();
+}
+
+
+SfxUndoManager::~SfxUndoManager()
+{
+}
+
+
+void SfxUndoManager::EnableUndo( bool i_enable )
+{
+ UndoManagerGuard aGuard( *m_xData );
+ ImplEnableUndo_Lock( i_enable );
+
+}
+
+
+void SfxUndoManager::ImplEnableUndo_Lock( bool const i_enable )
+{
+ if ( m_xData->mbUndoEnabled == i_enable )
+ return;
+ m_xData->mbUndoEnabled = i_enable;
+}
+
+
+bool SfxUndoManager::IsUndoEnabled() const
+{
+ UndoManagerGuard aGuard( *m_xData );
+ return ImplIsUndoEnabled_Lock();
+}
+
+
+bool SfxUndoManager::ImplIsUndoEnabled_Lock() const
+{
+ return m_xData->mbUndoEnabled;
+}
+
+void SfxUndoManager::SetMaxUndoActionCount( size_t nMaxUndoActionCount )
+{
+ UndoManagerGuard aGuard( *m_xData );
+
+ // Remove entries from the pActUndoArray when we have to reduce
+ // the number of entries due to a lower nMaxUndoActionCount.
+ // Both redo and undo action entries will be removed until we reached the
+ // new nMaxUndoActionCount.
+
+ tools::Long nNumToDelete = m_xData->pActUndoArray->maUndoActions.size() - nMaxUndoActionCount;
+ while ( nNumToDelete > 0 )
+ {
+ size_t nPos = m_xData->pActUndoArray->maUndoActions.size();
+ if ( nPos > m_xData->pActUndoArray->nCurUndoAction )
+ {
+ aGuard.markForDeletion( m_xData->pActUndoArray->Remove( nPos-1 ) );
+ --nNumToDelete;
+ }
+
+ if ( nNumToDelete > 0 && m_xData->pActUndoArray->nCurUndoAction > 0 )
+ {
+ aGuard.markForDeletion( m_xData->pActUndoArray->Remove(0) );
+ --m_xData->pActUndoArray->nCurUndoAction;
+ --nNumToDelete;
+ }
+
+ if ( nPos == m_xData->pActUndoArray->maUndoActions.size() )
+ break; // Cannot delete more entries
+ }
+
+ m_xData->pActUndoArray->nMaxUndoActions = nMaxUndoActionCount;
+ ImplCheckEmptyActions();
+}
+
+size_t SfxUndoManager::GetMaxUndoActionCount() const
+{
+ return m_xData->pActUndoArray->nMaxUndoActions;
+}
+
+void SfxUndoManager::ImplClearCurrentLevel_NoNotify( UndoManagerGuard& i_guard )
+{
+ // clear array
+ while ( !m_xData->pActUndoArray->maUndoActions.empty() )
+ {
+ size_t deletePos = m_xData->pActUndoArray->maUndoActions.size() - 1;
+ i_guard.markForDeletion( m_xData->pActUndoArray->Remove( deletePos ) );
+ }
+
+ m_xData->pActUndoArray->nCurUndoAction = 0;
+
+ m_xData->mnMarks = 0;
+ m_xData->mnEmptyMark = MARK_INVALID;
+ ImplCheckEmptyActions();
+}
+
+
+void SfxUndoManager::Clear()
+{
+ UndoManagerGuard aGuard( *m_xData );
+
+ SAL_WARN_IF( ImplIsInListAction_Lock(), "svl",
+ "SfxUndoManager::Clear: suspicious call - do you really wish to clear the current level?" );
+ ImplClearCurrentLevel_NoNotify( aGuard );
+
+ // notify listeners
+ aGuard.scheduleNotification( &SfxUndoListener::cleared );
+}
+
+
+void SfxUndoManager::ClearAllLevels()
+{
+ UndoManagerGuard aGuard( *m_xData );
+ ImplClearCurrentLevel_NoNotify( aGuard );
+
+ if ( ImplIsInListAction_Lock() )
+ {
+ m_xData->mbClearUntilTopLevel = true;
+ }
+ else
+ {
+ aGuard.scheduleNotification( &SfxUndoListener::cleared );
+ }
+}
+
+
+void SfxUndoManager::ImplClearRedo_NoLock( bool const i_currentLevel )
+{
+ UndoManagerGuard aGuard( *m_xData );
+ ImplClearRedo( aGuard, i_currentLevel );
+}
+
+
+void SfxUndoManager::ClearRedo()
+{
+ SAL_WARN_IF( IsInListAction(), "svl",
+ "SfxUndoManager::ClearRedo: suspicious call - do you really wish to clear the current level?" );
+ ImplClearRedo_NoLock( CurrentLevel );
+}
+
+
+void SfxUndoManager::Reset()
+{
+ UndoManagerGuard aGuard( *m_xData );
+
+ // clear all locks
+ while ( !ImplIsUndoEnabled_Lock() )
+ ImplEnableUndo_Lock( true );
+
+ // cancel all list actions
+ while ( IsInListAction() )
+ ImplLeaveListAction( false, aGuard );
+
+ // clear both stacks
+ ImplClearCurrentLevel_NoNotify( aGuard );
+
+ // cancel the notifications scheduled by ImplLeaveListAction,
+ // as we want to do an own, dedicated notification
+ aGuard.cancelNotifications();
+
+ // schedule notification
+ aGuard.scheduleNotification( &SfxUndoListener::resetAll );
+}
+
+
+void SfxUndoManager::ImplClearUndo( UndoManagerGuard& i_guard )
+{
+ while ( m_xData->pActUndoArray->nCurUndoAction > 0 )
+ {
+ i_guard.markForDeletion( m_xData->pActUndoArray->Remove( 0 ) );
+ --m_xData->pActUndoArray->nCurUndoAction;
+ }
+ ImplCheckEmptyActions();
+ // TODO: notifications? We don't have clearedUndo, only cleared and clearedRedo at the SfxUndoListener
+}
+
+
+void SfxUndoManager::ImplClearRedo( UndoManagerGuard& i_guard, bool const i_currentLevel )
+{
+ SfxUndoArray* pUndoArray = ( i_currentLevel == SfxUndoManager::CurrentLevel ) ? m_xData->pActUndoArray : &m_xData->maUndoArray;
+
+ // clearance
+ while ( pUndoArray->maUndoActions.size() > pUndoArray->nCurUndoAction )
+ {
+ size_t deletePos = pUndoArray->maUndoActions.size() - 1;
+ i_guard.markForDeletion( pUndoArray->Remove( deletePos ) );
+ }
+
+ ImplCheckEmptyActions();
+ // notification - only if the top level's stack was cleared
+ if ( i_currentLevel == SfxUndoManager::TopLevel )
+ i_guard.scheduleNotification( &SfxUndoListener::clearedRedo );
+}
+
+
+bool SfxUndoManager::ImplAddUndoAction_NoNotify( std::unique_ptr<SfxUndoAction> pAction, bool bTryMerge, bool bClearRedo, UndoManagerGuard& i_guard )
+{
+ if ( !ImplIsUndoEnabled_Lock() || ( m_xData->pActUndoArray->nMaxUndoActions == 0 ) )
+ {
+ i_guard.markForDeletion( std::move(pAction) );
+ return false;
+ }
+
+ // merge, if required
+ SfxUndoAction* pMergeWithAction = m_xData->pActUndoArray->nCurUndoAction ?
+ m_xData->pActUndoArray->maUndoActions[m_xData->pActUndoArray->nCurUndoAction-1].pAction.get() : nullptr;
+ if ( bTryMerge && pMergeWithAction )
+ {
+ bool bMerged = pMergeWithAction->Merge( pAction.get() );
+ if ( bMerged )
+ {
+ i_guard.markForDeletion( std::move(pAction) );
+ return false;
+ }
+ }
+
+ // clear redo stack, if requested
+ if ( bClearRedo && ( ImplGetRedoActionCount_Lock() > 0 ) )
+ ImplClearRedo( i_guard, SfxUndoManager::CurrentLevel );
+
+ // respect max number
+ if( m_xData->pActUndoArray == &m_xData->maUndoArray )
+ {
+ while(m_xData->pActUndoArray->maUndoActions.size() >= m_xData->pActUndoArray->nMaxUndoActions)
+ {
+ i_guard.markForDeletion( m_xData->pActUndoArray->Remove(0) );
+ if (m_xData->pActUndoArray->nCurUndoAction > 0)
+ {
+ --m_xData->pActUndoArray->nCurUndoAction;
+ }
+ else
+ {
+ assert(!"CurrentUndoAction going negative (!)");
+ }
+ // fdo#66071 invalidate the current empty mark when removing
+ --m_xData->mnEmptyMark;
+ }
+ }
+
+ // append new action
+ m_xData->pActUndoArray->Insert( std::move(pAction), m_xData->pActUndoArray->nCurUndoAction++ );
+ ImplCheckEmptyActions();
+ return true;
+}
+
+
+void SfxUndoManager::AddUndoAction( std::unique_ptr<SfxUndoAction> pAction, bool bTryMerge )
+{
+ UndoManagerGuard aGuard( *m_xData );
+
+ // add
+ auto pActionTmp = pAction.get();
+ if ( ImplAddUndoAction_NoNotify( std::move(pAction), bTryMerge, true, aGuard ) )
+ {
+ // notify listeners
+ aGuard.scheduleNotification( &SfxUndoListener::undoActionAdded, pActionTmp->GetComment() );
+ }
+}
+
+
+size_t SfxUndoManager::GetUndoActionCount( bool const i_currentLevel ) const
+{
+ UndoManagerGuard aGuard( *m_xData );
+ const SfxUndoArray* pUndoArray = i_currentLevel ? m_xData->pActUndoArray : &m_xData->maUndoArray;
+ return pUndoArray->nCurUndoAction;
+}
+
+
+OUString SfxUndoManager::GetUndoActionComment( size_t nNo, bool const i_currentLevel ) const
+{
+ UndoManagerGuard aGuard( *m_xData );
+
+ OUString sComment;
+ const SfxUndoArray* pUndoArray = i_currentLevel ? m_xData->pActUndoArray : &m_xData->maUndoArray;
+ assert(nNo < pUndoArray->nCurUndoAction);
+ if( nNo < pUndoArray->nCurUndoAction )
+ sComment = pUndoArray->maUndoActions[ pUndoArray->nCurUndoAction - 1 - nNo ].pAction->GetComment();
+ return sComment;
+}
+
+
+SfxUndoAction* SfxUndoManager::GetUndoAction( size_t nNo ) const
+{
+ UndoManagerGuard aGuard( *m_xData );
+
+ assert(nNo < m_xData->pActUndoArray->nCurUndoAction);
+ if( nNo >= m_xData->pActUndoArray->nCurUndoAction )
+ return nullptr;
+ return m_xData->pActUndoArray->maUndoActions[m_xData->pActUndoArray->nCurUndoAction-1-nNo].pAction.get();
+}
+
+
+/** clears the redo stack and removes the top undo action */
+void SfxUndoManager::RemoveLastUndoAction()
+{
+ UndoManagerGuard aGuard( *m_xData );
+
+ ENSURE_OR_RETURN_VOID( m_xData->pActUndoArray->nCurUndoAction, "svl::SfxUndoManager::RemoveLastUndoAction(), no action to remove?!" );
+
+ m_xData->pActUndoArray->nCurUndoAction--;
+
+ // delete redo-actions and top action
+ for ( size_t nPos = m_xData->pActUndoArray->maUndoActions.size(); nPos > m_xData->pActUndoArray->nCurUndoAction; --nPos )
+ {
+ aGuard.markForDeletion( std::move(m_xData->pActUndoArray->maUndoActions[nPos-1].pAction) );
+ }
+
+ m_xData->pActUndoArray->Remove(
+ m_xData->pActUndoArray->nCurUndoAction,
+ m_xData->pActUndoArray->maUndoActions.size() - m_xData->pActUndoArray->nCurUndoAction );
+ ImplCheckEmptyActions();
+}
+
+
+bool SfxUndoManager::IsDoing() const
+{
+ UndoManagerGuard aGuard( *m_xData );
+ return m_xData->mbDoing;
+}
+
+
+bool SfxUndoManager::Undo()
+{
+ return ImplUndo( nullptr );
+}
+
+
+bool SfxUndoManager::UndoWithContext( SfxUndoContext& i_context )
+{
+ return ImplUndo( &i_context );
+}
+
+
+bool SfxUndoManager::ImplUndo( SfxUndoContext* i_contextOrNull )
+{
+ UndoManagerGuard aGuard( *m_xData );
+ assert( !IsDoing() && "SfxUndoManager::Undo: *nested* Undo/Redo actions? How this?" );
+
+ ::comphelper::FlagGuard aDoingGuard( m_xData->mbDoing );
+ LockGuard aLockGuard( *this );
+
+ if ( ImplIsInListAction_Lock() )
+ {
+ assert(!"SfxUndoManager::Undo: not possible when within a list action!");
+ return false;
+ }
+
+ if ( m_xData->pActUndoArray->nCurUndoAction == 0 )
+ {
+ SAL_WARN("svl", "SfxUndoManager::Undo: undo stack is empty!" );
+ return false;
+ }
+
+ if (i_contextOrNull && i_contextOrNull->GetUndoOffset() > 0)
+ {
+ size_t nCurrent = m_xData->pActUndoArray->nCurUndoAction;
+ size_t nOffset = i_contextOrNull->GetUndoOffset();
+ if (nCurrent >= nOffset + 1)
+ {
+ // Move the action we want to execute to the top of the undo stack.
+ // data() + nCurrent - nOffset - 1 is the start, data() + nCurrent - nOffset is what we
+ // want to move to the top, maUndoActions.data() + nCurrent is past the end/top of the
+ // undo stack.
+ std::rotate(m_xData->pActUndoArray->maUndoActions.data() + nCurrent - nOffset - 1,
+ m_xData->pActUndoArray->maUndoActions.data() + nCurrent - nOffset,
+ m_xData->pActUndoArray->maUndoActions.data() + nCurrent);
+ }
+ }
+
+ SfxUndoAction* pAction = m_xData->pActUndoArray->maUndoActions[ --m_xData->pActUndoArray->nCurUndoAction ].pAction.get();
+ const OUString sActionComment = pAction->GetComment();
+ try
+ {
+ // clear the guard/mutex before calling into the SfxUndoAction - this can be an extension-implemented UNO component
+ // nowadays ...
+ auto aResetGuard(aGuard.clear());
+ if ( i_contextOrNull != nullptr )
+ pAction->UndoWithContext( *i_contextOrNull );
+ else
+ pAction->Undo();
+ }
+ catch( ... )
+ {
+ // in theory, somebody might have tampered with all of *m_xData while the mutex was unlocked. So, see if
+ // we still find pAction in our current Undo array
+ size_t nCurAction = 0;
+ while ( nCurAction < m_xData->pActUndoArray->maUndoActions.size() )
+ {
+ if ( m_xData->pActUndoArray->maUndoActions[ nCurAction++ ].pAction.get() == pAction )
+ {
+ // the Undo action is still there ...
+ // assume the error is a permanent failure, and clear the Undo stack
+ ImplClearUndo( aGuard );
+ throw;
+ }
+ }
+ SAL_WARN("svl", "SfxUndoManager::Undo: can't clear the Undo stack after the failure - some other party was faster ..." );
+ throw;
+ }
+
+ aGuard.scheduleNotification( &SfxUndoListener::actionUndone, sActionComment );
+
+ return true;
+}
+
+
+size_t SfxUndoManager::GetRedoActionCount( bool const i_currentLevel ) const
+{
+ UndoManagerGuard aGuard( *m_xData );
+ return ImplGetRedoActionCount_Lock( i_currentLevel );
+}
+
+
+size_t SfxUndoManager::ImplGetRedoActionCount_Lock( bool const i_currentLevel ) const
+{
+ const SfxUndoArray* pUndoArray = i_currentLevel ? m_xData->pActUndoArray : &m_xData->maUndoArray;
+ return pUndoArray->maUndoActions.size() - pUndoArray->nCurUndoAction;
+}
+
+
+SfxUndoAction* SfxUndoManager::GetRedoAction(size_t nNo) const
+{
+ UndoManagerGuard aGuard( *m_xData );
+
+ const SfxUndoArray* pUndoArray = m_xData->pActUndoArray;
+ if ( (pUndoArray->nCurUndoAction) > pUndoArray->maUndoActions.size() )
+ {
+ return nullptr;
+ }
+ return pUndoArray->maUndoActions[pUndoArray->nCurUndoAction + nNo].pAction.get();
+}
+
+
+OUString SfxUndoManager::GetRedoActionComment( size_t nNo, bool const i_currentLevel ) const
+{
+ OUString sComment;
+ UndoManagerGuard aGuard( *m_xData );
+ const SfxUndoArray* pUndoArray = i_currentLevel ? m_xData->pActUndoArray : &m_xData->maUndoArray;
+ if ( (pUndoArray->nCurUndoAction + nNo) < pUndoArray->maUndoActions.size() )
+ {
+ sComment = pUndoArray->maUndoActions[ pUndoArray->nCurUndoAction + nNo ].pAction->GetComment();
+ }
+ return sComment;
+}
+
+
+bool SfxUndoManager::Redo()
+{
+ return ImplRedo( nullptr );
+}
+
+
+bool SfxUndoManager::RedoWithContext( SfxUndoContext& i_context )
+{
+ return ImplRedo( &i_context );
+}
+
+
+bool SfxUndoManager::ImplRedo( SfxUndoContext* i_contextOrNull )
+{
+ UndoManagerGuard aGuard( *m_xData );
+ assert( !IsDoing() && "SfxUndoManager::Redo: *nested* Undo/Redo actions? How this?" );
+
+ ::comphelper::FlagGuard aDoingGuard( m_xData->mbDoing );
+ LockGuard aLockGuard( *this );
+
+ if ( ImplIsInListAction_Lock() )
+ {
+ assert(!"SfxUndoManager::Redo: not possible when within a list action!");
+ return false;
+ }
+
+ if ( m_xData->pActUndoArray->nCurUndoAction >= m_xData->pActUndoArray->maUndoActions.size() )
+ {
+ SAL_WARN("svl", "SfxUndoManager::Redo: redo stack is empty!");
+ return false;
+ }
+
+ SfxUndoAction* pAction = m_xData->pActUndoArray->maUndoActions[ m_xData->pActUndoArray->nCurUndoAction++ ].pAction.get();
+ const OUString sActionComment = pAction->GetComment();
+ try
+ {
+ // clear the guard/mutex before calling into the SfxUndoAction - this can be an extension-implemented UNO component
+ // nowadays ...
+ auto aResetGuard(aGuard.clear());
+ if ( i_contextOrNull != nullptr )
+ pAction->RedoWithContext( *i_contextOrNull );
+ else
+ pAction->Redo();
+ }
+ catch( ... )
+ {
+ // in theory, somebody might have tampered with all of *m_xData while the mutex was unlocked. So, see if
+ // we still find pAction in our current Undo array
+ size_t nCurAction = 0;
+ while ( nCurAction < m_xData->pActUndoArray->maUndoActions.size() )
+ {
+ if ( m_xData->pActUndoArray->maUndoActions[ nCurAction ].pAction.get() == pAction )
+ {
+ // the Undo action is still there ...
+ // assume the error is a permanent failure, and clear the Undo stack
+ ImplClearRedo( aGuard, SfxUndoManager::CurrentLevel );
+ throw;
+ }
+ ++nCurAction;
+ }
+ SAL_WARN("svl", "SfxUndoManager::Redo: can't clear the Undo stack after the failure - some other party was faster ..." );
+ throw;
+ }
+
+ ImplCheckEmptyActions();
+ aGuard.scheduleNotification( &SfxUndoListener::actionRedone, sActionComment );
+
+ return true;
+}
+
+
+size_t SfxUndoManager::GetRepeatActionCount() const
+{
+ UndoManagerGuard aGuard( *m_xData );
+ return m_xData->pActUndoArray->maUndoActions.size();
+}
+
+
+OUString SfxUndoManager::GetRepeatActionComment(SfxRepeatTarget &rTarget) const
+{
+ UndoManagerGuard aGuard( *m_xData );
+ return m_xData->pActUndoArray->maUndoActions[ m_xData->pActUndoArray->maUndoActions.size() - 1 ].pAction
+ ->GetRepeatComment(rTarget);
+}
+
+
+bool SfxUndoManager::Repeat( SfxRepeatTarget &rTarget )
+{
+ UndoManagerGuard aGuard( *m_xData );
+ if ( !m_xData->pActUndoArray->maUndoActions.empty() )
+ {
+ SfxUndoAction* pAction = m_xData->pActUndoArray->maUndoActions.back().pAction.get();
+ auto aResetGuard(aGuard.clear());
+ if ( pAction->CanRepeat( rTarget ) )
+ pAction->Repeat( rTarget );
+ return true;
+ }
+
+ return false;
+}
+
+
+bool SfxUndoManager::CanRepeat( SfxRepeatTarget &rTarget ) const
+{
+ UndoManagerGuard aGuard( *m_xData );
+ if ( !m_xData->pActUndoArray->maUndoActions.empty() )
+ {
+ size_t nActionNo = m_xData->pActUndoArray->maUndoActions.size() - 1;
+ return m_xData->pActUndoArray->maUndoActions[nActionNo].pAction->CanRepeat(rTarget);
+ }
+ return false;
+}
+
+
+void SfxUndoManager::AddUndoListener( SfxUndoListener& i_listener )
+{
+ UndoManagerGuard aGuard( *m_xData );
+ m_xData->aListeners.push_back( &i_listener );
+}
+
+
+void SfxUndoManager::RemoveUndoListener( SfxUndoListener& i_listener )
+{
+ UndoManagerGuard aGuard( *m_xData );
+ auto lookup = std::find(m_xData->aListeners.begin(), m_xData->aListeners.end(), &i_listener);
+ if (lookup != m_xData->aListeners.end())
+ m_xData->aListeners.erase( lookup );
+}
+
+/**
+ * Inserts a ListUndoAction and sets its UndoArray as current.
+ */
+void SfxUndoManager::EnterListAction( const OUString& rComment,
+ const OUString &rRepeatComment, sal_uInt16 nId,
+ ViewShellId nViewShellId )
+{
+ UndoManagerGuard aGuard( *m_xData );
+
+ if( !ImplIsUndoEnabled_Lock() )
+ return;
+
+ if ( !m_xData->maUndoArray.nMaxUndoActions )
+ return;
+
+ SfxListUndoAction* pAction = new SfxListUndoAction( rComment, rRepeatComment, nId, nViewShellId, m_xData->pActUndoArray );
+ OSL_VERIFY( ImplAddUndoAction_NoNotify( std::unique_ptr<SfxUndoAction>(pAction), false, false, aGuard ) );
+ // expected to succeed: all conditions under which it could fail should have been checked already
+ m_xData->pActUndoArray = pAction;
+
+ // notification
+ aGuard.scheduleNotification( &SfxUndoListener::listActionEntered, rComment );
+}
+
+
+bool SfxUndoManager::IsInListAction() const
+{
+ UndoManagerGuard aGuard( *m_xData );
+ return ImplIsInListAction_Lock();
+}
+
+
+bool SfxUndoManager::ImplIsInListAction_Lock() const
+{
+ return m_xData->pActUndoArray != &m_xData->maUndoArray;
+}
+
+
+size_t SfxUndoManager::GetListActionDepth() const
+{
+ UndoManagerGuard aGuard( *m_xData );
+ size_t nDepth(0);
+
+ SfxUndoArray* pLookup( m_xData->pActUndoArray );
+ while ( pLookup != &m_xData->maUndoArray )
+ {
+ pLookup = pLookup->pFatherUndoArray;
+ ++nDepth;
+ }
+
+ return nDepth;
+}
+
+
+size_t SfxUndoManager::LeaveListAction()
+{
+ UndoManagerGuard aGuard( *m_xData );
+ size_t nCount = ImplLeaveListAction( false, aGuard );
+
+ if ( m_xData->mbClearUntilTopLevel )
+ {
+ ImplClearCurrentLevel_NoNotify( aGuard );
+ if ( !ImplIsInListAction_Lock() )
+ {
+ m_xData->mbClearUntilTopLevel = false;
+ aGuard.scheduleNotification( &SfxUndoListener::cleared );
+ }
+ nCount = 0;
+ }
+
+ return nCount;
+}
+
+
+size_t SfxUndoManager::LeaveAndMergeListAction()
+{
+ UndoManagerGuard aGuard( *m_xData );
+ return ImplLeaveListAction( true, aGuard );
+}
+
+
+size_t SfxUndoManager::ImplLeaveListAction( const bool i_merge, UndoManagerGuard& i_guard )
+{
+ if ( !ImplIsUndoEnabled_Lock() )
+ return 0;
+
+ if ( !m_xData->maUndoArray.nMaxUndoActions )
+ return 0;
+
+ if( !ImplIsInListAction_Lock() )
+ {
+ SAL_WARN("svl", "svl::SfxUndoManager::ImplLeaveListAction, called without calling EnterListAction()!" );
+ return 0;
+ }
+
+ assert(m_xData->pActUndoArray->pFatherUndoArray);
+
+ // the array/level which we're about to leave
+ SfxUndoArray* pArrayToLeave = m_xData->pActUndoArray;
+ // one step up
+ m_xData->pActUndoArray = m_xData->pActUndoArray->pFatherUndoArray;
+
+ // If no undo actions were added to the list, delete the list action
+ const size_t nListActionElements = pArrayToLeave->nCurUndoAction;
+ if ( nListActionElements == 0 )
+ {
+ i_guard.markForDeletion( m_xData->pActUndoArray->Remove( --m_xData->pActUndoArray->nCurUndoAction ) );
+ i_guard.scheduleNotification( &SfxUndoListener::listActionCancelled );
+ return 0;
+ }
+
+ // now that it is finally clear the list action is non-trivial, and does participate in the Undo stack, clear
+ // the redo stack
+ ImplClearRedo( i_guard, SfxUndoManager::CurrentLevel );
+
+ SfxUndoAction* pCurrentAction= m_xData->pActUndoArray->maUndoActions[ m_xData->pActUndoArray->nCurUndoAction-1 ].pAction.get();
+ SfxListUndoAction* pListAction = dynamic_cast< SfxListUndoAction * >( pCurrentAction );
+ ENSURE_OR_RETURN( pListAction, "SfxUndoManager::ImplLeaveListAction: list action expected at this position!", nListActionElements );
+
+ if ( i_merge )
+ {
+ // merge the list action with its predecessor on the same level
+ SAL_WARN_IF( m_xData->pActUndoArray->nCurUndoAction <= 1, "svl",
+ "SfxUndoManager::ImplLeaveListAction: cannot merge the list action if there's no other action on the same level - check this beforehand!" );
+ if ( m_xData->pActUndoArray->nCurUndoAction > 1 )
+ {
+ std::unique_ptr<SfxUndoAction> pPreviousAction = m_xData->pActUndoArray->Remove( m_xData->pActUndoArray->nCurUndoAction - 2 );
+ --m_xData->pActUndoArray->nCurUndoAction;
+ pListAction->SetComment( pPreviousAction->GetComment() );
+ pListAction->Insert( std::move(pPreviousAction), 0 );
+ ++pListAction->nCurUndoAction;
+ }
+ }
+
+ // if the undo array has no comment, try to get it from its children
+ if ( pListAction->GetComment().isEmpty() )
+ {
+ for( size_t n = 0; n < pListAction->maUndoActions.size(); n++ )
+ {
+ if (!pListAction->maUndoActions[n].pAction->GetComment().isEmpty())
+ {
+ pListAction->SetComment( pListAction->maUndoActions[n].pAction->GetComment() );
+ break;
+ }
+ }
+ }
+
+ ImplIsEmptyActions();
+ // notify listeners
+ i_guard.scheduleNotification( &SfxUndoListener::listActionLeft, pListAction->GetComment() );
+
+ // outta here
+ return nListActionElements;
+}
+
+UndoStackMark SfxUndoManager::MarkTopUndoAction()
+{
+ UndoManagerGuard aGuard( *m_xData );
+
+ SAL_WARN_IF( IsInListAction(), "svl",
+ "SfxUndoManager::MarkTopUndoAction(): suspicious call!" );
+ assert((m_xData->mnMarks + 1) < (m_xData->mnEmptyMark - 1) &&
+ "SfxUndoManager::MarkTopUndoAction(): mark overflow!");
+
+ size_t const nActionPos = m_xData->maUndoArray.nCurUndoAction;
+ if (0 == nActionPos)
+ {
+ --m_xData->mnEmptyMark;
+ return m_xData->mnEmptyMark;
+ }
+
+ m_xData->maUndoArray.maUndoActions[ nActionPos-1 ].aMarks.push_back(
+ ++m_xData->mnMarks );
+ return m_xData->mnMarks;
+}
+
+void SfxUndoManager::RemoveMark( UndoStackMark const i_mark )
+{
+ UndoManagerGuard aGuard( *m_xData );
+
+ if ((m_xData->mnEmptyMark < i_mark) || (MARK_INVALID == i_mark))
+ {
+ return; // nothing to remove
+ }
+ else if (i_mark == m_xData->mnEmptyMark)
+ {
+ --m_xData->mnEmptyMark; // never returned from MarkTop => invalid
+ return;
+ }
+
+ for ( size_t i=0; i<m_xData->maUndoArray.maUndoActions.size(); ++i )
+ {
+ MarkedUndoAction& rAction = m_xData->maUndoArray.maUndoActions[i];
+ auto markPos = std::find(rAction.aMarks.begin(), rAction.aMarks.end(), i_mark);
+ if (markPos != rAction.aMarks.end())
+ {
+ rAction.aMarks.erase( markPos );
+ return;
+ }
+ }
+ SAL_WARN("svl", "SfxUndoManager::RemoveMark: mark not found!");
+ // TODO: this might be too offensive. There are situations where we implicitly remove marks
+ // without our clients, in particular the client which created the mark, having a chance to know
+ // about this.
+}
+
+bool SfxUndoManager::HasTopUndoActionMark( UndoStackMark const i_mark )
+{
+ UndoManagerGuard aGuard( *m_xData );
+
+ size_t nActionPos = m_xData->maUndoArray.nCurUndoAction;
+ if ( nActionPos == 0 )
+ {
+ return (i_mark == m_xData->mnEmptyMark);
+ }
+
+ const MarkedUndoAction& rAction =
+ m_xData->maUndoArray.maUndoActions[ nActionPos-1 ];
+
+ return std::find(rAction.aMarks.begin(), rAction.aMarks.end(), i_mark) != rAction.aMarks.end();
+}
+
+
+void SfxUndoManager::RemoveOldestUndoAction()
+{
+ UndoManagerGuard aGuard( *m_xData );
+
+ if ( IsInListAction() && ( m_xData->maUndoArray.nCurUndoAction == 1 ) )
+ {
+ assert(!"SfxUndoManager::RemoveOldestUndoActions: cannot remove a not-yet-closed list action!");
+ return;
+ }
+
+ aGuard.markForDeletion( m_xData->maUndoArray.Remove( 0 ) );
+ --m_xData->maUndoArray.nCurUndoAction;
+ ImplCheckEmptyActions();
+}
+
+void SfxUndoManager::dumpAsXml(xmlTextWriterPtr pWriter) const
+{
+ UndoManagerGuard aGuard(*m_xData);
+
+ bool bOwns = false;
+ if (!pWriter)
+ {
+ pWriter = xmlNewTextWriterFilename("undo.xml", 0);
+ xmlTextWriterSetIndent(pWriter,1);
+ (void)xmlTextWriterSetIndentString(pWriter, BAD_CAST(" "));
+ (void)xmlTextWriterStartDocument(pWriter, nullptr, nullptr, nullptr);
+ bOwns = true;
+ }
+
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SfxUndoManager"));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("nUndoActionCount"), BAD_CAST(OString::number(GetUndoActionCount()).getStr()));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("nRedoActionCount"), BAD_CAST(OString::number(GetRedoActionCount()).getStr()));
+
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("undoActions"));
+ for (size_t i = 0; i < GetUndoActionCount(); ++i)
+ {
+ const SfxUndoArray* pUndoArray = m_xData->pActUndoArray;
+ pUndoArray->maUndoActions[pUndoArray->nCurUndoAction - 1 - i].pAction->dumpAsXml(pWriter);
+ }
+ (void)xmlTextWriterEndElement(pWriter);
+
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("redoActions"));
+ for (size_t i = 0; i < GetRedoActionCount(); ++i)
+ {
+ const SfxUndoArray* pUndoArray = m_xData->pActUndoArray;
+ pUndoArray->maUndoActions[pUndoArray->nCurUndoAction + i].pAction->dumpAsXml(pWriter);
+ }
+ (void)xmlTextWriterEndElement(pWriter);
+
+ (void)xmlTextWriterEndElement(pWriter);
+ if (bOwns)
+ {
+ (void)xmlTextWriterEndDocument(pWriter);
+ xmlFreeTextWriter(pWriter);
+ }
+}
+
+/// Returns a JSON representation of pAction.
+static boost::property_tree::ptree lcl_ActionToJson(size_t nIndex, SfxUndoAction const * pAction)
+{
+ boost::property_tree::ptree aRet;
+ aRet.put("index", nIndex);
+ aRet.put("comment", pAction->GetComment().toUtf8().getStr());
+ aRet.put("viewId", static_cast<sal_Int32>(pAction->GetViewShellId()));
+ aRet.put("dateTime", utl::toISO8601(pAction->GetDateTime().GetUNODateTime()).toUtf8().getStr());
+ return aRet;
+}
+
+OUString SfxUndoManager::GetUndoActionsInfo() const
+{
+ boost::property_tree::ptree aActions;
+ const SfxUndoArray* pUndoArray = m_xData->pActUndoArray;
+ for (size_t i = 0; i < GetUndoActionCount(); ++i)
+ {
+ boost::property_tree::ptree aAction = lcl_ActionToJson(i, pUndoArray->maUndoActions[pUndoArray->nCurUndoAction - 1 - i].pAction.get());
+ aActions.push_back(std::make_pair("", aAction));
+ }
+
+ boost::property_tree::ptree aTree;
+ aTree.add_child("actions", aActions);
+ std::stringstream aStream;
+ boost::property_tree::write_json(aStream, aTree);
+ return OUString::fromUtf8(aStream.str());
+}
+
+OUString SfxUndoManager::GetRedoActionsInfo() const
+{
+ boost::property_tree::ptree aActions;
+ const SfxUndoArray* pUndoArray = m_xData->pActUndoArray;
+ size_t nCount = GetRedoActionCount();
+ for (size_t i = 0; i < nCount; ++i)
+ {
+ size_t nIndex = nCount - i - 1;
+ boost::property_tree::ptree aAction = lcl_ActionToJson(nIndex, pUndoArray->maUndoActions[pUndoArray->nCurUndoAction + nIndex].pAction.get());
+ aActions.push_back(std::make_pair("", aAction));
+ }
+
+ boost::property_tree::ptree aTree;
+ aTree.add_child("actions", aActions);
+ std::stringstream aStream;
+ boost::property_tree::write_json(aStream, aTree);
+ return OUString::fromUtf8(aStream.str());
+}
+
+bool SfxUndoManager::IsEmptyActions() const
+{
+ UndoManagerGuard aGuard(*m_xData);
+
+ return ImplIsEmptyActions();
+}
+
+inline bool SfxUndoManager::ImplIsEmptyActions() const
+{
+ return m_xData->maUndoArray.nCurUndoAction || m_xData->maUndoArray.maUndoActions.size() - m_xData->maUndoArray.nCurUndoAction;
+}
+
+void SfxUndoManager::ImplCheckEmptyActions()
+{
+ bool bEmptyActions = ImplIsEmptyActions();
+ if (m_xData->mbEmptyActions != bEmptyActions)
+ {
+ m_xData->mbEmptyActions = bEmptyActions;
+ EmptyActionsChanged();
+ }
+}
+
+void SfxUndoManager::EmptyActionsChanged()
+{
+
+}
+
+struct SfxListUndoAction::Impl
+{
+ sal_uInt16 mnId;
+ ViewShellId mnViewShellId;
+
+ OUString maComment;
+ OUString maRepeatComment;
+
+ Impl( sal_uInt16 nId, ViewShellId nViewShellId, OUString aComment, OUString aRepeatComment ) :
+ mnId(nId), mnViewShellId(nViewShellId), maComment(std::move(aComment)), maRepeatComment(std::move(aRepeatComment)) {}
+};
+
+sal_uInt16 SfxListUndoAction::GetId() const
+{
+ return mpImpl->mnId;
+}
+
+OUString SfxListUndoAction::GetComment() const
+{
+ return mpImpl->maComment;
+}
+
+ViewShellId SfxListUndoAction::GetViewShellId() const
+{
+ return mpImpl->mnViewShellId;
+}
+
+void SfxListUndoAction::SetComment(const OUString& rComment)
+{
+ mpImpl->maComment = rComment;
+}
+
+OUString SfxListUndoAction::GetRepeatComment(SfxRepeatTarget &) const
+{
+ return mpImpl->maRepeatComment;
+}
+
+SfxListUndoAction::SfxListUndoAction(
+ const OUString &rComment,
+ const OUString &rRepeatComment,
+ sal_uInt16 nId,
+ ViewShellId nViewShellId,
+ SfxUndoArray *pFather ) :
+ mpImpl(new Impl(nId, nViewShellId, rComment, rRepeatComment))
+{
+ pFatherUndoArray = pFather;
+ nMaxUndoActions = USHRT_MAX;
+}
+
+SfxListUndoAction::~SfxListUndoAction()
+{
+}
+
+void SfxListUndoAction::Undo()
+{
+ for(size_t i=nCurUndoAction;i>0;)
+ maUndoActions[--i].pAction->Undo();
+ nCurUndoAction=0;
+}
+
+
+void SfxListUndoAction::UndoWithContext( SfxUndoContext& i_context )
+{
+ for(size_t i=nCurUndoAction;i>0;)
+ maUndoActions[--i].pAction->UndoWithContext( i_context );
+ nCurUndoAction=0;
+}
+
+
+void SfxListUndoAction::Redo()
+{
+ for(size_t i=nCurUndoAction;i<maUndoActions.size();i++)
+ maUndoActions[i].pAction->Redo();
+ nCurUndoAction = maUndoActions.size();
+}
+
+
+void SfxListUndoAction::RedoWithContext( SfxUndoContext& i_context )
+{
+ for(size_t i=nCurUndoAction;i<maUndoActions.size();i++)
+ maUndoActions[i].pAction->RedoWithContext( i_context );
+ nCurUndoAction = maUndoActions.size();
+}
+
+
+void SfxListUndoAction::Repeat(SfxRepeatTarget&rTarget)
+{
+ for(size_t i=0;i<nCurUndoAction;i++)
+ maUndoActions[i].pAction->Repeat(rTarget);
+}
+
+
+bool SfxListUndoAction::CanRepeat(SfxRepeatTarget&r) const
+{
+ for(size_t i=0;i<nCurUndoAction;i++)
+ {
+ if(!maUndoActions[i].pAction->CanRepeat(r))
+ return false;
+ }
+ return true;
+}
+
+
+bool SfxListUndoAction::Merge( SfxUndoAction *pNextAction )
+{
+ return !maUndoActions.empty() && maUndoActions[maUndoActions.size()-1].pAction->Merge( pNextAction );
+}
+
+void SfxListUndoAction::dumpAsXml(xmlTextWriterPtr pWriter) const
+{
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SfxListUndoAction"));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("size"), BAD_CAST(OString::number(maUndoActions.size()).getStr()));
+ SfxUndoAction::dumpAsXml(pWriter);
+
+ for (size_t i = 0; i < maUndoActions.size(); ++i)
+ maUndoActions[i].pAction->dumpAsXml(pWriter);
+
+ (void)xmlTextWriterEndElement(pWriter);
+}
+
+SfxUndoArray::~SfxUndoArray()
+{
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/uno/pathservice.cxx b/svl/source/uno/pathservice.cxx
new file mode 100644
index 0000000000..b1d109894b
--- /dev/null
+++ b/svl/source/uno/pathservice.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/pathoptions.hxx>
+#include <sal/types.h>
+#include <rtl/ustring.hxx>
+#include <cppuhelper/implbase.hxx>
+#include <cppuhelper/supportsservice.hxx>
+#include <com/sun/star/frame/XConfigManager.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+
+namespace com::sun::star::uno {
+ class XComponentContext;
+}
+
+namespace {
+
+class PathService : public ::cppu::WeakImplHelper< css::frame::XConfigManager, css::lang::XServiceInfo >
+{
+ SvtPathOptions m_aOptions;
+
+public:
+ PathService()
+ {}
+
+ virtual OUString SAL_CALL getImplementationName() override
+ {
+ return "com.sun.star.comp.svl.PathService";
+ }
+
+ virtual sal_Bool SAL_CALL supportsService (
+ const OUString & rName) override
+ {
+ return cppu::supportsService(this, rName);
+ }
+
+ virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override
+ {
+ css::uno::Sequence< OUString > aRet { "com.sun.star.config.SpecialConfigManager" };
+ return aRet;
+ }
+
+ virtual OUString SAL_CALL substituteVariables (
+ const OUString& sText) override
+ {
+ return m_aOptions.SubstituteVariable( sText );
+ }
+
+ virtual void SAL_CALL addPropertyChangeListener (
+ const OUString &, const css::uno::Reference< css::beans::XPropertyChangeListener > &) override
+ {}
+
+ virtual void SAL_CALL removePropertyChangeListener (
+ const OUString &, const css::uno::Reference< css::beans::XPropertyChangeListener > &) override
+ {}
+
+ virtual void SAL_CALL flush() override
+ {}
+};
+
+}
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
+com_sun_star_comp_svl_PathService_get_implementation(css::uno::XComponentContext*,
+ css::uno::Sequence<css::uno::Any> const &)
+{
+ return cppu::acquire(new PathService());
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */