diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
commit | 267c6f2ac71f92999e969232431ba04678e7437e (patch) | |
tree | 358c9467650e1d0a1d7227a21dac2e3d08b622b2 /svl/source | |
parent | Initial commit. (diff) | |
download | libreoffice-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')
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, ×tamp) != 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: */ |