diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
commit | ed5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch) | |
tree | 7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /unotools/source/i18n | |
parent | Initial commit. (diff) | |
download | libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.tar.xz libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.zip |
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | unotools/source/i18n/calendarwrapper.cxx | 342 | ||||
-rw-r--r-- | unotools/source/i18n/caserotate.cxx | 43 | ||||
-rw-r--r-- | unotools/source/i18n/charclass.cxx | 373 | ||||
-rw-r--r-- | unotools/source/i18n/collatorwrapper.cxx | 97 | ||||
-rw-r--r-- | unotools/source/i18n/intlwrapper.cxx | 59 | ||||
-rw-r--r-- | unotools/source/i18n/localedatawrapper.cxx | 1562 | ||||
-rw-r--r-- | unotools/source/i18n/nativenumberwrapper.cxx | 110 | ||||
-rw-r--r-- | unotools/source/i18n/resmgr.cxx | 312 | ||||
-rw-r--r-- | unotools/source/i18n/textsearch.cxx | 398 | ||||
-rw-r--r-- | unotools/source/i18n/transliterationwrapper.cxx | 231 |
10 files changed, 3527 insertions, 0 deletions
diff --git a/unotools/source/i18n/calendarwrapper.cxx b/unotools/source/i18n/calendarwrapper.cxx new file mode 100644 index 000000000..fd5d166fd --- /dev/null +++ b/unotools/source/i18n/calendarwrapper.cxx @@ -0,0 +1,342 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/log.hxx> +#include <unotools/calendarwrapper.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <com/sun/star/i18n/LocaleCalendar2.hpp> +#include <tools/diagnose_ex.h> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::i18n; +using namespace ::com::sun::star::uno; + +CalendarWrapper::CalendarWrapper( + const Reference< uno::XComponentContext > & rxContext + ) + : + aEpochStart( Date( 1, 1, 1970 ) ) +{ + xC = LocaleCalendar2::create(rxContext); +} + +CalendarWrapper::~CalendarWrapper() +{ +} + +void CalendarWrapper::loadDefaultCalendar( const css::lang::Locale& rLocale, bool bTimeZoneUTC ) +{ + try + { + if ( xC.is() ) + xC->loadDefaultCalendarTZ( rLocale, (bTimeZoneUTC ? "UTC" : OUString())); + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "loadDefaultCalendar" ); + } +} + +void CalendarWrapper::loadCalendar( const OUString& rUniqueID, const css::lang::Locale& rLocale, bool bTimeZoneUTC ) +{ + try + { + if ( xC.is() ) + xC->loadCalendarTZ( rUniqueID, rLocale, (bTimeZoneUTC ? "UTC" : OUString())); + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "loadCalendar: " + << rUniqueID << " Locale: " << LanguageTag::convertToBcp47(rLocale) ); + } +} + +css::uno::Sequence< OUString > CalendarWrapper::getAllCalendars( const css::lang::Locale& rLocale ) const +{ + try + { + if ( xC.is() ) + return xC->getAllCalendars( rLocale ); + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "getAllCalendars" ); + } + + return {}; +} + +OUString CalendarWrapper::getUniqueID() const +{ + try + { + if ( xC.is() ) + return xC->getUniqueID(); + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "getUniqueID" ); + } + return OUString(); +} + +void CalendarWrapper::setDateTime( double fTimeInDays ) +{ + try + { + if ( xC.is() ) + xC->setDateTime( fTimeInDays ); + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "setDateTime" ); + } +} + +double CalendarWrapper::getDateTime() const +{ + try + { + if ( xC.is() ) + return xC->getDateTime(); + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "getDateTime" ); + } + return 0.0; +} + +void CalendarWrapper::setLocalDateTime( double fTimeInDays ) +{ + try + { + if ( xC.is() ) + { + xC->setLocalDateTime( fTimeInDays ); + } + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "setLocalDateTime" ); + } +} + +double CalendarWrapper::getLocalDateTime() const +{ + try + { + if ( xC.is() ) + { + return xC->getLocalDateTime(); + } + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "getLocalDateTime" ); + } + return 0.0; +} + +void CalendarWrapper::setValue( sal_Int16 nFieldIndex, sal_Int16 nValue ) +{ + try + { + if ( xC.is() ) + xC->setValue( nFieldIndex, nValue ); + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "setValue" ); + } +} + +bool CalendarWrapper::isValid() const +{ + try + { + if ( xC.is() ) + return xC->isValid(); + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "isValid" ); + } + return false; +} + +sal_Int16 CalendarWrapper::getValue( sal_Int16 nFieldIndex ) const +{ + try + { + if ( xC.is() ) + return xC->getValue( nFieldIndex ); + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "getValue" ); + } + return 0; +} + +sal_Int16 CalendarWrapper::getFirstDayOfWeek() const +{ + try + { + if ( xC.is() ) + return xC->getFirstDayOfWeek(); + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "getFirstDayOfWeek" ); + } + return 0; +} + +sal_Int16 CalendarWrapper::getNumberOfMonthsInYear() const +{ + try + { + if ( xC.is() ) + return xC->getNumberOfMonthsInYear(); + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "getNumberOfMonthsInYear" ); + } + return 0; +} + +sal_Int16 CalendarWrapper::getNumberOfDaysInWeek() const +{ + try + { + if ( xC.is() ) + return xC->getNumberOfDaysInWeek(); + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "getNumberOfDaysInWeek" ); + } + return 0; +} + +css::uno::Sequence< css::i18n::CalendarItem2 > CalendarWrapper::getMonths() const +{ + try + { + if ( xC.is() ) + return xC->getMonths2(); + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "getMonths" ); + } + return {}; +} + +css::uno::Sequence< css::i18n::CalendarItem2 > CalendarWrapper::getDays() const +{ + try + { + if ( xC.is() ) + return xC->getDays2(); + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "getDays" ); + } + return {}; +} + +OUString CalendarWrapper::getDisplayName( sal_Int16 nCalendarDisplayIndex, sal_Int16 nIdx, sal_Int16 nNameType ) const +{ + try + { + if ( xC.is() ) + return xC->getDisplayName( nCalendarDisplayIndex, nIdx, nNameType ); + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "getDisplayName" ); + } + return OUString(); +} + +// --- XExtendedCalendar ----------------------------------------------------- + +OUString CalendarWrapper::getDisplayString( sal_Int32 nCalendarDisplayCode, sal_Int16 nNativeNumberMode ) const +{ + try + { + if ( xC.is() ) + return xC->getDisplayString( nCalendarDisplayCode, nNativeNumberMode ); + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "getDisplayString" ); + } + return OUString(); +} + +// --- XCalendar3 ------------------------------------------------------------ + +css::i18n::Calendar2 CalendarWrapper::getLoadedCalendar() const +{ + try + { + if ( xC.is() ) + return xC->getLoadedCalendar2(); + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "getLoadedCalendar2" ); + } + return css::i18n::Calendar2(); +} + +css::uno::Sequence< css::i18n::CalendarItem2 > CalendarWrapper::getGenitiveMonths() const +{ + try + { + if ( xC.is() ) + return xC->getGenitiveMonths2(); + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "getGenitiveMonths" ); + } + return {}; +} + +css::uno::Sequence< css::i18n::CalendarItem2 > CalendarWrapper::getPartitiveMonths() const +{ + try + { + if ( xC.is() ) + return xC->getPartitiveMonths2(); + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "getPartitiveMonths" ); + } + return {}; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/i18n/caserotate.cxx b/unotools/source/i18n/caserotate.cxx new file mode 100644 index 000000000..a242b855e --- /dev/null +++ b/unotools/source/i18n/caserotate.cxx @@ -0,0 +1,43 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <unotools/caserotate.hxx> +#include <i18nutil/transliteration.hxx> + +//TODO Use XCharacterClassification::getStringType to determine the current +//(possibly mixed) case type and rotate to the next one + +TransliterationFlags RotateTransliteration::getNextMode() +{ + TransliterationFlags nMode = TransliterationFlags::NONE; + + switch (nF3ShiftCounter) + { + case 0: + nMode = TransliterationFlags::TITLE_CASE; + break; + case 1: //tdf#116315 + nMode = TransliterationFlags::SENTENCE_CASE; + break; + case 2: + nMode = TransliterationFlags::LOWERCASE_UPPERCASE; + break; + default: + case 3: + nMode = TransliterationFlags::UPPERCASE_LOWERCASE; + nF3ShiftCounter = -1; + break; + } + + nF3ShiftCounter++; + + return nMode; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/i18n/charclass.cxx b/unotools/source/i18n/charclass.cxx new file mode 100644 index 000000000..b577d2577 --- /dev/null +++ b/unotools/source/i18n/charclass.cxx @@ -0,0 +1,373 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <comphelper/processfactory.hxx> +#include <unotools/charclass.hxx> +#include <rtl/character.hxx> +#include <tools/diagnose_ex.h> + +#include <com/sun/star/i18n/CharacterClassification.hpp> +#include <utility> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::i18n; +using namespace ::com::sun::star::uno; + +CharClass::CharClass( + const Reference< uno::XComponentContext > & rxContext, + LanguageTag aLanguageTag + ) + : maLanguageTag(std::move( aLanguageTag)) +{ + xCC = CharacterClassification::create( rxContext ); +} + +CharClass::CharClass( LanguageTag aLanguageTag ) + : maLanguageTag(std::move( aLanguageTag)) +{ + xCC = CharacterClassification::create( comphelper::getProcessComponentContext() ); +} + +CharClass::~CharClass() +{ +} + +const LanguageTag& CharClass::getLanguageTag() const +{ + return maLanguageTag; +} + +const css::lang::Locale& CharClass::getMyLocale() const +{ + return maLanguageTag.getLocale(); +} + +// static +bool CharClass::isAsciiNumeric( std::u16string_view rStr ) +{ + if ( rStr.empty() ) + return false; + const sal_Unicode* p = rStr.data(); + const sal_Unicode* const pStop = p + rStr.size(); + + do + { + if ( !rtl::isAsciiDigit( *p ) ) + return false; + } + while ( ++p < pStop ); + + return true; +} + +// static +bool CharClass::isAsciiAlpha( std::u16string_view rStr ) +{ + if ( rStr.empty() ) + return false; + const sal_Unicode* p = rStr.data(); + const sal_Unicode* const pStop = p + rStr.size(); + + do + { + if ( !rtl::isAsciiAlpha( *p ) ) + return false; + } + while ( ++p < pStop ); + + return true; +} + +bool CharClass::isAlpha( const OUString& rStr, sal_Int32 nPos ) const +{ + sal_Unicode c = rStr[nPos]; + if ( c < 128 ) + return rtl::isAsciiAlpha( c ); + + try + { + return (xCC->getCharacterType( rStr, nPos, getMyLocale() ) & + nCharClassAlphaType) != 0; + } + catch ( const Exception& ) + { + TOOLS_WARN_EXCEPTION("unotools.i18n", "" ); + } + return false; +} + +bool CharClass::isLetter( const OUString& rStr, sal_Int32 nPos ) const +{ + sal_Unicode c = rStr[nPos]; + if ( c < 128 ) + return rtl::isAsciiAlpha( c ); + + try + { + return (xCC->getCharacterType( rStr, nPos, getMyLocale() ) & + nCharClassLetterType) != 0; + } + catch ( const Exception& ) + { + TOOLS_WARN_EXCEPTION("unotools.i18n", "" ); + } + return false; +} + +bool CharClass::isLetter( const OUString& rStr ) const +{ + try + { + return isLetterType( xCC->getStringType( rStr, 0, rStr.getLength(), getMyLocale() ) ); + } + catch ( const Exception& ) + { + TOOLS_WARN_EXCEPTION("unotools.i18n", "" ); + } + return false; +} + +bool CharClass::isDigit( const OUString& rStr, sal_Int32 nPos ) const +{ + sal_Unicode c = rStr[ nPos ]; + if ( c < 128 ) + return rtl::isAsciiDigit( c ); + + try + { + return (xCC->getCharacterType( rStr, nPos, getMyLocale() ) & + KCharacterType::DIGIT) != 0; + } + catch ( const Exception& ) + { + TOOLS_WARN_EXCEPTION("unotools.i18n", "" ); + } + return false; +} + +bool CharClass::isNumeric( const OUString& rStr ) const +{ + try + { + return isNumericType( xCC->getStringType( rStr, 0, rStr.getLength(), getMyLocale() ) ); + } + catch ( const Exception& ) + { + TOOLS_WARN_EXCEPTION("unotools.i18n", "" ); + } + return false; +} + +bool CharClass::isAlphaNumeric( const OUString& rStr, sal_Int32 nPos ) const +{ + sal_Unicode c = rStr[nPos]; + if ( c < 128 ) + return rtl::isAsciiAlphanumeric( c ); + + try + { + return (xCC->getCharacterType( rStr, nPos, getMyLocale() ) & + (nCharClassAlphaType | KCharacterType::DIGIT)) != 0; + } + catch ( const Exception& ) + { + TOOLS_WARN_EXCEPTION("unotools.i18n", "" ); + } + return false; +} + +bool CharClass::isLetterNumeric( const OUString& rStr, sal_Int32 nPos ) const +{ + sal_Unicode c = rStr[nPos]; + if ( c < 128 ) + return rtl::isAsciiAlphanumeric( c ); + + try + { + return (xCC->getCharacterType( rStr, nPos, getMyLocale() ) & + (nCharClassLetterType | KCharacterType::DIGIT)) != 0; + } + catch ( const Exception& ) + { + TOOLS_WARN_EXCEPTION("unotools.i18n", "" ); + } + return false; +} + +bool CharClass::isLetterNumeric( const OUString& rStr ) const +{ + try + { + return isLetterNumericType( xCC->getStringType( rStr, 0, rStr.getLength(), getMyLocale() ) ); + } + catch ( const Exception& ) + { + TOOLS_WARN_EXCEPTION("unotools.i18n", "" ); + } + return false; +} + +OUString CharClass::titlecase(const OUString& rStr, sal_Int32 nPos, sal_Int32 nCount) const +{ + try + { + return xCC->toTitle( rStr, nPos, nCount, getMyLocale() ); + } + catch ( const Exception& ) + { + TOOLS_WARN_EXCEPTION("unotools.i18n", "" ); + } + return rStr.copy( nPos, nCount ); +} + +OUString CharClass::uppercase( const OUString& rStr, sal_Int32 nPos, sal_Int32 nCount ) const +{ + try + { + return xCC->toUpper( rStr, nPos, nCount, getMyLocale() ); + } + catch ( const Exception& ) + { + TOOLS_WARN_EXCEPTION("unotools.i18n", "" ); + } + return rStr.copy( nPos, nCount ); +} + +OUString CharClass::lowercase( const OUString& rStr, sal_Int32 nPos, sal_Int32 nCount ) const +{ + try + { + return xCC->toLower( rStr, nPos, nCount, getMyLocale() ); + } + catch ( const Exception& ) + { + TOOLS_WARN_EXCEPTION("unotools.i18n", "" ); + } + return rStr.copy( nPos, nCount ); +} + +sal_Int16 CharClass::getType( const OUString& rStr, sal_Int32 nPos ) const +{ + try + { + return xCC->getType( rStr, nPos ); + } + catch ( const Exception& ) + { + TOOLS_WARN_EXCEPTION("unotools.i18n", "" ); + } + return 0; +} + +css::i18n::DirectionProperty CharClass::getCharacterDirection( const OUString& rStr, sal_Int32 nPos ) const +{ + try + { + return static_cast<css::i18n::DirectionProperty>(xCC->getCharacterDirection( rStr, nPos )); + } + catch ( const Exception& ) + { + TOOLS_WARN_EXCEPTION("unotools.i18n", "" ); + } + return css::i18n::DirectionProperty_LEFT_TO_RIGHT; +} + +css::i18n::UnicodeScript CharClass::getScript( const OUString& rStr, sal_Int32 nPos ) const +{ + try + { + return static_cast<css::i18n::UnicodeScript>(xCC->getScript( rStr, nPos )); + } + catch ( const Exception& ) + { + TOOLS_WARN_EXCEPTION("unotools.i18n", "" ); + } + return UnicodeScript_kBasicLatin; +} + +sal_Int32 CharClass::getCharacterType( const OUString& rStr, sal_Int32 nPos ) const +{ + try + { + return xCC->getCharacterType( rStr, nPos, getMyLocale() ); + } + catch ( const Exception& ) + { + TOOLS_WARN_EXCEPTION("unotools.i18n", "" ); + } + return 0; +} + +sal_Int32 CharClass::getStringType( const OUString& rStr, sal_Int32 nPos, sal_Int32 nCount ) const +{ + try + { + return xCC->getStringType( rStr, nPos, nCount, getMyLocale() ); + } + catch ( const Exception& ) + { + TOOLS_WARN_EXCEPTION("unotools.i18n", "" ); + } + return 0; +} + +css::i18n::ParseResult CharClass::parseAnyToken( + const OUString& rStr, + sal_Int32 nPos, + sal_Int32 nStartCharFlags, + const OUString& userDefinedCharactersStart, + sal_Int32 nContCharFlags, + const OUString& userDefinedCharactersCont ) const +{ + try + { + return xCC->parseAnyToken( rStr, nPos, getMyLocale(), + nStartCharFlags, userDefinedCharactersStart, + nContCharFlags, userDefinedCharactersCont ); + } + catch ( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "parseAnyToken" ); + } + return ParseResult(); +} + +css::i18n::ParseResult CharClass::parsePredefinedToken( + sal_Int32 nTokenType, + const OUString& rStr, + sal_Int32 nPos, + sal_Int32 nStartCharFlags, + const OUString& userDefinedCharactersStart, + sal_Int32 nContCharFlags, + const OUString& userDefinedCharactersCont ) const +{ + try + { + return xCC->parsePredefinedToken( nTokenType, rStr, nPos, getMyLocale(), + nStartCharFlags, userDefinedCharactersStart, + nContCharFlags, userDefinedCharactersCont ); + } + catch ( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "parsePredefinedToken" ); + } + return ParseResult(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/i18n/collatorwrapper.cxx b/unotools/source/i18n/collatorwrapper.cxx new file mode 100644 index 000000000..4da1398e0 --- /dev/null +++ b/unotools/source/i18n/collatorwrapper.cxx @@ -0,0 +1,97 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <sal/log.hxx> +#include <unotools/collatorwrapper.hxx> +#include <com/sun/star/i18n/Collator.hpp> + +using namespace ::com::sun::star; + +CollatorWrapper::CollatorWrapper ( const uno::Reference< uno::XComponentContext > &rxContext ) +{ + mxInternationalCollator = i18n::Collator::create( rxContext ); +} + +sal_Int32 +CollatorWrapper::compareString (const OUString& s1, const OUString& s2) const +{ + try + { + if (mxInternationalCollator.is()) + return mxInternationalCollator->compareString (s1, s2); + } + catch (const uno::RuntimeException&) + { + SAL_WARN( "unotools.i18n","CollatorWrapper: compareString failed"); + } + + return 0; +} + +uno::Sequence< OUString > +CollatorWrapper::listCollatorAlgorithms (const lang::Locale& rLocale) const +{ + try + { + if (mxInternationalCollator.is()) + return mxInternationalCollator->listCollatorAlgorithms (rLocale); + } + catch (const uno::RuntimeException&) + { + SAL_WARN( "unotools.i18n","CollatorWrapper: listCollatorAlgorithms failed"); + } + + return uno::Sequence< OUString > (); +} + +sal_Int32 +CollatorWrapper::loadDefaultCollator (const lang::Locale& rLocale, sal_Int32 nOptions) +{ + try + { + if (mxInternationalCollator.is()) + return mxInternationalCollator->loadDefaultCollator (rLocale, nOptions); + } + catch (const uno::RuntimeException&) + { + SAL_WARN( "unotools.i18n","CollatorWrapper: loadDefaultCollator failed"); + } + + return 0; +} + +void +CollatorWrapper::loadCollatorAlgorithm (const OUString& rAlgorithm, + const lang::Locale& rLocale, sal_Int32 nOptions) +{ + try + { + if (mxInternationalCollator.is()) + mxInternationalCollator->loadCollatorAlgorithm ( + rAlgorithm, rLocale, nOptions); + } + catch (const uno::RuntimeException&) + { + SAL_WARN( "unotools.i18n","CollatorWrapper: loadCollatorAlgorithm failed"); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/i18n/intlwrapper.cxx b/unotools/source/i18n/intlwrapper.cxx new file mode 100644 index 000000000..a4c21ea94 --- /dev/null +++ b/unotools/source/i18n/intlwrapper.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 <unotools/intlwrapper.hxx> +#include <unotools/collatorwrapper.hxx> +#include <unotools/localedatawrapper.hxx> +#include <com/sun/star/i18n/CollatorOptions.hpp> +#include <comphelper/processfactory.hxx> +#include <utility> + +IntlWrapper::IntlWrapper( LanguageTag aLanguageTag ) + : + maLanguageTag(std::move( aLanguageTag )), + m_xContext( comphelper::getProcessComponentContext() ) +{ +} + +IntlWrapper::~IntlWrapper() +{ +} + +void IntlWrapper::ImplNewLocaleData() const +{ + const_cast<IntlWrapper*>(this)->pLocaleData.reset( new LocaleDataWrapper( m_xContext, maLanguageTag ) ); +} + +void IntlWrapper::ImplNewCollator( bool bCaseSensitive ) const +{ + CollatorWrapper* p = new CollatorWrapper( m_xContext ); + if ( bCaseSensitive ) + { + p->loadDefaultCollator( maLanguageTag.getLocale(), 0 ); + const_cast<IntlWrapper*>(this)->pCaseCollator.reset(p); + } + else + { + p->loadDefaultCollator( maLanguageTag.getLocale(), + css::i18n::CollatorOptions::CollatorOptions_IGNORE_CASE ); + const_cast<IntlWrapper*>(this)->pCollator.reset(p); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/i18n/localedatawrapper.cxx b/unotools/source/i18n/localedatawrapper.cxx new file mode 100644 index 000000000..25a3fc2f5 --- /dev/null +++ b/unotools/source/i18n/localedatawrapper.cxx @@ -0,0 +1,1562 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <limits> +#include <stdio.h> +#include <string> + +#include <sal/log.hxx> +#include <unotools/localedatawrapper.hxx> +#include <unotools/digitgroupingiterator.hxx> +#include <tools/diagnose_ex.h> +#include <tools/debug.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <o3tl/safeint.hxx> + +#include <com/sun/star/i18n/KNumberFormatUsage.hpp> +#include <com/sun/star/i18n/KNumberFormatType.hpp> +#include <com/sun/star/i18n/LocaleData2.hpp> +#include <com/sun/star/i18n/NumberFormatIndex.hpp> +#include <com/sun/star/i18n/NumberFormatMapper.hpp> + +#include <comphelper/processfactory.hxx> +#include <comphelper/sequence.hxx> +#include <rtl/ustrbuf.hxx> +#include <rtl/math.hxx> +#include <tools/date.hxx> +#include <tools/time.hxx> +#include <o3tl/string_view.hxx> +#include <utility> + +const sal_uInt16 nCurrFormatDefault = 0; + +using namespace ::com::sun::star; +using namespace ::com::sun::star::i18n; +using namespace ::com::sun::star::uno; + +namespace +{ + uno::Sequence< lang::Locale > gInstalledLocales; + std::vector< LanguageType > gInstalledLanguageTypes; +} + +sal_uInt8 LocaleDataWrapper::nLocaleDataChecking = 0; + +LocaleDataWrapper::LocaleDataWrapper( + const Reference< uno::XComponentContext > & rxContext, + LanguageTag aLanguageTag + ) + : + m_xContext( rxContext ), + xLD( LocaleData2::create(rxContext) ), + maLanguageTag(std::move( aLanguageTag )) +{ + loadData(); + loadDateAcceptancePatterns({}); +} + +LocaleDataWrapper::LocaleDataWrapper( + LanguageTag aLanguageTag, + const std::vector<OUString> & rOverrideDateAcceptancePatterns + ) + : + m_xContext( comphelper::getProcessComponentContext() ), + xLD( LocaleData2::create(m_xContext) ), + maLanguageTag(std::move( aLanguageTag )) +{ + loadData(); + loadDateAcceptancePatterns(rOverrideDateAcceptancePatterns); +} + +LocaleDataWrapper::~LocaleDataWrapper() +{ +} + +const LanguageTag& LocaleDataWrapper::getLanguageTag() const +{ + return maLanguageTag; +} + +const css::lang::Locale& LocaleDataWrapper::getMyLocale() const +{ + return maLanguageTag.getLocale(); +} + +void LocaleDataWrapper::loadData() +{ + const css::lang::Locale& rMyLocale = maLanguageTag.getLocale(); + + { + const Sequence< Currency2 > aCurrSeq = getAllCurrencies(); + if ( !aCurrSeq.hasElements() ) + { + if (areChecksEnabled()) + outputCheckMessage("LocaleDataWrapper::getCurrSymbolsImpl: no currency at all, using ShellsAndPebbles"); + aCurrSymbol = "ShellsAndPebbles"; + aCurrBankSymbol = aCurrSymbol; + nCurrPositiveFormat = nCurrNegativeFormat = nCurrFormatDefault; + nCurrDigits = 2; + } + else + { + auto pCurr = std::find_if(aCurrSeq.begin(), aCurrSeq.end(), + [](const Currency2& rCurr) { return rCurr.Default; }); + if ( pCurr == aCurrSeq.end() ) + { + if (areChecksEnabled()) + { + outputCheckMessage( appendLocaleInfo( u"LocaleDataWrapper::getCurrSymbolsImpl: no default currency" ) ); + } + pCurr = aCurrSeq.begin(); + } + aCurrSymbol = pCurr->Symbol; + aCurrBankSymbol = pCurr->BankSymbol; + nCurrDigits = pCurr->DecimalPlaces; + } + } + + loadCurrencyFormats(); + + { + xDefaultCalendar.reset(); + xSecondaryCalendar.reset(); + const Sequence< Calendar2 > xCals = getAllCalendars(); + if (xCals.getLength() > 1) + { + auto pCal = std::find_if(xCals.begin(), xCals.end(), + [](const Calendar2& rCal) { return !rCal.Default; }); + if (pCal != xCals.end()) + xSecondaryCalendar = std::make_shared<Calendar2>( *pCal); + } + auto pCal = xCals.begin(); + if (xCals.getLength() > 1) + { + pCal = std::find_if(xCals.begin(), xCals.end(), + [](const Calendar2& rCal) { return rCal.Default; }); + if (pCal == xCals.end()) + pCal = xCals.begin(); + } + xDefaultCalendar = std::make_shared<Calendar2>( *pCal); + } + + loadDateOrders(); + + try + { + aDateAcceptancePatterns = xLD->getDateAcceptancePatterns( rMyLocale ); + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "getDateAcceptancePatterns" ); + aDateAcceptancePatterns = {}; + } + + + loadDigitGrouping(); + + try + { + aReservedWords = comphelper::sequenceToContainer<std::vector<OUString>>(xLD->getReservedWord( rMyLocale )); + } + catch ( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "getReservedWord" ); + } + + try + { + aLocaleDataItem = xLD->getLocaleItem2( rMyLocale ); + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "getLocaleItem" ); + static const css::i18n::LocaleDataItem2 aEmptyItem; + aLocaleDataItem = aEmptyItem; + } + + aLocaleItem[LocaleItem::DATE_SEPARATOR] = aLocaleDataItem.dateSeparator; + aLocaleItem[LocaleItem::THOUSAND_SEPARATOR] = aLocaleDataItem.thousandSeparator; + aLocaleItem[LocaleItem::DECIMAL_SEPARATOR] = aLocaleDataItem.decimalSeparator; + aLocaleItem[LocaleItem::TIME_SEPARATOR] = aLocaleDataItem.timeSeparator; + aLocaleItem[LocaleItem::TIME_100SEC_SEPARATOR] = aLocaleDataItem.time100SecSeparator; + aLocaleItem[LocaleItem::LIST_SEPARATOR] = aLocaleDataItem.listSeparator; + aLocaleItem[LocaleItem::SINGLE_QUOTATION_START] = aLocaleDataItem.quotationStart; + aLocaleItem[LocaleItem::SINGLE_QUOTATION_END] = aLocaleDataItem.quotationEnd; + aLocaleItem[LocaleItem::DOUBLE_QUOTATION_START] = aLocaleDataItem.doubleQuotationStart; + aLocaleItem[LocaleItem::DOUBLE_QUOTATION_END] = aLocaleDataItem.doubleQuotationEnd; + aLocaleItem[LocaleItem::MEASUREMENT_SYSTEM] = aLocaleDataItem.measurementSystem; + aLocaleItem[LocaleItem::TIME_AM] = aLocaleDataItem.timeAM; + aLocaleItem[LocaleItem::TIME_PM] = aLocaleDataItem.timePM; + aLocaleItem[LocaleItem::LONG_DATE_DAY_OF_WEEK_SEPARATOR] = aLocaleDataItem.LongDateDayOfWeekSeparator; + aLocaleItem[LocaleItem::LONG_DATE_DAY_SEPARATOR] = aLocaleDataItem.LongDateDaySeparator; + aLocaleItem[LocaleItem::LONG_DATE_MONTH_SEPARATOR] = aLocaleDataItem.LongDateMonthSeparator; + aLocaleItem[LocaleItem::LONG_DATE_YEAR_SEPARATOR] = aLocaleDataItem.LongDateYearSeparator; + aLocaleItem[LocaleItem::DECIMAL_SEPARATOR_ALTERNATIVE] = aLocaleDataItem.decimalSeparatorAlternative; +} + +/* FIXME-BCP47: locale data should provide a language tag instead that could be + * passed on. */ +css::i18n::LanguageCountryInfo LocaleDataWrapper::getLanguageCountryInfo() const +{ + try + { + return xLD->getLanguageCountryInfo( getMyLocale() ); + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "getLanguageCountryInfo" ); + } + return css::i18n::LanguageCountryInfo(); +} + +const css::i18n::LocaleDataItem2& LocaleDataWrapper::getLocaleItem() const +{ + return aLocaleDataItem; +} + +css::uno::Sequence< css::i18n::Currency2 > LocaleDataWrapper::getAllCurrencies() const +{ + try + { + return xLD->getAllCurrencies2( getMyLocale() ); + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "getAllCurrencies" ); + } + return {}; +} + +css::uno::Sequence< css::i18n::FormatElement > LocaleDataWrapper::getAllFormats() const +{ + try + { + return xLD->getAllFormats( getMyLocale() ); + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "getAllFormats" ); + } + return {}; +} + +css::i18n::ForbiddenCharacters LocaleDataWrapper::getForbiddenCharacters() const +{ + try + { + return xLD->getForbiddenCharacters( getMyLocale() ); + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "getForbiddenCharacters" ); + } + return css::i18n::ForbiddenCharacters(); +} + +const css::uno::Sequence< css::lang::Locale > & LocaleDataWrapper::getAllInstalledLocaleNames() const +{ + uno::Sequence< lang::Locale > &rInstalledLocales = gInstalledLocales; + + if ( rInstalledLocales.hasElements() ) + return rInstalledLocales; + + try + { + rInstalledLocales = xLD->getAllInstalledLocaleNames(); + } + catch ( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "getAllInstalledLocaleNames" ); + } + return rInstalledLocales; +} + +// --- Impl and helpers ---------------------------------------------------- + +// static +const css::uno::Sequence< css::lang::Locale >& LocaleDataWrapper::getInstalledLocaleNames() +{ + const uno::Sequence< lang::Locale > &rInstalledLocales = gInstalledLocales; + + if ( !rInstalledLocales.hasElements() ) + { + LocaleDataWrapper aLDW( ::comphelper::getProcessComponentContext(), LanguageTag( LANGUAGE_SYSTEM) ); + aLDW.getAllInstalledLocaleNames(); + } + return rInstalledLocales; +} + +// static +const std::vector< LanguageType >& LocaleDataWrapper::getInstalledLanguageTypes() +{ + std::vector< LanguageType > &rInstalledLanguageTypes = gInstalledLanguageTypes; + + if ( !rInstalledLanguageTypes.empty() ) + return rInstalledLanguageTypes; + + const css::uno::Sequence< css::lang::Locale > xLoc = getInstalledLocaleNames(); + sal_Int32 nCount = xLoc.getLength(); + std::vector< LanguageType > xLang; + xLang.reserve(nCount); + for ( const auto& rLoc : xLoc ) + { + LanguageTag aLanguageTag( rLoc ); + OUString aDebugLocale; + if (areChecksEnabled()) + { + aDebugLocale = aLanguageTag.getBcp47( false); + } + + LanguageType eLang = aLanguageTag.getLanguageType( false); + if (areChecksEnabled() && eLang == LANGUAGE_DONTKNOW) + { + OUString aMsg = "ConvertIsoNamesToLanguage: unknown MS-LCID for locale\n" + + aDebugLocale; + outputCheckMessage(aMsg); + } + + if ( eLang == LANGUAGE_NORWEGIAN) // no_NO, not Bokmal (nb_NO), not Nynorsk (nn_NO) + eLang = LANGUAGE_DONTKNOW; // don't offer "Unknown" language + if ( eLang != LANGUAGE_DONTKNOW ) + { + LanguageTag aBackLanguageTag( eLang); + if ( aLanguageTag != aBackLanguageTag ) + { + // In checks, exclude known problems because no MS-LCID defined + // and default for Language found. + if ( areChecksEnabled() + && aDebugLocale != "ar-SD" // Sudan/ar + && aDebugLocale != "en-CB" // Caribbean is not a country +// && aDebugLocale != "en-BG" // ?!? Bulgaria/en +// && aDebugLocale != "es-BR" // ?!? Brazil/es + ) + { + OUStringBuffer aMsg("ConvertIsoNamesToLanguage/ConvertLanguageToIsoNames: ambiguous locale (MS-LCID?)\n"); + aMsg.append(aDebugLocale); + aMsg.append(" -> 0x"); + aMsg.append(static_cast<sal_Int32>(static_cast<sal_uInt16>(eLang)), 16); + aMsg.append(" -> "); + aMsg.append(aBackLanguageTag.getBcp47()); + outputCheckMessage( aMsg.makeStringAndClear() ); + } + eLang = LANGUAGE_DONTKNOW; + } + } + if ( eLang != LANGUAGE_DONTKNOW ) + xLang.push_back(eLang); + } + rInstalledLanguageTypes = xLang; + + return rInstalledLanguageTypes; +} + +const OUString& LocaleDataWrapper::getOneLocaleItem( sal_Int16 nItem ) const +{ + if ( nItem >= LocaleItem::COUNT2 ) + { + SAL_WARN( "unotools.i18n", "getOneLocaleItem: bounds" ); + return aLocaleItem[0]; + } + return aLocaleItem[nItem]; +} + +const OUString& LocaleDataWrapper::getOneReservedWord( sal_Int16 nWord ) const +{ + if ( nWord < 0 || o3tl::make_unsigned(nWord) >= aReservedWords.size() ) + { + SAL_WARN( "unotools.i18n", "getOneReservedWord: bounds" ); + static const OUString EMPTY; + return EMPTY; + } + return aReservedWords[nWord]; +} + +MeasurementSystem LocaleDataWrapper::mapMeasurementStringToEnum( std::u16string_view rMS ) const +{ +//! TODO: could be cached too + if ( o3tl::equalsIgnoreAsciiCase( rMS, u"metric" ) ) + return MeasurementSystem::Metric; +//! TODO: other measurement systems? => extend enum MeasurementSystem + return MeasurementSystem::US; +} + +bool LocaleDataWrapper::doesSecondaryCalendarUseEC( std::u16string_view rName ) const +{ + if (rName.empty()) + return false; + + // Check language tag first to avoid loading all calendars of this locale. + LanguageTag aLoaded( getLoadedLanguageTag()); + const OUString& aBcp47( aLoaded.getBcp47()); + // So far determine only by locale, we know for a few. + /* TODO: check date format codes? or add to locale data? */ + if ( aBcp47 != "ja-JP" && + aBcp47 != "lo-LA" && + aBcp47 != "zh-TW") + return false; + + if (!xSecondaryCalendar) + return false; + if (!xSecondaryCalendar->Name.equalsIgnoreAsciiCase( rName)) + return false; + + return true; +} + +const std::shared_ptr< css::i18n::Calendar2 >& LocaleDataWrapper::getDefaultCalendar() const +{ + return xDefaultCalendar; +} + +css::uno::Sequence< css::i18n::CalendarItem2 > const & LocaleDataWrapper::getDefaultCalendarDays() const +{ + return getDefaultCalendar()->Days; +} + +css::uno::Sequence< css::i18n::CalendarItem2 > const & LocaleDataWrapper::getDefaultCalendarMonths() const +{ + return getDefaultCalendar()->Months; +} + +// --- currencies ----------------------------------------------------- + +const OUString& LocaleDataWrapper::getCurrSymbol() const +{ + return aCurrSymbol; +} + +const OUString& LocaleDataWrapper::getCurrBankSymbol() const +{ + return aCurrBankSymbol; +} + +sal_uInt16 LocaleDataWrapper::getCurrPositiveFormat() const +{ + return nCurrPositiveFormat; +} + +sal_uInt16 LocaleDataWrapper::getCurrNegativeFormat() const +{ + return nCurrNegativeFormat; +} + +sal_uInt16 LocaleDataWrapper::getCurrDigits() const +{ + return nCurrDigits; +} + +void LocaleDataWrapper::scanCurrFormatImpl( std::u16string_view rCode, + sal_Int32 nStart, sal_Int32& nSign, sal_Int32& nPar, + sal_Int32& nNum, sal_Int32& nBlank, sal_Int32& nSym ) const +{ + nSign = nPar = nNum = nBlank = nSym = -1; + const sal_Unicode* const pStr = rCode.data(); + const sal_Unicode* const pStop = pStr + rCode.size(); + const sal_Unicode* p = pStr + nStart; + int nInSection = 0; + bool bQuote = false; + while ( p < pStop ) + { + if ( bQuote ) + { + if ( *p == '"' && *(p-1) != '\\' ) + bQuote = false; + } + else + { + switch ( *p ) + { + case '"' : + if ( pStr == p || *(p-1) != '\\' ) + bQuote = true; + break; + case '-' : + if (!nInSection && nSign == -1) + nSign = p - pStr; + break; + case '(' : + if (!nInSection && nPar == -1) + nPar = p - pStr; + break; + case '0' : + case '#' : + if (!nInSection && nNum == -1) + nNum = p - pStr; + break; + case '[' : + nInSection++; + break; + case ']' : + if ( nInSection ) + { + nInSection--; + if (!nInSection && nBlank == -1 + && nSym != -1 && p < pStop-1 && *(p+1) == ' ' ) + nBlank = p - pStr + 1; + } + break; + case '$' : + if (nSym == -1 && nInSection && *(p-1) == '[') + { + nSym = p - pStr + 1; + if (nNum != -1 && *(p-2) == ' ') + nBlank = p - pStr - 2; + } + break; + case ';' : + if ( !nInSection ) + p = pStop; + break; + default: + if (!nInSection && nSym == -1 && o3tl::starts_with(rCode.substr(static_cast<sal_Int32>(p - pStr)), aCurrSymbol)) + { // currency symbol not surrounded by [$...] + nSym = p - pStr; + if (nBlank == -1 && pStr < p && *(p-1) == ' ') + nBlank = p - pStr - 1; + p += aCurrSymbol.getLength() - 1; + if (nBlank == -1 && p < pStop-2 && *(p+2) == ' ') + nBlank = p - pStr + 2; + } + } + } + p++; + } +} + +void LocaleDataWrapper::loadCurrencyFormats() +{ + css::uno::Reference< css::i18n::XNumberFormatCode > xNFC = i18n::NumberFormatMapper::create( m_xContext ); + uno::Sequence< NumberFormatCode > aFormatSeq = xNFC->getAllFormatCode( KNumberFormatUsage::CURRENCY, maLanguageTag.getLocale() ); + sal_Int32 nCnt = aFormatSeq.getLength(); + if ( !nCnt ) + { // bad luck + if (areChecksEnabled()) + { + outputCheckMessage( appendLocaleInfo( u"LocaleDataWrapper::getCurrFormatsImpl: no currency formats" ) ); + } + nCurrPositiveFormat = nCurrNegativeFormat = nCurrFormatDefault; + return; + } + // find a negative code (medium preferred) and a default (medium preferred) (not necessarily the same) + NumberFormatCode const * const pFormatArr = aFormatSeq.getArray(); + sal_Int32 nElem, nDef, nNeg, nMedium; + nDef = nNeg = nMedium = -1; + for ( nElem = 0; nElem < nCnt; nElem++ ) + { + if ( pFormatArr[nElem].Type == KNumberFormatType::MEDIUM ) + { + if ( pFormatArr[nElem].Default ) + { + nDef = nElem; + nMedium = nElem; + if ( pFormatArr[nElem].Code.indexOf( ';' ) >= 0 ) + nNeg = nElem; + } + else + { + if ( (nNeg == -1 || nMedium == -1) && pFormatArr[nElem].Code.indexOf( ';' ) >= 0 ) + nNeg = nElem; + if ( nMedium == -1 ) + nMedium = nElem; + } + } + else + { + if ( nDef == -1 && pFormatArr[nElem].Default ) + nDef = nElem; + if ( nNeg == -1 && pFormatArr[nElem].Code.indexOf( ';' ) >= 0 ) + nNeg = nElem; + } + } + + sal_Int32 nSign, nPar, nNum, nBlank, nSym; + + // positive format + nElem = (nDef >= 0 ? nDef : (nNeg >= 0 ? nNeg : 0)); + scanCurrFormatImpl( pFormatArr[nElem].Code, 0, nSign, nPar, nNum, nBlank, nSym ); + if (areChecksEnabled() && (nNum == -1 || nSym == -1)) + { + outputCheckMessage( appendLocaleInfo( u"LocaleDataWrapper::getCurrFormatsImpl: CurrPositiveFormat?" ) ); + } + if (nBlank == -1) + { + if ( nSym < nNum ) + nCurrPositiveFormat = 0; // $1 + else + nCurrPositiveFormat = 1; // 1$ + } + else + { + if ( nSym < nNum ) + nCurrPositiveFormat = 2; // $ 1 + else + nCurrPositiveFormat = 3; // 1 $ + } + + // negative format + if ( nNeg < 0 ) + nCurrNegativeFormat = nCurrFormatDefault; + else + { + const OUString& rCode = pFormatArr[nNeg].Code; + sal_Int32 nDelim = rCode.indexOf(';'); + scanCurrFormatImpl( rCode, nDelim+1, nSign, nPar, nNum, nBlank, nSym ); + if (areChecksEnabled() && (nNum == -1 || nSym == -1 || (nPar == -1 && nSign == -1))) + { + outputCheckMessage( appendLocaleInfo( u"LocaleDataWrapper::getCurrFormatsImpl: CurrNegativeFormat?" ) ); + } + // NOTE: one of nPar or nSign are allowed to be -1 + if (nBlank == -1) + { + if ( nSym < nNum ) + { + if ( -1 < nPar && nPar < nSym ) + nCurrNegativeFormat = 0; // ($1) + else if ( -1 < nSign && nSign < nSym ) + nCurrNegativeFormat = 1; // -$1 + else if ( nNum < nSign ) + nCurrNegativeFormat = 3; // $1- + else + nCurrNegativeFormat = 2; // $-1 + } + else + { + if ( -1 < nPar && nPar < nNum ) + nCurrNegativeFormat = 4; // (1$) + else if ( -1 < nSign && nSign < nNum ) + nCurrNegativeFormat = 5; // -1$ + else if ( nSym < nSign ) + nCurrNegativeFormat = 7; // 1$- + else + nCurrNegativeFormat = 6; // 1-$ + } + } + else + { + if ( nSym < nNum ) + { + if ( -1 < nPar && nPar < nSym ) + nCurrNegativeFormat = 14; // ($ 1) + else if ( -1 < nSign && nSign < nSym ) + nCurrNegativeFormat = 9; // -$ 1 + else if ( nNum < nSign ) + nCurrNegativeFormat = 12; // $ 1- + else + nCurrNegativeFormat = 11; // $ -1 + } + else + { + if ( -1 < nPar && nPar < nNum ) + nCurrNegativeFormat = 15; // (1 $) + else if ( -1 < nSign && nSign < nNum ) + nCurrNegativeFormat = 8; // -1 $ + else if ( nSym < nSign ) + nCurrNegativeFormat = 10; // 1 $- + else + nCurrNegativeFormat = 13; // 1- $ + } + } + } +} + +// --- date ----------------------------------------------------------- + +DateOrder LocaleDataWrapper::getDateOrder() const +{ + return nDateOrder; +} + +LongDateOrder LocaleDataWrapper::getLongDateOrder() const +{ + return nLongDateOrder; +} + +LongDateOrder LocaleDataWrapper::scanDateOrderImpl( std::u16string_view rCode ) const +{ + // Only some european versions were translated, the ones with different + // keyword combinations are: + // English DMY, German TMJ, Spanish DMA, French JMA, Italian GMA, + // Dutch DMJ, Finnish PKV + + // default is English keywords for every other language + size_t nDay = rCode.find('D'); + size_t nMonth = rCode.find('M'); + size_t nYear = rCode.find('Y'); + if (nDay == std::u16string_view::npos || nMonth == std::u16string_view::npos || nYear == std::u16string_view::npos) + { // This algorithm assumes that all three parts (DMY) are present + if (nMonth == std::u16string_view::npos) + { // only Finnish has something else than 'M' for month + nMonth = rCode.find('K'); + if (nMonth != std::u16string_view::npos) + { + nDay = rCode.find('P'); + nYear = rCode.find('V'); + } + } + else if (nDay == std::u16string_view::npos) + { // We have a month 'M' if we reach this branch. + // Possible languages containing 'M' but no 'D': + // German, French, Italian + nDay = rCode.find('T'); // German + if (nDay != std::u16string_view::npos) + nYear = rCode.find('J'); + else + { + nYear = rCode.find('A'); // French, Italian + if (nYear != std::u16string_view::npos) + { + nDay = rCode.find('J'); // French + if (nDay == std::u16string_view::npos) + nDay = rCode.find('G'); // Italian + } + } + } + else + { // We have a month 'M' and a day 'D'. + // Possible languages containing 'D' and 'M' but not 'Y': + // Spanish, Dutch + nYear = rCode.find('A'); // Spanish + if (nYear == std::u16string_view::npos) + nYear = rCode.find('J'); // Dutch + } + if (nDay == std::u16string_view::npos || nMonth == std::u16string_view::npos || nYear == std::u16string_view::npos) + { + if (areChecksEnabled()) + { + outputCheckMessage( appendLocaleInfo( u"LocaleDataWrapper::scanDateOrder: not all DMY present" ) ); + } + if (nDay == std::u16string_view::npos) + nDay = rCode.size(); + if (nMonth == std::u16string_view::npos) + nMonth = rCode.size(); + if (nYear == std::u16string_view::npos) + nYear = rCode.size(); + } + } + // compare with <= because each position may equal rCode.getLength() + if ( nDay <= nMonth && nMonth <= nYear ) + return LongDateOrder::DMY; // also if every position equals rCode.getLength() + else if ( nMonth <= nDay && nDay <= nYear ) + return LongDateOrder::MDY; + else if ( nYear <= nMonth && nMonth <= nDay ) + return LongDateOrder::YMD; + else if ( nYear <= nDay && nDay <= nMonth ) + return LongDateOrder::YDM; + else + { + if (areChecksEnabled()) + { + outputCheckMessage( appendLocaleInfo( u"LocaleDataWrapper::scanDateOrder: no magic applicable" ) ); + } + return LongDateOrder::DMY; + } +} + +static DateOrder getDateOrderFromLongDateOrder( LongDateOrder eLong ) +{ + switch (eLong) + { + case LongDateOrder::YMD: + return DateOrder::YMD; + break; + case LongDateOrder::DMY: + return DateOrder::DMY; + break; + case LongDateOrder::MDY: + return DateOrder::MDY; + break; + case LongDateOrder::YDM: + default: + assert(!"unhandled LongDateOrder to DateOrder"); + return DateOrder::DMY; + } +} + +void LocaleDataWrapper::loadDateOrders() +{ + css::uno::Reference< css::i18n::XNumberFormatCode > xNFC = i18n::NumberFormatMapper::create( m_xContext ); + uno::Sequence< NumberFormatCode > aFormatSeq = xNFC->getAllFormatCode( KNumberFormatUsage::DATE, maLanguageTag.getLocale() ); + sal_Int32 nCnt = aFormatSeq.getLength(); + if ( !nCnt ) + { // bad luck + if (areChecksEnabled()) + { + outputCheckMessage( appendLocaleInfo( u"LocaleDataWrapper::getDateOrdersImpl: no date formats" ) ); + } + nDateOrder = DateOrder::DMY; + nLongDateOrder = LongDateOrder::DMY; + return; + } + // find the edit (21), a default (medium preferred), + // a medium (default preferred), and a long (default preferred) + NumberFormatCode const * const pFormatArr = aFormatSeq.getArray(); + sal_Int32 nEdit, nDef, nMedium, nLong; + nEdit = nDef = nMedium = nLong = -1; + for ( sal_Int32 nElem = 0; nElem < nCnt; nElem++ ) + { + if ( nEdit == -1 && pFormatArr[nElem].Index == NumberFormatIndex::DATE_SYS_DDMMYYYY ) + nEdit = nElem; + if ( nDef == -1 && pFormatArr[nElem].Default ) + nDef = nElem; + switch ( pFormatArr[nElem].Type ) + { + case KNumberFormatType::MEDIUM : + { + if ( pFormatArr[nElem].Default ) + { + nDef = nElem; + nMedium = nElem; + } + else if ( nMedium == -1 ) + nMedium = nElem; + } + break; + case KNumberFormatType::LONG : + { + if ( pFormatArr[nElem].Default ) + nLong = nElem; + else if ( nLong == -1 ) + nLong = nElem; + } + break; + } + } + if ( nEdit == -1 ) + { + if (areChecksEnabled()) + { + outputCheckMessage( appendLocaleInfo( u"LocaleDataWrapper::getDateOrdersImpl: no edit" ) ); + } + if ( nDef == -1 ) + { + if (areChecksEnabled()) + { + outputCheckMessage( appendLocaleInfo( u"LocaleDataWrapper::getDateOrdersImpl: no default" ) ); + } + if ( nMedium != -1 ) + nDef = nMedium; + else if ( nLong != -1 ) + nDef = nLong; + else + nDef = 0; + } + nEdit = nDef; + } + LongDateOrder nDO = scanDateOrderImpl( pFormatArr[nEdit].Code ); + if ( pFormatArr[nEdit].Type == KNumberFormatType::LONG ) + { // normally this is not the case + nLongDateOrder = nDO; + nDateOrder = getDateOrderFromLongDateOrder(nDO); + } + else + { + // YDM should not occur in a short/medium date (i.e. no locale has + // that) and is nowhere handled. + nDateOrder = getDateOrderFromLongDateOrder(nDO); + if ( nLong == -1 ) + nLongDateOrder = nDO; + else + nLongDateOrder = scanDateOrderImpl( pFormatArr[nLong].Code ); + } +} + +// --- digit grouping ------------------------------------------------- + +void LocaleDataWrapper::loadDigitGrouping() +{ + /* TODO: This is a very simplified grouping setup that only serves its + * current purpose for Indian locales. A free-form flexible one would + * obtain grouping from locale data where it could be specified using, for + * example, codes like #,### and #,##,### that would generate the integer + * sequence. Needed additional API and a locale data element. + */ + + if (aGrouping.hasElements() && aGrouping[0]) + return; + + i18n::LanguageCountryInfo aLCInfo( getLanguageCountryInfo()); + if (aLCInfo.Country.equalsIgnoreAsciiCase("IN") || // India + aLCInfo.Country.equalsIgnoreAsciiCase("BT") ) // Bhutan + { + aGrouping = { 3, 2, 0 }; + } + else + { + aGrouping = { 3, 0, 0 }; + } +} + +const css::uno::Sequence< sal_Int32 >& LocaleDataWrapper::getDigitGrouping() const +{ + return aGrouping; +} + +// --- simple number formatting helpers ------------------------------- + +// The ImplAdd... methods are taken from class International and modified to +// suit the needs. + +static void ImplAddUNum( OUStringBuffer& rBuf, sal_uInt64 nNumber ) +{ + // fill temp buffer with digits + sal_Unicode aTempBuf[64]; + sal_Unicode* pTempBuf = aTempBuf; + do + { + *pTempBuf = static_cast<sal_Unicode>(nNumber % 10) + '0'; + pTempBuf++; + nNumber /= 10; + } + while ( nNumber ); + + // copy temp buffer to buffer passed + do + { + pTempBuf--; + rBuf.append(*pTempBuf); + } + while ( pTempBuf != aTempBuf ); +} + +static void ImplAddUNum( OUStringBuffer& rBuf, sal_uInt64 nNumber, int nMinLen ) +{ + // fill temp buffer with digits + sal_Unicode aTempBuf[64]; + sal_Unicode* pTempBuf = aTempBuf; + do + { + *pTempBuf = static_cast<sal_Unicode>(nNumber % 10) + '0'; + pTempBuf++; + nNumber /= 10; + nMinLen--; + } + while ( nNumber ); + + // fill with zeros up to the minimal length + while ( nMinLen > 0 ) + { + rBuf.append('0'); + nMinLen--; + } + + // copy temp buffer to real buffer + do + { + pTempBuf--; + rBuf.append(*pTempBuf); + } + while ( pTempBuf != aTempBuf ); +} + +static void ImplAddNum( OUStringBuffer& rBuf, sal_Int64 nNumber, int nMinLen ) +{ + if (nNumber < 0) + { + rBuf.append('-'); + nNumber = -nNumber; + } + return ImplAddUNum( rBuf, nNumber, nMinLen); +} + +static void ImplAdd2UNum( OUStringBuffer& rBuf, sal_uInt16 nNumber ) +{ + DBG_ASSERT( nNumber < 100, "ImplAdd2UNum() - Number >= 100" ); + + if ( nNumber < 10 ) + { + rBuf.append('0'); + rBuf.append(static_cast<char>(nNumber + '0')); + } + else + { + sal_uInt16 nTemp = nNumber % 10; + nNumber /= 10; + rBuf.append(static_cast<char>(nNumber + '0')); + rBuf.append(static_cast<char>(nTemp + '0')); + } +} + +static void ImplAdd9UNum( OUStringBuffer& rBuf, sal_uInt32 nNumber ) +{ + DBG_ASSERT( nNumber < 1000000000, "ImplAdd9UNum() - Number >= 1000000000" ); + + std::ostringstream ostr; + ostr.fill('0'); + ostr.width(9); + ostr << nNumber; + std::string aStr = ostr.str(); + rBuf.appendAscii(aStr.c_str(), aStr.size()); +} + +void LocaleDataWrapper::ImplAddFormatNum( OUStringBuffer& rBuf, + sal_Int64 nNumber, sal_uInt16 nDecimals, bool bUseThousandSep, + bool bTrailingZeros ) const +{ + OUStringBuffer aNumBuf(64); + sal_uInt16 nNumLen; + + // negative number + sal_uInt64 abs; + if ( nNumber < 0 ) + { + // Avoid overflow, map -2^63 -> 2^63 explicitly: + abs = nNumber == std::numeric_limits<sal_Int64>::min() + ? static_cast<sal_uInt64>(std::numeric_limits<sal_Int64>::min()) : nNumber * -1; + rBuf.append('-'); + } + else + { + abs = nNumber; + } + + // convert number + ImplAddUNum( aNumBuf, abs ); + nNumLen = static_cast<sal_uInt16>(aNumBuf.getLength()); + + if ( nNumLen <= nDecimals ) + { + // strip .0 in decimals? + if ( !nNumber && !bTrailingZeros ) + { + rBuf.append('0'); + } + else + { + // LeadingZero, insert 0 + if ( isNumLeadingZero() ) + { + rBuf.append('0'); + } + + // append decimal separator + rBuf.append( aLocaleDataItem.decimalSeparator ); + + // fill with zeros + sal_uInt16 i = 0; + while ( i < (nDecimals-nNumLen) ) + { + rBuf.append('0'); + i++; + } + + // append decimals + rBuf.append(aNumBuf); + } + } + else + { + const OUString& rThoSep = aLocaleDataItem.thousandSeparator; + + // copy number to buffer (excluding decimals) + sal_uInt16 nNumLen2 = nNumLen-nDecimals; + uno::Sequence< sal_Bool > aGroupPos; + if (bUseThousandSep) + aGroupPos = utl::DigitGroupingIterator::createForwardSequence( + nNumLen2, getDigitGrouping()); + sal_uInt16 i = 0; + for (; i < nNumLen2; ++i ) + { + rBuf.append(aNumBuf[i]); + + // add thousand separator? + if ( bUseThousandSep && aGroupPos[i] ) + rBuf.append( rThoSep ); + } + + // append decimals + if ( nDecimals ) + { + rBuf.append( aLocaleDataItem.decimalSeparator ); + + bool bNullEnd = true; + while ( i < nNumLen ) + { + if ( aNumBuf[i] != '0' ) + bNullEnd = false; + + rBuf.append(aNumBuf[i]); + i++; + } + + // strip .0 in decimals? + if ( bNullEnd && !bTrailingZeros ) + rBuf.setLength( rBuf.getLength() - (nDecimals + 1) ); + } + } +} + +// --- simple date and time formatting -------------------------------- + +OUString LocaleDataWrapper::getDate( const Date& rDate ) const +{ +//!TODO: leading zeros et al + OUStringBuffer aBuf(128); + sal_uInt16 nDay = rDate.GetDay(); + sal_uInt16 nMonth = rDate.GetMonth(); + sal_Int16 nYear = rDate.GetYear(); + sal_uInt16 nYearLen; + + if ( (true) /* IsDateCentury() */ ) + nYearLen = 4; + else + { + nYearLen = 2; + nYear %= 100; + } + + switch ( getDateOrder() ) + { + case DateOrder::DMY : + ImplAdd2UNum( aBuf, nDay ); + aBuf.append( aLocaleDataItem.dateSeparator ); + ImplAdd2UNum( aBuf, nMonth ); + aBuf.append( aLocaleDataItem.dateSeparator ); + ImplAddNum( aBuf, nYear, nYearLen ); + break; + case DateOrder::MDY : + ImplAdd2UNum( aBuf, nMonth ); + aBuf.append( aLocaleDataItem.dateSeparator ); + ImplAdd2UNum( aBuf, nDay ); + aBuf.append( aLocaleDataItem.dateSeparator ); + ImplAddNum( aBuf, nYear, nYearLen ); + break; + default: + ImplAddNum( aBuf, nYear, nYearLen ); + aBuf.append( aLocaleDataItem.dateSeparator ); + ImplAdd2UNum( aBuf, nMonth ); + aBuf.append( aLocaleDataItem.dateSeparator ); + ImplAdd2UNum( aBuf, nDay ); + } + + return aBuf.makeStringAndClear(); +} + +OUString LocaleDataWrapper::getTime( const tools::Time& rTime, bool bSec, bool b100Sec ) const +{ +//!TODO: leading zeros et al + OUStringBuffer aBuf(128); + sal_uInt16 nHour = rTime.GetHour(); + + nHour %= 24; + + ImplAdd2UNum( aBuf, nHour ); + aBuf.append( aLocaleDataItem.timeSeparator ); + ImplAdd2UNum( aBuf, rTime.GetMin() ); + if ( bSec ) + { + aBuf.append( aLocaleDataItem.timeSeparator ); + ImplAdd2UNum( aBuf, rTime.GetSec() ); + + if ( b100Sec ) + { + aBuf.append( aLocaleDataItem.time100SecSeparator ); + ImplAdd9UNum( aBuf, rTime.GetNanoSec() ); + } + } + + return aBuf.makeStringAndClear(); +} + +OUString LocaleDataWrapper::getDuration( const tools::Time& rTime, bool bSec, bool b100Sec ) const +{ + OUStringBuffer aBuf(128); + + if ( rTime < tools::Time( 0 ) ) + aBuf.append(' ' ); + + if ( (true) /* IsTimeLeadingZero() */ ) + ImplAddUNum( aBuf, rTime.GetHour(), 2 ); + else + ImplAddUNum( aBuf, rTime.GetHour() ); + aBuf.append( aLocaleDataItem.timeSeparator ); + ImplAdd2UNum( aBuf, rTime.GetMin() ); + if ( bSec ) + { + aBuf.append( aLocaleDataItem.timeSeparator ); + ImplAdd2UNum( aBuf, rTime.GetSec() ); + + if ( b100Sec ) + { + aBuf.append( aLocaleDataItem.time100SecSeparator ); + ImplAdd9UNum( aBuf, rTime.GetNanoSec() ); + } + } + + return aBuf.makeStringAndClear(); +} + +// --- simple number formatting --------------------------------------- + +static size_t ImplGetNumberStringLengthGuess( const css::i18n::LocaleDataItem2& rLocaleDataItem, sal_uInt16 nDecimals ) +{ + // approximately 3.2 bits per digit + const size_t nDig = ((sizeof(sal_Int64) * 8) / 3) + 1; + // digits, separators (pessimized for insane "every digit may be grouped"), leading zero, sign + size_t nGuess = ((nDecimals < nDig) ? + (((nDig - nDecimals) * rLocaleDataItem.thousandSeparator.getLength()) + nDig) : + nDecimals) + rLocaleDataItem.decimalSeparator.getLength() + 3; + return nGuess; +} + +OUString LocaleDataWrapper::getNum( sal_Int64 nNumber, sal_uInt16 nDecimals, + bool bUseThousandSep, bool bTrailingZeros ) const +{ + // check if digits and separators will fit into fixed buffer or allocate + size_t nGuess = ImplGetNumberStringLengthGuess( aLocaleDataItem, nDecimals ); + OUStringBuffer aBuf(int(nGuess + 16)); + + ImplAddFormatNum( aBuf, nNumber, nDecimals, + bUseThousandSep, bTrailingZeros ); + + return aBuf.makeStringAndClear(); +} + +OUString LocaleDataWrapper::getCurr( sal_Int64 nNumber, sal_uInt16 nDecimals, + std::u16string_view rCurrencySymbol, bool bUseThousandSep ) const +{ + sal_Unicode cZeroChar = getCurrZeroChar(); + + // check if digits and separators will fit into fixed buffer or allocate + size_t nGuess = ImplGetNumberStringLengthGuess( aLocaleDataItem, nDecimals ); + OUStringBuffer aNumBuf(int(nGuess + 16)); + OUStringBuffer aBuf(int(rCurrencySymbol.size() + nGuess + 20 )); + + bool bNeg; + if ( nNumber < 0 ) + { + bNeg = true; + nNumber *= -1; + } + else + bNeg = false; + + // convert number + ImplAddFormatNum( aNumBuf, nNumber, nDecimals, + bUseThousandSep, true ); + const sal_Int32 nNumLen = aNumBuf.getLength(); + + // replace zeros with zero character + if ( (cZeroChar != '0') && nDecimals /* && IsNumTrailingZeros() */ ) + { + sal_uInt16 i; + bool bZero = true; + + sal_uInt16 nNumBufIndex = nNumLen-nDecimals; + i = 0; + do + { + if ( aNumBuf[nNumBufIndex] != '0' ) + { + bZero = false; + break; + } + + nNumBufIndex++; + i++; + } + while ( i < nDecimals ); + + if ( bZero ) + { + nNumBufIndex = nNumLen-nDecimals; + i = 0; + do + { + aNumBuf[nNumBufIndex] = cZeroChar; + nNumBufIndex++; + i++; + } + while ( i < nDecimals ); + } + } + + if ( !bNeg ) + { + switch( getCurrPositiveFormat() ) + { + case 0: + aBuf.append( rCurrencySymbol ); + aBuf.append( aNumBuf ); + break; + case 1: + aBuf.append( aNumBuf ); + aBuf.append( rCurrencySymbol ); + break; + case 2: + aBuf.append( rCurrencySymbol ); + aBuf.append( ' ' ); + aBuf.append( aNumBuf ); + break; + case 3: + aBuf.append( aNumBuf ); + aBuf.append( ' ' ); + aBuf.append( rCurrencySymbol ); + break; + } + } + else + { + switch( getCurrNegativeFormat() ) + { + case 0: + aBuf.append( '(' ); + aBuf.append( rCurrencySymbol ); + aBuf.append( aNumBuf ); + aBuf.append( ')' ); + break; + case 1: + aBuf.append( '-' ); + aBuf.append( rCurrencySymbol ); + aBuf.append( aNumBuf ); + break; + case 2: + aBuf.append( rCurrencySymbol ); + aBuf.append( '-' ); + aBuf.append( aNumBuf ); + break; + case 3: + aBuf.append( rCurrencySymbol ); + aBuf.append( aNumBuf ); + aBuf.append( '-' ); + break; + case 4: + aBuf.append( '(' ); + aBuf.append( aNumBuf ); + aBuf.append( rCurrencySymbol ); + aBuf.append( ')' ); + break; + case 5: + aBuf.append( '-' ); + aBuf.append( aNumBuf ); + aBuf.append( rCurrencySymbol ); + break; + case 6: + aBuf.append( aNumBuf ); + aBuf.append( '-' ); + aBuf.append( rCurrencySymbol ); + break; + case 7: + aBuf.append( aNumBuf ); + aBuf.append( rCurrencySymbol ); + aBuf.append( '-' ); + break; + case 8: + aBuf.append( '-' ); + aBuf.append( aNumBuf ); + aBuf.append( ' ' ); + aBuf.append( rCurrencySymbol ); + break; + case 9: + aBuf.append( '-' ); + aBuf.append( rCurrencySymbol ); + aBuf.append( ' ' ); + aBuf.append( aNumBuf ); + break; + case 10: + aBuf.append( aNumBuf ); + aBuf.append( ' ' ); + aBuf.append( rCurrencySymbol ); + aBuf.append( '-' ); + break; + case 11: + aBuf.append( rCurrencySymbol ); + aBuf.append( ' ' ); + aBuf.append( '-' ); + aBuf.append( aNumBuf ); + break; + case 12: + aBuf.append( rCurrencySymbol ); + aBuf.append( ' ' ); + aBuf.append( aNumBuf ); + aBuf.append( '-' ); + break; + case 13: + aBuf.append( aNumBuf ); + aBuf.append( '-' ); + aBuf.append( ' ' ); + aBuf.append( rCurrencySymbol ); + break; + case 14: + aBuf.append( '(' ); + aBuf.append( rCurrencySymbol ); + aBuf.append( ' ' ); + aBuf.append( aNumBuf ); + aBuf.append( ')' ); + break; + case 15: + aBuf.append( '(' ); + aBuf.append( aNumBuf ); + aBuf.append( ' ' ); + aBuf.append( rCurrencySymbol ); + aBuf.append( ')' ); + break; + } + } + + return aBuf.makeStringAndClear(); +} + +// --- number parsing ------------------------------------------------- + +double LocaleDataWrapper::stringToDouble( std::u16string_view aString, bool bUseGroupSep, + rtl_math_ConversionStatus* pStatus, sal_Int32* pParseEnd ) const +{ + const sal_Unicode* pParseEndChar; + double fValue = stringToDouble(aString.data(), aString.data() + aString.size(), bUseGroupSep, pStatus, &pParseEndChar); + if (pParseEnd) + *pParseEnd = pParseEndChar - aString.data(); + return fValue; +} + +double LocaleDataWrapper::stringToDouble( const sal_Unicode* pBegin, const sal_Unicode* pEnd, bool bUseGroupSep, + rtl_math_ConversionStatus* pStatus, const sal_Unicode** ppParseEnd ) const +{ + const sal_Unicode cGroupSep = (bUseGroupSep ? aLocaleDataItem.thousandSeparator[0] : 0); + rtl_math_ConversionStatus eStatus = rtl_math_ConversionStatus_Ok; + const sal_Unicode* pParseEnd = nullptr; + double fValue = rtl_math_uStringToDouble( pBegin, pEnd, aLocaleDataItem.decimalSeparator[0], cGroupSep, &eStatus, &pParseEnd); + bool bTryAlt = (pParseEnd < pEnd && !aLocaleDataItem.decimalSeparatorAlternative.isEmpty() && + *pParseEnd == aLocaleDataItem.decimalSeparatorAlternative.toChar()); + // Try re-parsing with alternative if that was the reason to stop. + if (bTryAlt) + fValue = rtl_math_uStringToDouble( pBegin, pEnd, aLocaleDataItem.decimalSeparatorAlternative.toChar(), cGroupSep, &eStatus, &pParseEnd); + if (pStatus) + *pStatus = eStatus; + if (ppParseEnd) + *ppParseEnd = pParseEnd; + return fValue; +} + +// --- mixed ---------------------------------------------------------- + +LanguageTag LocaleDataWrapper::getLoadedLanguageTag() const +{ + LanguageCountryInfo aLCInfo = getLanguageCountryInfo(); + return LanguageTag( lang::Locale( aLCInfo.Language, aLCInfo.Country, aLCInfo.Variant )); +} + +OUString LocaleDataWrapper::appendLocaleInfo(std::u16string_view rDebugMsg) const +{ + LanguageTag aLoaded = getLoadedLanguageTag(); + return OUString::Concat(rDebugMsg) + "\n" + maLanguageTag.getBcp47() + " requested\n" + + aLoaded.getBcp47() + " loaded"; +} + +// static +void LocaleDataWrapper::outputCheckMessage( std::u16string_view rMsg ) +{ + outputCheckMessage(OUStringToOString(rMsg, RTL_TEXTENCODING_UTF8).getStr()); +} + +// static +void LocaleDataWrapper::outputCheckMessage( const char* pStr ) +{ + fprintf( stderr, "\n%s\n", pStr); + fflush( stderr); + SAL_WARN("unotools.i18n", pStr); +} + +// static +void LocaleDataWrapper::evaluateLocaleDataChecking() +{ + // Using the rtl_Instance template here wouldn't solve all threaded write + // accesses, since we want to assign the result to the static member + // variable and would need to dereference the pointer returned and assign + // the value unguarded. This is the same pattern manually coded. + sal_uInt8 nCheck = nLocaleDataChecking; + if (!nCheck) + { + ::osl::MutexGuard aGuard( ::osl::Mutex::getGlobalMutex()); + nCheck = nLocaleDataChecking; + if (!nCheck) + { +#ifdef DBG_UTIL + nCheck = 1; +#else + const char* pEnv = getenv( "OOO_ENABLE_LOCALE_DATA_CHECKS"); + if (pEnv && (pEnv[0] == 'Y' || pEnv[0] == 'y' || pEnv[0] == '1')) + nCheck = 1; + else + nCheck = 2; +#endif + OSL_DOUBLE_CHECKED_LOCKING_MEMORY_BARRIER(); + nLocaleDataChecking = nCheck; + } + } + else { + OSL_DOUBLE_CHECKED_LOCKING_MEMORY_BARRIER(); + } +} + +// --- XLocaleData3 ---------------------------------------------------------- + +css::uno::Sequence< css::i18n::Calendar2 > LocaleDataWrapper::getAllCalendars() const +{ + try + { + return xLD->getAllCalendars2( getMyLocale() ); + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "getAllCalendars" ); + } + return {}; +} + +// --- XLocaleData4 ---------------------------------------------------------- + +const css::uno::Sequence< OUString > & LocaleDataWrapper::getDateAcceptancePatterns() const +{ + return aDateAcceptancePatterns; +} + +// --- Override layer -------------------------------------------------------- + +void LocaleDataWrapper::loadDateAcceptancePatterns( + const std::vector<OUString> & rPatterns ) +{ + if (!aDateAcceptancePatterns.hasElements() || rPatterns.empty()) + { + try + { + aDateAcceptancePatterns = xLD->getDateAcceptancePatterns( maLanguageTag.getLocale() ); + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "setDateAcceptancePatterns" ); + } + if (rPatterns.empty()) + return; // just a reset + if (!aDateAcceptancePatterns.hasElements()) + { + aDateAcceptancePatterns = comphelper::containerToSequence(rPatterns); + return; + } + } + + // Earlier versions checked for presence of the full date pattern with + // aDateAcceptancePatterns[0] == rPatterns[0] and prepended that if not. + // This lead to confusion if the patterns were intentionally specified + // without, giving entirely a different DMY order, see tdf#150288. + // Not checking this and accepting the given patterns as is may result in + // the user shooting themself in the foot, but we can't have both. + aDateAcceptancePatterns = comphelper::containerToSequence(rPatterns); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/i18n/nativenumberwrapper.cxx b/unotools/source/i18n/nativenumberwrapper.cxx new file mode 100644 index 000000000..9c7b7d422 --- /dev/null +++ b/unotools/source/i18n/nativenumberwrapper.cxx @@ -0,0 +1,110 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <unotools/nativenumberwrapper.hxx> +#include <com/sun/star/i18n/NativeNumberSupplier2.hpp> +#include <tools/diagnose_ex.h> + +using namespace ::com::sun::star; + +NativeNumberWrapper::NativeNumberWrapper( + const uno::Reference< uno::XComponentContext > & rxContext + ) +{ + xNNS = i18n::NativeNumberSupplier2::create(rxContext); +} + +NativeNumberWrapper::~NativeNumberWrapper() +{ +} + +OUString +NativeNumberWrapper::getNativeNumberString( + const OUString& rNumberString, + const css::lang::Locale& rLocale, + sal_Int16 nNativeNumberMode) const +{ + try + { + if ( xNNS.is() ) + return xNNS->getNativeNumberString(rNumberString, rLocale, nNativeNumberMode); + } + catch ( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("unotools.i18n", "" ); + } + return OUString(); +} + +OUString +NativeNumberWrapper::getNativeNumberStringParams( + const OUString& rNumberString, + const css::lang::Locale& rLocale, + sal_Int16 nNativeNumberMode, + const OUString& rNativeNumberParams) const +{ + try + { + if ( xNNS.is() ) + return xNNS->getNativeNumberStringParams(rNumberString, rLocale, nNativeNumberMode, + rNativeNumberParams); + } + catch ( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("unotools.i18n", "" ); + } + return OUString(); +} + +i18n::NativeNumberXmlAttributes +NativeNumberWrapper::convertToXmlAttributes( + const css::lang::Locale& rLocale, + sal_Int16 nNativeNumberMode ) const +{ + try + { + if ( xNNS.is() ) + return xNNS->convertToXmlAttributes( rLocale, nNativeNumberMode ); + } + catch ( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("unotools.i18n", "" ); + } + return i18n::NativeNumberXmlAttributes(); +} + +sal_Int16 +NativeNumberWrapper::convertFromXmlAttributes( + const i18n::NativeNumberXmlAttributes& rAttr ) const +{ + try + { + if ( xNNS.is() ) + return xNNS->convertFromXmlAttributes( rAttr ); + } + catch ( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION("unotools.i18n", "" ); + } + return 0; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/i18n/resmgr.cxx b/unotools/source/i18n/resmgr.cxx new file mode 100644 index 000000000..641fa61be --- /dev/null +++ b/unotools/source/i18n/resmgr.cxx @@ -0,0 +1,312 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <boost/version.hpp> +#if BOOST_VERSION < 106700 +// Needed when #include <boost/locale.hpp> below includes Boost 1.65.1 +// workdir/UnpackedTarball/boost/boost/locale/format.hpp using "std::auto_ptr<data> d;", but must +// come very early here in case <memory> is already (indirectly) included earlier: +#include <config_libcxx.h> +#if HAVE_LIBCPP +#define _LIBCPP_ENABLE_CXX17_REMOVED_AUTO_PTR +#elif defined _MSC_VER +#define _HAS_AUTO_PTR_ETC 1 +#endif +#endif + +#include <sal/config.h> + +#include <cassert> + +#include <string.h> +#include <stdio.h> +#if defined UNX && !defined MACOSX && !defined IOS && !defined ANDROID +# include <libintl.h> +#endif + +#include <comphelper/lok.hxx> +#include <unotools/resmgr.hxx> +#include <osl/thread.h> +#include <osl/file.hxx> +#include <rtl/crc.h> +#include <rtl/bootstrap.hxx> +#include <i18nlangtag/languagetag.hxx> + +#include <boost/locale.hpp> +#include <boost/locale/gnu_gettext.hpp> + +#include <unordered_map> + +#ifdef ANDROID +#include <osl/detail/android-bootstrap.h> +#endif + +#if defined(_WIN32) && defined(DBG_UTIL) +#include <o3tl/char16_t2wchar_t.hxx> +#include <prewin.h> +#include <crtdbg.h> +#include <postwin.h> +#endif + +namespace +{ + OUString createFromUtf8(const char* data, size_t size) + { + OUString aTarget; + bool bSuccess = rtl_convertStringToUString(&aTarget.pData, + data, + size, + RTL_TEXTENCODING_UTF8, + RTL_TEXTTOUNICODE_FLAGS_UNDEFINED_ERROR|RTL_TEXTTOUNICODE_FLAGS_MBUNDEFINED_ERROR|RTL_TEXTTOUNICODE_FLAGS_INVALID_ERROR); + (void) bSuccess; + assert(bSuccess); + return aTarget; + } + + OString genKeyId(const OString& rGenerator) + { + sal_uInt32 nCRC = rtl_crc32(0, rGenerator.getStr(), rGenerator.getLength()); + // Use simple ASCII characters, exclude I, l, 1 and O, 0 to avoid confusing IDs + static const char sSymbols[] = + "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789"; + char sKeyId[6]; + for (short nKeyInd = 0; nKeyInd < 5; ++nKeyInd) + { + sKeyId[nKeyInd] = sSymbols[(nCRC & 63) % strlen(sSymbols)]; + nCRC >>= 6; + } + sKeyId[5] = '\0'; + return OString(sKeyId); + } +} + +#if defined(_WIN32) && defined(DBG_UTIL) +static int IgnoringCrtReportHook(int reportType, wchar_t *message, int * /* returnValue */) +{ + OUString sType; + if (reportType == _CRT_WARN) + sType = "WARN"; + else if (reportType == _CRT_ERROR) + sType = "ERROR"; + else if (reportType == _CRT_ASSERT) + sType = "ASSERT"; + else + sType = "?(" + OUString::number(reportType) + ")"; + + SAL_WARN("unotools.i18n", "CRT Report Hook: " << sType << ": " << OUString(o3tl::toU(message))); + + return TRUE; +} +#endif + + +namespace Translate +{ + std::locale Create(std::string_view aPrefixName, const LanguageTag& rLocale) + { + static std::unordered_map<OString, std::locale> aCache; + OString sIdentifier = rLocale.getGlibcLocaleString(u".UTF-8").toUtf8(); + OString sUnique = sIdentifier + aPrefixName; + auto aFind = aCache.find(sUnique); + if (aFind != aCache.end()) + return aFind->second; + boost::locale::generator gen; + gen.characters(boost::locale::char_facet); + gen.categories(boost::locale::message_facet | boost::locale::information_facet); +#if defined(ANDROID) + OString sPath(OString(lo_get_app_data_dir()) + "/program/resource"); +#else + OUString uri("$BRAND_BASE_DIR/$BRAND_SHARE_RESOURCE_SUBDIR/"); + rtl::Bootstrap::expandMacros(uri); + OUString path; + osl::File::getSystemPathFromFileURL(uri, path); +#if defined _WIN32 + // add_messages_path is documented to treat path string in the *created* locale's encoding + // on Windows; creating an UTF-8 encoding, we're lucky to have Unicode path support here. + constexpr rtl_TextEncoding eEncoding = RTL_TEXTENCODING_UTF8; +#else + const rtl_TextEncoding eEncoding = osl_getThreadTextEncoding(); +#endif + OString sPath(OUStringToOString(path, eEncoding)); +#endif + gen.add_messages_path(sPath.getStr()); +#if defined UNX && !defined MACOSX && !defined IOS && !defined ANDROID + // allow gettext to find these .mo files e.g. so gtk dialogs can use them + bindtextdomain(aPrefixName.data(), sPath.getStr()); + // tdf#131069 gtk, and anything sane, always wants utf-8 strings as output + bind_textdomain_codeset(aPrefixName.data(), "UTF-8"); +#endif + gen.add_messages_domain(aPrefixName.data()); + +#if defined(_WIN32) && defined(DBG_UTIL) + // With a newer C++ debug runtime (in an --enable-dbgutil build), passing an invalid locale + // name causes an attempt to display an error dialog. Which does not even show up, at least + // for me, but instead the process (gengal, at least) just hangs. Which is far from ideal. + + // Passing a POSIX-style locale name to the std::locale constructor on Windows is a bit odd, + // but apparently in the normal C++ runtime it "just" causes an exception to be thrown, that + // boost catches (see the loadable(std::string name) in boost's + // libs\locale\src\std\std_backend.cpp), and then instead uses the Windows style locale name + // it knows how to construct. (Why does it even try the POSIX style name I can't + // understand.) + + // Actually it isn't just the locale name part "en_US" of a locale like "en_US.UTF-8" that + // is problematic, but also the encoding part, "UTF-8". The Microsoft C/C++ library does not + // support UTF-8 locales. The error message that our own report hook catches says: + // "f:\dd\vctools\crt\crtw32\stdcpp\xmbtowc.c(89) : Assertion failed: ploc->_Mbcurmax == 1 + // || ploc->_Mbcurmax == 2". Clearly in a UTF-8 locale (perhaps one that boost internally + // constructs?) the maximum bytes per character will be more than 2. + + // With a debug C++ runtime, we need to avoid the error dialog, and just ignore the error. + + struct CrtSetReportHook + { + int mnCrtSetReportHookSucceeded; + + CrtSetReportHook() + { + mnCrtSetReportHookSucceeded = _CrtSetReportHookW2(_CRT_RPTHOOK_INSTALL, IgnoringCrtReportHook); + } + + ~CrtSetReportHook() + { + if (mnCrtSetReportHookSucceeded >= 0) + _CrtSetReportHookW2(_CRT_RPTHOOK_REMOVE, IgnoringCrtReportHook); + } + } aHook; + +#endif + + std::locale aRet(gen(sIdentifier.getStr())); + + aCache[sUnique] = aRet; + return aRet; + } + + OUString get(TranslateId sContextAndId, const std::locale &loc) + { + assert(!strchr(sContextAndId.mpId, '\004') && "should be using nget, not get"); + + //if it's a key id locale, generate it here + if (std::use_facet<boost::locale::info>(loc).language() == "qtz") + { + OString sKeyId(genKeyId(OString::Concat(sContextAndId.mpContext) + "|" + std::string_view(sContextAndId.mpId))); + return OUString::fromUtf8(sKeyId) + u"\u2016" + createFromUtf8(sContextAndId.mpId, strlen(sContextAndId.mpId)); + } + + //otherwise translate it + const std::string ret = boost::locale::pgettext(sContextAndId.mpContext, sContextAndId.mpId, loc); + OUString result(ExpandVariables(createFromUtf8(ret.data(), ret.size()))); + + if (comphelper::LibreOfficeKit::isActive()) + { + // If it is de-CH, change sharp s to double s. + if (std::use_facet<boost::locale::info>(loc).country() == "CH" && + std::use_facet<boost::locale::info>(loc).language() == "de") + result = result.replaceAll(OUString::fromUtf8("\xC3\x9F"), "ss"); + } + return result; + } + + OUString nget(TranslateNId aContextSingularPlural, int n, const std::locale &loc) + { + //if it's a key id locale, generate it here + if (std::use_facet<boost::locale::info>(loc).language() == "qtz") + { + OString sKeyId(genKeyId(OString::Concat(aContextSingularPlural.mpContext) + "|" + aContextSingularPlural.mpSingular)); + const char* pForm = n == 0 ? aContextSingularPlural.mpSingular : aContextSingularPlural.mpPlural; + return OUString::fromUtf8(sKeyId) + u"\u2016" + createFromUtf8(pForm, strlen(pForm)); + } + + //otherwise translate it + const std::string ret = boost::locale::npgettext(aContextSingularPlural.mpContext, aContextSingularPlural.mpSingular, aContextSingularPlural.mpPlural, n, loc); + OUString result(ExpandVariables(createFromUtf8(ret.data(), ret.size()))); + + if (comphelper::LibreOfficeKit::isActive()) + { + if (std::use_facet<boost::locale::info>(loc).country() == "CH" && + std::use_facet<boost::locale::info>(loc).language() == "de") + result = result.replaceAll(OUString::fromUtf8("\xC3\x9F"), "ss"); + } + return result; + } + + static ResHookProc pImplResHookProc = nullptr; + + OUString ExpandVariables(const OUString& rString) + { + if (pImplResHookProc) + return pImplResHookProc(rString); + return rString; + } + + void SetReadStringHook( ResHookProc pProc ) + { + pImplResHookProc = pProc; + } + + ResHookProc GetReadStringHook() + { + return pImplResHookProc; + } +} + +bool TranslateId::operator==(const TranslateId& other) const +{ + if (mpContext == nullptr || other.mpContext == nullptr) + { + if (mpContext != other.mpContext) + return false; + } + else if (strcmp(mpContext, other.mpContext) != 0) + return false; + + if (mpId == nullptr || other.mpId == nullptr) + { + return mpId == other.mpId; + } + return strcmp(mpId,other.mpId) == 0; +} + +bool TranslateNId::operator==(const TranslateNId& other) const +{ + if (mpContext == nullptr || other.mpContext == nullptr) + { + if (mpContext != other.mpContext) + return false; + } + else if (strcmp(mpContext, other.mpContext) != 0) + return false; + + if (mpSingular == nullptr || other.mpSingular == nullptr) + { + if (mpSingular != other.mpSingular) + return false; + } + else if (strcmp(mpSingular, other.mpSingular) != 0) + return false; + + if (mpPlural == nullptr || other.mpPlural == nullptr) + { + return mpPlural == other.mpPlural; + } + return strcmp(mpPlural,other.mpPlural) == 0; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/i18n/textsearch.cxx b/unotools/source/i18n/textsearch.cxx new file mode 100644 index 000000000..79d5cc68f --- /dev/null +++ b/unotools/source/i18n/textsearch.cxx @@ -0,0 +1,398 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <cstdlib> +#include <string_view> + +#include <i18nlangtag/languagetag.hxx> +#include <i18nutil/searchopt.hxx> +#include <i18nutil/transliteration.hxx> +#include <com/sun/star/util/TextSearch2.hpp> +#include <com/sun/star/util/SearchAlgorithms2.hpp> +#include <com/sun/star/util/SearchFlags.hpp> +#include <unotools/charclass.hxx> +#include <comphelper/processfactory.hxx> +#include <unotools/textsearch.hxx> +#include <rtl/ustrbuf.hxx> +#include <tools/diagnose_ex.h> +#include <mutex> + +using namespace ::com::sun::star::util; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::lang; + +namespace utl +{ + +SearchParam::SearchParam( const OUString &rText, + SearchType eType, + bool bCaseSensitive, + sal_uInt32 cWildEscChar, + bool bWildMatchSel ) +{ + sSrchStr = rText; + m_eSrchType = eType; + + m_cWildEscChar = cWildEscChar; + + m_bCaseSense = bCaseSensitive; + m_bWildMatchSel = bWildMatchSel; +} + +SearchParam::SearchParam( const SearchParam& rParam ) +{ + sSrchStr = rParam.sSrchStr; + m_eSrchType = rParam.m_eSrchType; + + m_cWildEscChar = rParam.m_cWildEscChar; + + m_bCaseSense = rParam.m_bCaseSense; + m_bWildMatchSel = rParam.m_bWildMatchSel; +} + +SearchParam::~SearchParam() {} + +static bool lcl_Equals( const i18nutil::SearchOptions2& rSO1, const i18nutil::SearchOptions2& rSO2 ) +{ + return + rSO1.AlgorithmType2 == rSO2.AlgorithmType2 && + rSO1.WildcardEscapeCharacter == rSO2.WildcardEscapeCharacter && + rSO1.algorithmType == rSO2.algorithmType && + rSO1.searchFlag == rSO2.searchFlag && + rSO1.searchString == rSO2.searchString && + rSO1.replaceString == rSO2.replaceString && + rSO1.changedChars == rSO2.changedChars && + rSO1.deletedChars == rSO2.deletedChars && + rSO1.insertedChars == rSO2.insertedChars && + rSO1.Locale.Language == rSO2.Locale.Language && + rSO1.Locale.Country == rSO2.Locale.Country && + rSO1.Locale.Variant == rSO2.Locale.Variant && + rSO1.transliterateFlags == rSO2.transliterateFlags; +} + +namespace +{ + struct CachedTextSearch + { + std::mutex mutex; + i18nutil::SearchOptions2 Options; + css::uno::Reference< css::util::XTextSearch2 > xTextSearch; + }; +} + +Reference<XTextSearch2> TextSearch::getXTextSearch( const i18nutil::SearchOptions2& rPara ) +{ + static CachedTextSearch theCachedTextSearch; + + std::scoped_lock aGuard(theCachedTextSearch.mutex); + + if ( lcl_Equals(theCachedTextSearch.Options, rPara) ) + return theCachedTextSearch.xTextSearch; + + Reference< XComponentContext > xContext = ::comphelper::getProcessComponentContext(); + theCachedTextSearch.xTextSearch.set( ::TextSearch2::create(xContext) ); + theCachedTextSearch.xTextSearch->setOptions2( rPara.toUnoSearchOptions2() ); + theCachedTextSearch.Options = rPara; + + return theCachedTextSearch.xTextSearch; +} + +TextSearch::TextSearch(const SearchParam & rParam, LanguageType eLang ) +{ + if( LANGUAGE_NONE == eLang ) + eLang = LANGUAGE_SYSTEM; + css::lang::Locale aLocale( LanguageTag::convertToLocale( eLang ) ); + + Init( rParam, aLocale); +} + +TextSearch::TextSearch(const SearchParam & rParam, const CharClass& rCClass ) +{ + Init( rParam, rCClass.getLanguageTag().getLocale() ); +} + +TextSearch::TextSearch( const i18nutil::SearchOptions2& rPara ) +{ + xTextSearch = getXTextSearch( rPara ); +} + +i18nutil::SearchOptions2 TextSearch::UpgradeToSearchOptions2( const i18nutil::SearchOptions& rOptions ) +{ + sal_Int16 nAlgorithmType2; + switch (rOptions.algorithmType) + { + case SearchAlgorithms_REGEXP: + nAlgorithmType2 = SearchAlgorithms2::REGEXP; + break; + case SearchAlgorithms_APPROXIMATE: + nAlgorithmType2 = SearchAlgorithms2::APPROXIMATE; + break; + case SearchAlgorithms_ABSOLUTE: + nAlgorithmType2 = SearchAlgorithms2::ABSOLUTE; + break; + default: + for (;;) std::abort(); + } + // It would be nice if an inherited struct had a ctor that takes an + // instance of the object the struct derived from... + i18nutil::SearchOptions2 aOptions2( + rOptions.algorithmType, + rOptions.searchFlag, + rOptions.searchString, + rOptions.replaceString, + rOptions.Locale, + rOptions.changedChars, + rOptions.deletedChars, + rOptions.insertedChars, + rOptions.transliterateFlags, + nAlgorithmType2, + 0 // no wildcard search, no escape character... + ); + return aOptions2; +} + +void TextSearch::Init( const SearchParam & rParam, + const css::lang::Locale& rLocale ) +{ + // convert SearchParam to the UNO SearchOptions2 + i18nutil::SearchOptions2 aSOpt; + + switch( rParam.GetSrchType() ) + { + case SearchParam::SearchType::Wildcard: + aSOpt.AlgorithmType2 = SearchAlgorithms2::WILDCARD; + aSOpt.algorithmType = SearchAlgorithms::SearchAlgorithms_MAKE_FIXED_SIZE; // no old enum for that + aSOpt.WildcardEscapeCharacter = rParam.GetWildEscChar(); + if (rParam.IsWildMatchSel()) + aSOpt.searchFlag |= SearchFlags::WILD_MATCH_SELECTION; + break; + + case SearchParam::SearchType::Regexp: + aSOpt.AlgorithmType2 = SearchAlgorithms2::REGEXP; + aSOpt.algorithmType = SearchAlgorithms_REGEXP; + break; + + case SearchParam::SearchType::Normal: + aSOpt.AlgorithmType2 = SearchAlgorithms2::ABSOLUTE; + aSOpt.algorithmType = SearchAlgorithms_ABSOLUTE; + break; + + default: + for (;;) std::abort(); + } + aSOpt.searchString = rParam.GetSrchStr(); + aSOpt.replaceString = ""; + aSOpt.Locale = rLocale; + aSOpt.transliterateFlags = TransliterationFlags::NONE; + if( !rParam.IsCaseSensitive() ) + { + aSOpt.searchFlag |= SearchFlags::ALL_IGNORE_CASE; + aSOpt.transliterateFlags |= TransliterationFlags::IGNORE_CASE; + } + + xTextSearch = getXTextSearch( aSOpt ); +} + +void TextSearch::SetLocale( const i18nutil::SearchOptions2& rOptions, + const css::lang::Locale& rLocale ) +{ + i18nutil::SearchOptions2 aSOpt( rOptions ); + aSOpt.Locale = rLocale; + + xTextSearch = getXTextSearch( aSOpt ); +} + +TextSearch::~TextSearch() +{ +} + +/* + * General search methods. These methods will call the respective + * methods, such as ordinary string searching or regular expression + * matching, using the method pointer. + */ +bool TextSearch::SearchForward( const OUString &rStr, + sal_Int32* pStart, sal_Int32* pEnd, + css::util::SearchResult* pRes) +{ + bool bRet = false; + try + { + if( xTextSearch.is() ) + { + SearchResult aRet( xTextSearch->searchForward( rStr, *pStart, *pEnd )); + if( aRet.subRegExpressions > 0 ) + { + bRet = true; + // the XTextsearch returns in startOffset the higher position + // and the endposition is always exclusive. + // The caller of this function will have in startPos the + // lower pos. and end + *pStart = aRet.startOffset[ 0 ]; + *pEnd = aRet.endOffset[ 0 ]; + if( pRes ) + *pRes = aRet; + } + } + } + catch ( Exception& ) + { + TOOLS_WARN_EXCEPTION("unotools.i18n", "" ); + } + return bRet; +} + +bool TextSearch::searchForward( const OUString &rStr ) +{ + sal_Int32 pStart = 0; + sal_Int32 pEnd = rStr.getLength(); + + bool bResult = SearchForward(rStr, &pStart, &pEnd); + + return bResult; +} + +bool TextSearch::SearchBackward( const OUString & rStr, sal_Int32* pStart, + sal_Int32* pEnd, SearchResult* pRes ) +{ + bool bRet = false; + try + { + if( xTextSearch.is() ) + { + SearchResult aRet( xTextSearch->searchBackward( rStr, *pStart, *pEnd )); + if( aRet.subRegExpressions ) + { + bRet = true; + // the XTextsearch returns in startOffset the higher position + // and the endposition is always exclusive. + // The caller of this function will have in startPos the + // lower pos. and end + *pEnd = aRet.startOffset[ 0 ]; + *pStart = aRet.endOffset[ 0 ]; + if( pRes ) + *pRes = aRet; + } + } + } + catch ( Exception& ) + { + TOOLS_WARN_EXCEPTION("unotools.i18n", "" ); + } + return bRet; +} + +void TextSearch::ReplaceBackReferences( OUString& rReplaceStr, std::u16string_view rStr, const SearchResult& rResult ) const +{ + if( rResult.subRegExpressions <= 0 ) + return; + + sal_Unicode sFndChar; + sal_Int32 i; + OUStringBuffer sBuff(rReplaceStr.getLength()*4); + for(i = 0; i < rReplaceStr.getLength(); i++) + { + if( rReplaceStr[i] == '&') + { + sal_Int32 nStart = rResult.startOffset[0]; + sal_Int32 nLength = rResult.endOffset[0] - rResult.startOffset[0]; + sBuff.append(rStr.substr(nStart, nLength)); + } + else if((i < rReplaceStr.getLength() - 1) && rReplaceStr[i] == '$') + { + sFndChar = rReplaceStr[ i + 1 ]; + switch(sFndChar) + { // placeholder for a backward reference? + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + int j = sFndChar - '0'; // index + if(j < rResult.subRegExpressions) + { + sal_Int32 nSttReg = rResult.startOffset[j]; + sal_Int32 nRegLen = rResult.endOffset[j]; + if (nSttReg < 0 || nRegLen < 0) // A "not found" optional capture + { + nSttReg = nRegLen = 0; // Copy empty string + } + else if (nRegLen >= nSttReg) + { + nRegLen = nRegLen - nSttReg; + } + else + { + nRegLen = nSttReg - nRegLen; + nSttReg = rResult.endOffset[j]; + } + // Copy reference from found string + sBuff.append(rStr.substr(nSttReg, nRegLen)); + } + i += 1; + } + break; + default: + sBuff.append(rReplaceStr[i]); + sBuff.append(rReplaceStr[i+1]); + i += 1; + break; + } + } + else if((i < rReplaceStr.getLength() - 1) && rReplaceStr[i] == '\\') + { + sFndChar = rReplaceStr[ i+1 ]; + switch(sFndChar) + { + case '\\': + case '&': + case '$': + sBuff.append(sFndChar); + i+=1; + break; + case 't': + sBuff.append('\t'); + i += 1; + break; + default: + sBuff.append(rReplaceStr[i]); + sBuff.append(rReplaceStr[i+1]); + i += 1; + break; + } + } + else + { + sBuff.append(rReplaceStr[i]); + } + } + rReplaceStr = sBuff.makeStringAndClear(); +} + +} // namespace utl + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/i18n/transliterationwrapper.cxx b/unotools/source/i18n/transliterationwrapper.cxx new file mode 100644 index 000000000..904ec82dc --- /dev/null +++ b/unotools/source/i18n/transliterationwrapper.cxx @@ -0,0 +1,231 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <unotools/transliterationwrapper.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <i18nutil/transliteration.hxx> +#include <tools/diagnose_ex.h> + +#include <com/sun/star/i18n/Transliteration.hpp> + +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::i18n; +using namespace ::com::sun::star::uno; +using namespace ::utl; + +TransliterationWrapper::TransliterationWrapper( + const Reference< XComponentContext > & rxContext, + TransliterationFlags nTyp ) + : xTrans( Transliteration::create(rxContext) ), + aLanguageTag( LANGUAGE_SYSTEM ), nType( nTyp ), bFirstCall( true ) +{ +} + +TransliterationWrapper::~TransliterationWrapper() +{ +} + +OUString TransliterationWrapper::transliterate(const OUString& rStr, LanguageType nLang, + sal_Int32 nStart, sal_Int32 nLen, + Sequence <sal_Int32>* pOffset ) +{ + OUString sRet; + if( xTrans.is() ) + { + try + { + loadModuleIfNeeded( nLang ); + + if ( pOffset ) + sRet = xTrans->transliterate( rStr, nStart, nLen, *pOffset ); + else + sRet = xTrans->transliterateString2String( rStr, nStart, nLen); + } + catch( Exception& ) + { + TOOLS_WARN_EXCEPTION("unotools.i18n", "" ); + } + } + return sRet; +} + +OUString TransliterationWrapper::transliterate( const OUString& rStr, + sal_Int32 nStart, sal_Int32 nLen ) const +{ + OUString sRet( rStr ); + if( xTrans.is() ) + { + try + { + sRet = xTrans->transliterateString2String( rStr, nStart, nLen); + } + catch( Exception& ) + { + TOOLS_WARN_EXCEPTION("unotools.i18n", "" ); + } + } + return sRet; +} + +bool TransliterationWrapper::needLanguageForTheMode() const +{ + return TransliterationFlags::UPPERCASE_LOWERCASE == nType || + TransliterationFlags::LOWERCASE_UPPERCASE == nType || + TransliterationFlags::IGNORE_CASE == nType || + TransliterationFlags::SENTENCE_CASE == nType || + TransliterationFlags::TITLE_CASE == nType || + TransliterationFlags::TOGGLE_CASE == nType; +} + +void TransliterationWrapper::setLanguageLocaleImpl( LanguageType nLang ) +{ + if( LANGUAGE_NONE == nLang ) + nLang = LANGUAGE_SYSTEM; + aLanguageTag.reset( nLang); +} + +void TransliterationWrapper::loadModuleIfNeeded( LanguageType nLang ) +{ + bool bLoad = bFirstCall; + bFirstCall = false; + + if( nType == TransliterationFlags::SENTENCE_CASE ) + { + if( bLoad ) + loadModuleByImplName("SENTENCE_CASE", nLang); + } + else if( nType == TransliterationFlags::TITLE_CASE ) + { + if( bLoad ) + loadModuleByImplName("TITLE_CASE", nLang); + } + else if( nType == TransliterationFlags::TOGGLE_CASE ) + { + if( bLoad ) + loadModuleByImplName("TOGGLE_CASE", nLang); + } + else + { + if( aLanguageTag.getLanguageType() != nLang ) + { + setLanguageLocaleImpl( nLang ); + if( !bLoad ) + bLoad = needLanguageForTheMode(); + } + if( bLoad ) + loadModuleImpl(); + } +} + +void TransliterationWrapper::loadModuleImpl() const +{ + if ( bFirstCall ) + const_cast<TransliterationWrapper*>(this)->setLanguageLocaleImpl( LANGUAGE_SYSTEM ); + + try + { + if ( xTrans.is() ) + xTrans->loadModule( static_cast<TransliterationModules>(nType), aLanguageTag.getLocale() ); + } + catch ( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "loadModuleImpl" ); + } + + bFirstCall = false; +} + +void TransliterationWrapper::loadModuleByImplName(const OUString& rModuleName, + LanguageType nLang ) +{ + try + { + setLanguageLocaleImpl( nLang ); + css::lang::Locale aLocale( aLanguageTag.getLocale()); + // Reset LanguageTag, so the next call to loadModuleIfNeeded() forces + // new settings. + aLanguageTag.reset( LANGUAGE_DONTKNOW); + if ( xTrans.is() ) + xTrans->loadModuleByImplName( rModuleName, aLocale ); + } + catch ( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "loadModuleByImplName" ); + } + + bFirstCall = false; +} + +bool TransliterationWrapper::equals( + const OUString& rStr1, sal_Int32 nPos1, sal_Int32 nCount1, sal_Int32& nMatch1, + const OUString& rStr2, sal_Int32 nPos2, sal_Int32 nCount2, sal_Int32& nMatch2 ) const +{ + try + { + if( bFirstCall ) + loadModuleImpl(); + if ( xTrans.is() ) + return xTrans->equals( rStr1, nPos1, nCount1, nMatch1, rStr2, nPos2, nCount2, nMatch2 ); + } + catch ( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "equals" ); + } + return false; +} + +sal_Int32 TransliterationWrapper::compareString( const OUString& rStr1, const OUString& rStr2 ) const +{ + try + { + if( bFirstCall ) + loadModuleImpl(); + if ( xTrans.is() ) + return xTrans->compareString( rStr1, rStr2 ); + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "compareString" ); + } + return 0; +} + +// --- helpers -------------------------------------------------------- + +bool TransliterationWrapper::isEqual( const OUString& rStr1, const OUString& rStr2 ) const +{ + sal_Int32 nMatch1(0), nMatch2(0); + bool bMatch = equals( + rStr1, 0, rStr1.getLength(), nMatch1, + rStr2, 0, rStr2.getLength(), nMatch2 ); + return bMatch; +} + +bool TransliterationWrapper::isMatch( const OUString& rStr1, const OUString& rStr2 ) const +{ + sal_Int32 nMatch1(0), nMatch2(0); + equals( + rStr1, 0, rStr1.getLength(), nMatch1, + rStr2, 0, rStr2.getLength(), nMatch2 ); + return (nMatch1 <= nMatch2) && (nMatch1 == rStr1.getLength()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |