diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 16:51:28 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 16:51:28 +0000 |
commit | 940b4d1848e8c70ab7642901a68594e8016caffc (patch) | |
tree | eb72f344ee6c3d9b80a7ecc079ea79e9fba8676d /unotools/source/i18n | |
parent | Initial commit. (diff) | |
download | libreoffice-upstream.tar.xz libreoffice-upstream.zip |
Adding upstream version 1:7.0.4.upstream/1%7.0.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'unotools/source/i18n')
-rw-r--r-- | unotools/source/i18n/calendarwrapper.cxx | 341 | ||||
-rw-r--r-- | unotools/source/i18n/caserotate.cxx | 43 | ||||
-rw-r--r-- | unotools/source/i18n/charclass.cxx | 456 | ||||
-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 | 1826 | ||||
-rw-r--r-- | unotools/source/i18n/nativenumberwrapper.cxx | 110 | ||||
-rw-r--r-- | unotools/source/i18n/readwritemutexguard.cxx | 109 | ||||
-rw-r--r-- | unotools/source/i18n/resmgr.cxx | 280 | ||||
-rw-r--r-- | unotools/source/i18n/textsearch.cxx | 401 | ||||
-rw-r--r-- | unotools/source/i18n/transliterationwrapper.cxx | 232 |
11 files changed, 3954 insertions, 0 deletions
diff --git a/unotools/source/i18n/calendarwrapper.cxx b/unotools/source/i18n/calendarwrapper.cxx new file mode 100644 index 000000000..5f4a1669b --- /dev/null +++ b/unotools/source/i18n/calendarwrapper.cxx @@ -0,0 +1,341 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 <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: " << rLocale.Language << "_" << rLocale.Country ); + } +} + +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 css::uno::Sequence< OUString > (0); +} + +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 > (0); +} + +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 css::uno::Sequence< css::i18n::CalendarItem2 > (0); +} + +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 > (0); +} + +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 css::uno::Sequence< css::i18n::CalendarItem2 > (0); +} + +/* 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..faec870ff --- /dev/null +++ b/unotools/source/i18n/charclass.cxx @@ -0,0 +1,456 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 <sal/log.hxx> +#include <unotools/charclass.hxx> +#include <rtl/character.hxx> +#include <tools/diagnose_ex.h> + +#include <com/sun/star/i18n/CharacterClassification.hpp> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::i18n; +using namespace ::com::sun::star::uno; + +CharClass::CharClass( + const Reference< uno::XComponentContext > & rxContext, + const LanguageTag& rLanguageTag + ) + : + maLanguageTag( rLanguageTag) +{ + xCC = CharacterClassification::create( rxContext ); +} + +CharClass::CharClass( + const LanguageTag& rLanguageTag ) + : + maLanguageTag( rLanguageTag) +{ + xCC = CharacterClassification::create( comphelper::getProcessComponentContext() ); +} + +CharClass::~CharClass() +{ +} + +void CharClass::setLanguageTag( const LanguageTag& rLanguageTag ) +{ + ::osl::MutexGuard aGuard( aMutex ); + maLanguageTag = rLanguageTag; +} + +const LanguageTag& CharClass::getLanguageTag() const +{ + ::osl::MutexGuard aGuard( aMutex ); + return maLanguageTag; +} + +const css::lang::Locale& CharClass::getMyLocale() const +{ + // Mutex locked by callers. + return maLanguageTag.getLocale(); +} + +// static +bool CharClass::isAsciiNumeric( const OUString& rStr ) +{ + if ( rStr.isEmpty() ) + return false; + const sal_Unicode* p = rStr.getStr(); + const sal_Unicode* const pStop = p + rStr.getLength(); + + do + { + if ( !rtl::isAsciiDigit( *p ) ) + return false; + } + while ( ++p < pStop ); + + return true; +} + +// static +bool CharClass::isAsciiAlpha( const OUString& rStr ) +{ + if ( rStr.isEmpty() ) + return false; + const sal_Unicode* p = rStr.getStr(); + const sal_Unicode* const pStop = p + rStr.getLength(); + + 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 + { + if ( xCC.is() ) + { + ::osl::MutexGuard aGuard( aMutex ); + return (xCC->getCharacterType( rStr, nPos, getMyLocale() ) & + nCharClassAlphaType) != 0; + } + } + catch ( const Exception& ) + { + SAL_WARN( "unotools.i18n", "isAlpha: Exception caught!" ); + } + 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 + { + if ( xCC.is() ) + { + ::osl::MutexGuard aGuard( aMutex ); + return (xCC->getCharacterType( rStr, nPos, getMyLocale() ) & + nCharClassLetterType) != 0; + } + } + catch ( const Exception& ) + { + SAL_WARN( "unotools.i18n", "isLetter: Exception caught!" ); + } + return false; +} + +bool CharClass::isLetter( const OUString& rStr ) const +{ + try + { + if ( xCC.is() ) + { + ::osl::MutexGuard aGuard( aMutex ); + return isLetterType( xCC->getStringType( rStr, 0, rStr.getLength(), getMyLocale() ) ); + } + } + catch ( const Exception& ) + { + SAL_WARN( "unotools.i18n", "isLetter: Exception caught!" ); + } + 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 + { + if ( xCC.is() ) + { + ::osl::MutexGuard aGuard( aMutex ); + return (xCC->getCharacterType( rStr, nPos, getMyLocale() ) & + KCharacterType::DIGIT) != 0; + } + } + catch ( const Exception& ) + { + SAL_WARN( "unotools.i18n", "isDigit: Exception caught!" ); + } + return false; +} + +bool CharClass::isNumeric( const OUString& rStr ) const +{ + try + { + if ( xCC.is() ) + { + ::osl::MutexGuard aGuard( aMutex ); + return isNumericType( xCC->getStringType( rStr, 0, rStr.getLength(), getMyLocale() ) ); + } + } + catch ( const Exception& ) + { + SAL_WARN( "unotools.i18n", "isNumeric: Exception caught!" ); + } + 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 + { + if ( xCC.is() ) + { + ::osl::MutexGuard aGuard( aMutex ); + return (xCC->getCharacterType( rStr, nPos, getMyLocale() ) & + (nCharClassAlphaType | KCharacterType::DIGIT)) != 0; + } + } + catch ( const Exception& ) + { + SAL_WARN( "unotools.i18n", "isAlphaNumeric: Exception caught!" ); + } + 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 + { + if ( xCC.is() ) + { + ::osl::MutexGuard aGuard( aMutex ); + return (xCC->getCharacterType( rStr, nPos, getMyLocale() ) & + (nCharClassLetterType | KCharacterType::DIGIT)) != 0; + } + } + catch ( const Exception& ) + { + SAL_WARN( "unotools.i18n", "isLetterNumeric: Exception caught!" ); + } + return false; +} + +bool CharClass::isLetterNumeric( const OUString& rStr ) const +{ + try + { + if ( xCC.is() ) + { + ::osl::MutexGuard aGuard( aMutex ); + return isLetterNumericType( xCC->getStringType( rStr, 0, rStr.getLength(), getMyLocale() ) ); + } + } + catch ( const Exception& ) + { + SAL_WARN( "unotools.i18n", "isLetterNumeric: Exception caught!" ); + } + return false; +} + +OUString CharClass::titlecase(const OUString& rStr, sal_Int32 nPos, sal_Int32 nCount) const +{ + try + { + if ( xCC.is() ) + { + ::osl::MutexGuard aGuard( aMutex ); + return xCC->toTitle( rStr, nPos, nCount, getMyLocale() ); + } + } + catch ( const Exception& ) + { + SAL_WARN( "unotools.i18n", "titlecase: Exception caught!" ); + } + return rStr.copy( nPos, nCount ); +} + +OUString CharClass::uppercase( const OUString& rStr, sal_Int32 nPos, sal_Int32 nCount ) const +{ + try + { + if ( xCC.is() ) + { + ::osl::MutexGuard aGuard( aMutex ); + return xCC->toUpper( rStr, nPos, nCount, getMyLocale() ); + } + } + catch ( const Exception& ) + { + SAL_WARN( "unotools.i18n", "uppercase: Exception caught!" ); + } + return rStr.copy( nPos, nCount ); +} + +OUString CharClass::lowercase( const OUString& rStr, sal_Int32 nPos, sal_Int32 nCount ) const +{ + try + { + if ( xCC.is() ) + { + ::osl::MutexGuard aGuard( aMutex ); + return xCC->toLower( rStr, nPos, nCount, getMyLocale() ); + } + } + catch ( const Exception& ) + { + SAL_WARN( "unotools.i18n", "lowercase: Exception caught!" ); + } + return rStr.copy( nPos, nCount ); +} + +sal_Int16 CharClass::getType( const OUString& rStr, sal_Int32 nPos ) const +{ + try + { + if ( xCC.is() ) + { + ::osl::MutexGuard aGuard( aMutex ); + return xCC->getType( rStr, nPos ); + } + } + catch ( const Exception& ) + { + SAL_WARN( "unotools.i18n", "getType: Exception caught!" ); + } + return 0; +} + +css::i18n::DirectionProperty CharClass::getCharacterDirection( const OUString& rStr, sal_Int32 nPos ) const +{ + try + { + if ( xCC.is() ) + { + ::osl::MutexGuard aGuard( aMutex ); + return static_cast<css::i18n::DirectionProperty>(xCC->getCharacterDirection( rStr, nPos )); + } + } + catch ( const Exception& ) + { + SAL_WARN( "unotools.i18n", "getCharacterDirection: Exception caught!" ); + } + return css::i18n::DirectionProperty_LEFT_TO_RIGHT; +} + +css::i18n::UnicodeScript CharClass::getScript( const OUString& rStr, sal_Int32 nPos ) const +{ + try + { + if ( xCC.is() ) + { + ::osl::MutexGuard aGuard( aMutex ); + return static_cast<css::i18n::UnicodeScript>(xCC->getScript( rStr, nPos )); + } + } + catch ( const Exception& ) + { + SAL_WARN( "unotools.i18n", "getScript: Exception caught!" ); + } + return UnicodeScript_kBasicLatin; +} + +sal_Int32 CharClass::getCharacterType( const OUString& rStr, sal_Int32 nPos ) const +{ + try + { + if ( xCC.is() ) + { + ::osl::MutexGuard aGuard( aMutex ); + return xCC->getCharacterType( rStr, nPos, getMyLocale() ); + } + } + catch ( const Exception& ) + { + SAL_WARN( "unotools.i18n", "getCharacterType: Exception caught!" ); + } + return 0; +} + +sal_Int32 CharClass::getStringType( const OUString& rStr, sal_Int32 nPos, sal_Int32 nCount ) const +{ + try + { + if ( xCC.is() ) + { + ::osl::MutexGuard aGuard( aMutex ); + return xCC->getStringType( rStr, nPos, nCount, getMyLocale() ); + } + } + catch ( const Exception& ) + { + SAL_WARN( "unotools.i18n", "getStringType: Exception caught!" ); + } + 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 + { + if ( xCC.is() ) + { + ::osl::MutexGuard aGuard( aMutex ); + 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 + { + if ( xCC.is() ) + { + ::osl::MutexGuard aGuard( aMutex ); + 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..f15869fb6 --- /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> + +IntlWrapper::IntlWrapper( + const LanguageTag& rLanguageTag ) + : + maLanguageTag( rLanguageTag ), + 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..f9ade4996 --- /dev/null +++ b/unotools/source/i18n/localedatawrapper.cxx @@ -0,0 +1,1826 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 <stdio.h> +#include <string> + +#include <sal/log.hxx> +#include <unotools/localedatawrapper.hxx> +#include <unotools/calendarwrapper.hxx> +#include <unotools/digitgroupingiterator.hxx> +#include <tools/diagnose_ex.h> +#include <tools/debug.hxx> +#include <i18nlangtag/languagetag.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/CalendarFieldIndex.hpp> +#include <com/sun/star/i18n/CalendarDisplayIndex.hpp> +#include <com/sun/star/i18n/NumberFormatIndex.hpp> +#include <com/sun/star/i18n/NumberFormatMapper.hpp> + +#include <comphelper/processfactory.hxx> +#include <rtl/instance.hxx> +#include <rtl/ustrbuf.hxx> +#include <rtl/math.hxx> + +static const sal_uInt16 nCurrFormatInvalid = 0xffff; +static const sal_uInt16 nCurrFormatDefault = 0; + +using namespace ::com::sun::star; +using namespace ::com::sun::star::i18n; +using namespace ::com::sun::star::uno; + +namespace +{ + struct InstalledLocales + : public rtl::Static< + uno::Sequence< lang::Locale >, InstalledLocales > + {}; + + struct InstalledLanguageTypes + : public rtl::Static< + std::vector< LanguageType >, InstalledLanguageTypes > + {}; +} + +bool LocaleDataWrapper::Locale_Compare::operator()(const css::lang::Locale& rLocale1, const css::lang::Locale& rLocale2) const +{ + if (rLocale1.Language < rLocale2.Language) + return true; + else if (rLocale1.Language > rLocale2.Language) + return false; + + if (rLocale1.Country < rLocale2.Country) + return true; + else if (rLocale1.Country > rLocale2.Country) + return false; + + return rLocale1.Variant < rLocale2.Variant; +} + +sal_uInt8 LocaleDataWrapper::nLocaleDataChecking = 0; + +LocaleDataWrapper::LocaleDataWrapper( + const Reference< uno::XComponentContext > & rxContext, + const LanguageTag& rLanguageTag + ) + : + m_xContext( rxContext ), + xLD( LocaleData2::create(rxContext) ), + maLanguageTag( rLanguageTag ), + bLocaleDataItemValid( false ), + bReservedWordValid( false ), + bSecondaryCalendarValid( false ) +{ + invalidateData(); +} + +LocaleDataWrapper::LocaleDataWrapper( + const LanguageTag& rLanguageTag + ) + : + m_xContext( comphelper::getProcessComponentContext() ), + xLD( LocaleData2::create(m_xContext) ), + maLanguageTag( rLanguageTag ), + bLocaleDataItemValid( false ), + bReservedWordValid( false ), + bSecondaryCalendarValid( false ) +{ + invalidateData(); +} + +LocaleDataWrapper::~LocaleDataWrapper() +{ +} + +void LocaleDataWrapper::setLanguageTag( const LanguageTag& rLanguageTag ) +{ + ::utl::ReadWriteGuard aGuard( aMutex, ReadWriteGuardMode::CriticalChange ); + maLanguageTag = rLanguageTag; + invalidateData(); +} + +const LanguageTag& LocaleDataWrapper::getLanguageTag() const +{ + ::utl::ReadWriteGuard aGuard( aMutex ); + return maLanguageTag; +} + +const css::lang::Locale& LocaleDataWrapper::getMyLocale() const +{ + ::utl::ReadWriteGuard aGuard( aMutex ); + return maLanguageTag.getLocale(); +} + +void LocaleDataWrapper::invalidateData() +{ + aCurrSymbol.clear(); + aCurrBankSymbol.clear(); + nDateOrder = nLongDateOrder = DateOrder::Invalid; + nCurrPositiveFormat = nCurrNegativeFormat = nCurrDigits = nCurrFormatInvalid; + if ( bLocaleDataItemValid ) + { + for (OUString & j : aLocaleItem) + j.clear(); + bLocaleDataItemValid = false; + } + if ( bReservedWordValid ) + { + for (OUString & j : aReservedWord) + j.clear(); + bReservedWordValid = false; + } + xDefaultCalendar.reset(); + xSecondaryCalendar.reset(); + bSecondaryCalendarValid = false; + if (aGrouping.hasElements()) + aGrouping[0] = 0; + if (aDateAcceptancePatterns.hasElements()) + aDateAcceptancePatterns = Sequence<OUString>(); +} + +/* 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 +{ + { + ::utl::ReadWriteGuard aGuard( aMutex ); + const css::lang::Locale& rLocal = getMyLocale(); + auto itr = maDataItemCache.find(rLocal); + if (itr != maDataItemCache.end()) + return itr->second; + } + + try + { + ::utl::ReadWriteGuard aGuard( aMutex ); + + const css::lang::Locale& rLocal = getMyLocale(); + css::i18n::LocaleDataItem2 aItem = xLD->getLocaleItem2( rLocal ); + auto aRet = maDataItemCache.insert(std::make_pair(rLocal, aItem)); + assert(aRet.second); + return aRet.first->second; + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "getLocaleItem" ); + } + static css::i18n::LocaleDataItem2 aEmptyItem; + return aEmptyItem; +} + +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::Currency2 >(0); +} + +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::uno::Sequence< css::i18n::FormatElement >(0); +} + +css::i18n::ForbiddenCharacters LocaleDataWrapper::getForbiddenCharacters() const +{ + try + { + return xLD->getForbiddenCharacters( getMyLocale() ); + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "getForbiddenCharacters" ); + } + return css::i18n::ForbiddenCharacters(); +} + +css::uno::Sequence< OUString > LocaleDataWrapper::getReservedWord() const +{ + try + { + return xLD->getReservedWord( getMyLocale() ); + } + catch ( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "getReservedWord" ); + } + return css::uno::Sequence< OUString >(0); +} + +css::uno::Sequence< css::lang::Locale > LocaleDataWrapper::getAllInstalledLocaleNames() const +{ + uno::Sequence< lang::Locale > &rInstalledLocales = InstalledLocales::get(); + + if ( rInstalledLocales.hasElements() ) + return rInstalledLocales; + + try + { + rInstalledLocales = xLD->getAllInstalledLocaleNames(); + } + catch ( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "getAllInstalledLocaleNames" ); + } + return rInstalledLocales; +} + +// --- Impl and helpers ---------------------------------------------------- + +// static +css::uno::Sequence< css::lang::Locale > LocaleDataWrapper::getInstalledLocaleNames() +{ + const uno::Sequence< lang::Locale > &rInstalledLocales = + InstalledLocales::get(); + + if ( !rInstalledLocales.hasElements() ) + { + LocaleDataWrapper aLDW( ::comphelper::getProcessComponentContext(), LanguageTag( LANGUAGE_SYSTEM) ); + aLDW.getAllInstalledLocaleNames(); + } + return rInstalledLocales; +} + +// static +std::vector< LanguageType > LocaleDataWrapper::getInstalledLanguageTypes() +{ + std::vector< LanguageType > &rInstalledLanguageTypes = + InstalledLanguageTypes::get(); + + 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 +{ + ::utl::ReadWriteGuard aGuard( aMutex ); + if ( nItem >= LocaleItem::COUNT2 ) + { + SAL_WARN( "unotools.i18n", "getOneLocaleItem: bounds" ); + return aLocaleItem[0]; + } + if (aLocaleItem[nItem].isEmpty()) + { // no cached content + aGuard.changeReadToWrite(); + const_cast<LocaleDataWrapper*>(this)->getOneLocaleItemImpl( nItem ); + } + return aLocaleItem[nItem]; +} + +void LocaleDataWrapper::getOneLocaleItemImpl( sal_Int16 nItem ) +{ + if ( !bLocaleDataItemValid ) + { + aLocaleDataItem = getLocaleItem(); + bLocaleDataItemValid = true; + } + switch ( nItem ) + { + case LocaleItem::DATE_SEPARATOR : + aLocaleItem[nItem] = aLocaleDataItem.dateSeparator; + break; + case LocaleItem::THOUSAND_SEPARATOR : + aLocaleItem[nItem] = aLocaleDataItem.thousandSeparator; + break; + case LocaleItem::DECIMAL_SEPARATOR : + aLocaleItem[nItem] = aLocaleDataItem.decimalSeparator; + break; + case LocaleItem::TIME_SEPARATOR : + aLocaleItem[nItem] = aLocaleDataItem.timeSeparator; + break; + case LocaleItem::TIME_100SEC_SEPARATOR : + aLocaleItem[nItem] = aLocaleDataItem.time100SecSeparator; + break; + case LocaleItem::LIST_SEPARATOR : + aLocaleItem[nItem] = aLocaleDataItem.listSeparator; + break; + case LocaleItem::SINGLE_QUOTATION_START : + aLocaleItem[nItem] = aLocaleDataItem.quotationStart; + break; + case LocaleItem::SINGLE_QUOTATION_END : + aLocaleItem[nItem] = aLocaleDataItem.quotationEnd; + break; + case LocaleItem::DOUBLE_QUOTATION_START : + aLocaleItem[nItem] = aLocaleDataItem.doubleQuotationStart; + break; + case LocaleItem::DOUBLE_QUOTATION_END : + aLocaleItem[nItem] = aLocaleDataItem.doubleQuotationEnd; + break; + case LocaleItem::MEASUREMENT_SYSTEM : + aLocaleItem[nItem] = aLocaleDataItem.measurementSystem; + break; + case LocaleItem::TIME_AM : + aLocaleItem[nItem] = aLocaleDataItem.timeAM; + break; + case LocaleItem::TIME_PM : + aLocaleItem[nItem] = aLocaleDataItem.timePM; + break; + case LocaleItem::LONG_DATE_DAY_OF_WEEK_SEPARATOR : + aLocaleItem[nItem] = aLocaleDataItem.LongDateDayOfWeekSeparator; + break; + case LocaleItem::LONG_DATE_DAY_SEPARATOR : + aLocaleItem[nItem] = aLocaleDataItem.LongDateDaySeparator; + break; + case LocaleItem::LONG_DATE_MONTH_SEPARATOR : + aLocaleItem[nItem] = aLocaleDataItem.LongDateMonthSeparator; + break; + case LocaleItem::LONG_DATE_YEAR_SEPARATOR : + aLocaleItem[nItem] = aLocaleDataItem.LongDateYearSeparator; + break; + case LocaleItem::DECIMAL_SEPARATOR_ALTERNATIVE : + aLocaleItem[nItem] = aLocaleDataItem.decimalSeparatorAlternative; + break; + default: + SAL_WARN( "unotools.i18n", "getOneLocaleItemImpl: which one?" ); + } +} + +void LocaleDataWrapper::getOneReservedWordImpl( sal_Int16 nWord ) +{ + if ( !bReservedWordValid ) + { + aReservedWordSeq = getReservedWord(); + bReservedWordValid = true; + } + DBG_ASSERT( nWord < aReservedWordSeq.getLength(), "getOneReservedWordImpl: which one?" ); + if ( nWord < aReservedWordSeq.getLength() ) + aReservedWord[nWord] = aReservedWordSeq[nWord]; +} + +const OUString& LocaleDataWrapper::getOneReservedWord( sal_Int16 nWord ) const +{ + ::utl::ReadWriteGuard aGuard( aMutex ); + if ( nWord < 0 || nWord >= reservedWords::COUNT ) + { + SAL_WARN( "unotools.i18n", "getOneReservedWord: bounds" ); + nWord = reservedWords::FALSE_WORD; + } + if (aReservedWord[nWord].isEmpty()) + { // no cached content + aGuard.changeReadToWrite(); + const_cast<LocaleDataWrapper*>(this)->getOneReservedWordImpl( nWord ); + } + return aReservedWord[nWord]; +} + +MeasurementSystem LocaleDataWrapper::mapMeasurementStringToEnum( const OUString& rMS ) const +{ +//! TODO: could be cached too + if ( rMS.equalsIgnoreAsciiCase( "metric" ) ) + return MeasurementSystem::Metric; +//! TODO: other measurement systems? => extend enum MeasurementSystem + return MeasurementSystem::US; +} + +void LocaleDataWrapper::getSecondaryCalendarImpl() +{ + if (!xSecondaryCalendar && !bSecondaryCalendarValid) + { + 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); + } + bSecondaryCalendarValid = true; + } +} + +bool LocaleDataWrapper::doesSecondaryCalendarUseEC( const OUString& rName ) const +{ + if (rName.isEmpty()) + 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; + + ::utl::ReadWriteGuard aGuard( aMutex ); + + if (!bSecondaryCalendarValid) + { // no cached content + aGuard.changeReadToWrite(); + const_cast<LocaleDataWrapper*>(this)->getSecondaryCalendarImpl(); + } + if (!xSecondaryCalendar) + return false; + if (!xSecondaryCalendar->Name.equalsIgnoreAsciiCase( rName)) + return false; + + return true; +} + +void LocaleDataWrapper::getDefaultCalendarImpl() +{ + if (!xDefaultCalendar) + { + Sequence< Calendar2 > xCals = getAllCalendars(); + 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); + } +} + +const std::shared_ptr< css::i18n::Calendar2 >& LocaleDataWrapper::getDefaultCalendar() const +{ + ::utl::ReadWriteGuard aGuard( aMutex ); + if (!xDefaultCalendar) + { // no cached content + aGuard.changeReadToWrite(); + const_cast<LocaleDataWrapper*>(this)->getDefaultCalendarImpl(); + } + 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 +{ + ::utl::ReadWriteGuard aGuard( aMutex ); + if (aCurrSymbol.isEmpty()) + { + aGuard.changeReadToWrite(); + const_cast<LocaleDataWrapper*>(this)->getCurrSymbolsImpl(); + } + return aCurrSymbol; +} + +const OUString& LocaleDataWrapper::getCurrBankSymbol() const +{ + ::utl::ReadWriteGuard aGuard( aMutex ); + if (aCurrBankSymbol.isEmpty()) + { + aGuard.changeReadToWrite(); + const_cast<LocaleDataWrapper*>(this)->getCurrSymbolsImpl(); + } + return aCurrBankSymbol; +} + +sal_uInt16 LocaleDataWrapper::getCurrPositiveFormat() const +{ + ::utl::ReadWriteGuard aGuard( aMutex ); + if ( nCurrPositiveFormat == nCurrFormatInvalid ) + { + aGuard.changeReadToWrite(); + const_cast<LocaleDataWrapper*>(this)->getCurrFormatsImpl(); + } + return nCurrPositiveFormat; +} + +sal_uInt16 LocaleDataWrapper::getCurrNegativeFormat() const +{ + ::utl::ReadWriteGuard aGuard( aMutex ); + if ( nCurrNegativeFormat == nCurrFormatInvalid ) + { + aGuard.changeReadToWrite(); + const_cast<LocaleDataWrapper*>(this)->getCurrFormatsImpl(); + } + return nCurrNegativeFormat; +} + +sal_uInt16 LocaleDataWrapper::getCurrDigits() const +{ + ::utl::ReadWriteGuard aGuard( aMutex ); + if ( nCurrDigits == nCurrFormatInvalid ) + { + aGuard.changeReadToWrite(); + const_cast<LocaleDataWrapper*>(this)->getCurrSymbolsImpl(); + } + return nCurrDigits; +} + +void LocaleDataWrapper::getCurrSymbolsImpl() +{ + Sequence< Currency2 > aCurrSeq = getAllCurrencies(); + if ( !aCurrSeq.hasElements() ) + { + if (areChecksEnabled()) + outputCheckMessage(OUString("LocaleDataWrapper::getCurrSymbolsImpl: no currency at all, using ShellsAndPebbles")); + aCurrSymbol = "ShellsAndPebbles"; + aCurrBankSymbol = aCurrSymbol; + nCurrPositiveFormat = nCurrNegativeFormat = nCurrFormatDefault; + nCurrDigits = 2; + return; + } + auto pCurr = std::find_if(aCurrSeq.begin(), aCurrSeq.end(), + [](const Currency2& rCurr) { return rCurr.Default; }); + if ( pCurr == aCurrSeq.end() ) + { + if (areChecksEnabled()) + { + outputCheckMessage( appendLocaleInfo( "LocaleDataWrapper::getCurrSymbolsImpl: no default currency" ) ); + } + pCurr = aCurrSeq.begin(); + } + aCurrSymbol = pCurr->Symbol; + aCurrBankSymbol = pCurr->BankSymbol; + nCurrDigits = pCurr->DecimalPlaces; +} + +void LocaleDataWrapper::scanCurrFormatImpl( const OUString& 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.getStr(); + const sal_Unicode* const pStop = pStr + rCode.getLength(); + 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 && rCode.match(aCurrSymbol, static_cast<sal_Int32>(p - pStr))) + { // 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::getCurrFormatsImpl() +{ + css::uno::Reference< css::i18n::XNumberFormatCode > xNFC = i18n::NumberFormatMapper::create( m_xContext ); + uno::Sequence< NumberFormatCode > aFormatSeq = xNFC->getAllFormatCode( KNumberFormatUsage::CURRENCY, getMyLocale() ); + sal_Int32 nCnt = aFormatSeq.getLength(); + if ( !nCnt ) + { // bad luck + if (areChecksEnabled()) + { + outputCheckMessage( appendLocaleInfo( "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; + } + } + + // make sure it's loaded + getCurrSymbol(); + + 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( "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( "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 +{ + ::utl::ReadWriteGuard aGuard( aMutex ); + if ( nDateOrder == DateOrder::Invalid ) + { + aGuard.changeReadToWrite(); + const_cast<LocaleDataWrapper*>(this)->getDateOrdersImpl(); + } + return nDateOrder; +} + +DateOrder LocaleDataWrapper::getLongDateOrder() const +{ + ::utl::ReadWriteGuard aGuard( aMutex ); + if ( nLongDateOrder == DateOrder::Invalid ) + { + aGuard.changeReadToWrite(); + const_cast<LocaleDataWrapper*>(this)->getDateOrdersImpl(); + } + return nLongDateOrder; +} + +DateOrder LocaleDataWrapper::scanDateOrderImpl( const OUString& 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 + sal_Int32 nDay = rCode.indexOf('D'); + sal_Int32 nMonth = rCode.indexOf('M'); + sal_Int32 nYear = rCode.indexOf('Y'); + if (nDay == -1 || nMonth == -1 || nYear == -1) + { // This algorithm assumes that all three parts (DMY) are present + if (nMonth == -1) + { // only Finnish has something else than 'M' for month + nMonth = rCode.indexOf('K'); + if (nMonth != -1) + { + nDay = rCode.indexOf('P'); + nYear = rCode.indexOf('V'); + } + } + else if (nDay == -1) + { // We have a month 'M' if we reach this branch. + // Possible languages containing 'M' but no 'D': + // German, French, Italian + nDay = rCode.indexOf('T'); // German + if (nDay != -1) + nYear = rCode.indexOf('J'); + else + { + nYear = rCode.indexOf('A'); // French, Italian + if (nYear != -1) + { + nDay = rCode.indexOf('J'); // French + if (nDay == -1) + nDay = rCode.indexOf('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.indexOf('A'); // Spanish + if (nYear == -1) + nYear = rCode.indexOf('J'); // Dutch + } + if (nDay == -1 || nMonth == -1 || nYear == -1) + { + if (areChecksEnabled()) + { + outputCheckMessage( appendLocaleInfo( "LocaleDataWrapper::scanDateOrder: not all DMY present" ) ); + } + if (nDay == -1) + nDay = rCode.getLength(); + if (nMonth == -1) + nMonth = rCode.getLength(); + if (nYear == -1) + nYear = rCode.getLength(); + } + } + // compare with <= because each position may equal rCode.getLength() + if ( nDay <= nMonth && nMonth <= nYear ) + return DateOrder::DMY; // also if every position equals rCode.getLength() + else if ( nMonth <= nDay && nDay <= nYear ) + return DateOrder::MDY; + else if ( nYear <= nMonth && nMonth <= nDay ) + return DateOrder::YMD; + else + { + if (areChecksEnabled()) + { + outputCheckMessage( appendLocaleInfo( "LocaleDataWrapper::scanDateOrder: no magic applicable" ) ); + } + return DateOrder::DMY; + } +} + +void LocaleDataWrapper::getDateOrdersImpl() +{ + css::uno::Reference< css::i18n::XNumberFormatCode > xNFC = i18n::NumberFormatMapper::create( m_xContext ); + uno::Sequence< NumberFormatCode > aFormatSeq = xNFC->getAllFormatCode( KNumberFormatUsage::DATE, getMyLocale() ); + sal_Int32 nCnt = aFormatSeq.getLength(); + if ( !nCnt ) + { // bad luck + if (areChecksEnabled()) + { + outputCheckMessage( appendLocaleInfo( "LocaleDataWrapper::getDateOrdersImpl: no date formats" ) ); + } + nDateOrder = nLongDateOrder = DateOrder::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( "LocaleDataWrapper::getDateOrdersImpl: no edit" ) ); + } + if ( nDef == -1 ) + { + if (areChecksEnabled()) + { + outputCheckMessage( appendLocaleInfo( "LocaleDataWrapper::getDateOrdersImpl: no default" ) ); + } + if ( nMedium != -1 ) + nDef = nMedium; + else if ( nLong != -1 ) + nDef = nLong; + else + nDef = 0; + } + nEdit = nDef; + } + DateOrder nDF = scanDateOrderImpl( pFormatArr[nEdit].Code ); + if ( pFormatArr[nEdit].Type == KNumberFormatType::LONG ) + { // normally this is not the case + nLongDateOrder = nDateOrder = nDF; + } + else + { + nDateOrder = nDF; + if ( nLong == -1 ) + nLongDateOrder = nDF; + else + nLongDateOrder = scanDateOrderImpl( pFormatArr[nLong].Code ); + } +} + +// --- digit grouping ------------------------------------------------- + +void LocaleDataWrapper::getDigitGroupingImpl() +{ + /* 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.realloc(3); // room for {3,2,0} + aGrouping[0] = 0; // invalidate + } + if (!aGrouping[0]) + { + i18n::LanguageCountryInfo aLCInfo( getLanguageCountryInfo()); + if (aLCInfo.Country.equalsIgnoreAsciiCase("IN") || // India + aLCInfo.Country.equalsIgnoreAsciiCase("BT") ) // Bhutan + { + aGrouping[0] = 3; + aGrouping[1] = 2; + aGrouping[2] = 0; + } + else + { + aGrouping[0] = 3; + aGrouping[1] = 0; + } + } +} + +css::uno::Sequence< sal_Int32 > LocaleDataWrapper::getDigitGrouping() const +{ + ::utl::ReadWriteGuard aGuard( aMutex ); + if (!aGrouping.hasElements() || aGrouping[0] == 0) + { // no cached content + aGuard.changeReadToWrite(); + const_cast<LocaleDataWrapper*>(this)->getDigitGroupingImpl(); + } + 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, bool bLeading ) +{ + DBG_ASSERT( nNumber < 100, "ImplAdd2UNum() - Number >= 100" ); + + if ( nNumber < 10 ) + { + if ( bLeading ) + { + 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 + if ( nNumber < 0 ) + { + nNumber *= -1; + rBuf.append('-'); + } + + // convert number + ImplAddUNum( aNumBuf, static_cast<sal_uInt64>(nNumber) ); + 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( getNumDecimalSep() ); + + // fill with zeros + sal_uInt16 i = 0; + while ( i < (nDecimals-nNumLen) ) + { + rBuf.append('0'); + i++; + } + + // append decimals + rBuf.append(aNumBuf); + } + } + else + { + const OUString& rThoSep = getNumThousandSep(); + + // 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( getNumDecimalSep() ); + + 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 +{ + ::utl::ReadWriteGuard aGuard( aMutex, ReadWriteGuardMode::BlockCritical ); +//!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, true /* IsDateDayLeadingZero() */ ); + aBuf.append( getDateSep() ); + ImplAdd2UNum( aBuf, nMonth, true /* IsDateMonthLeadingZero() */ ); + aBuf.append( getDateSep() ); + ImplAddNum( aBuf, nYear, nYearLen ); + break; + case DateOrder::MDY : + ImplAdd2UNum( aBuf, nMonth, true /* IsDateMonthLeadingZero() */ ); + aBuf.append( getDateSep() ); + ImplAdd2UNum( aBuf, nDay, true /* IsDateDayLeadingZero() */ ); + aBuf.append( getDateSep() ); + ImplAddNum( aBuf, nYear, nYearLen ); + break; + default: + ImplAddNum( aBuf, nYear, nYearLen ); + aBuf.append( getDateSep() ); + ImplAdd2UNum( aBuf, nMonth, true /* IsDateMonthLeadingZero() */ ); + aBuf.append( getDateSep() ); + ImplAdd2UNum( aBuf, nDay, true /* IsDateDayLeadingZero() */ ); + } + + return aBuf.makeStringAndClear(); +} + +OUString LocaleDataWrapper::getTime( const tools::Time& rTime, bool bSec, bool b100Sec ) const +{ + ::utl::ReadWriteGuard aGuard( aMutex, ReadWriteGuardMode::BlockCritical ); +//!TODO: leading zeros et al + OUStringBuffer aBuf(128); + sal_uInt16 nHour = rTime.GetHour(); + + nHour %= 24; + + ImplAdd2UNum( aBuf, nHour, true /* IsTimeLeadingZero() */ ); + aBuf.append( getTimeSep() ); + ImplAdd2UNum( aBuf, rTime.GetMin(), true ); + if ( bSec ) + { + aBuf.append( getTimeSep() ); + ImplAdd2UNum( aBuf, rTime.GetSec(), true ); + + if ( b100Sec ) + { + aBuf.append( getTime100SecSep() ); + ImplAdd9UNum( aBuf, rTime.GetNanoSec() ); + } + } + + return aBuf.makeStringAndClear(); +} + +OUString LocaleDataWrapper::getLongDate( const Date& rDate, CalendarWrapper& rCal, + bool bTwoDigitYear ) const +{ + ::utl::ReadWriteGuard aGuard( aMutex, ReadWriteGuardMode::BlockCritical ); + OUStringBuffer aBuf(20); + OUStringBuffer aStr(120); // complete guess + sal_Int16 nVal; + rCal.setGregorianDateTime( rDate ); + // day of week + nVal = rCal.getValue( CalendarFieldIndex::DAY_OF_WEEK ); + aStr.append(rCal.getDisplayName( CalendarDisplayIndex::DAY, nVal, 1 )); + aStr.append(getLongDateDayOfWeekSep()); + // day of month + nVal = rCal.getValue( CalendarFieldIndex::DAY_OF_MONTH ); + ImplAdd2UNum( aBuf, nVal, false/*bDayOfMonthWithLeadingZero*/ ); + OUString aDay = aBuf.makeStringAndClear(); + // month of year + nVal = rCal.getValue( CalendarFieldIndex::MONTH ); + OUString aMonth( rCal.getDisplayName( CalendarDisplayIndex::MONTH, nVal, 1 ) ); + // year + nVal = rCal.getValue( CalendarFieldIndex::YEAR ); + if ( bTwoDigitYear ) + ImplAddUNum( aBuf, nVal % 100, 2 ); + else + ImplAddUNum( aBuf, nVal ); + OUString aYear = aBuf.makeStringAndClear(); + // concatenate + switch ( getLongDateOrder() ) + { + case DateOrder::DMY : + aStr.append(aDay).append(getLongDateDaySep()).append(aMonth).append(getLongDateMonthSep()).append(aYear); + break; + case DateOrder::MDY : + aStr.append(aMonth).append(getLongDateMonthSep()).append(aDay).append(getLongDateDaySep()).append(aYear); + break; + default: // YMD + aStr.append(aYear).append(getLongDateYearSep()).append(aMonth).append(getLongDateMonthSep()).append(aDay); + } + return aStr.makeStringAndClear(); +} + +OUString LocaleDataWrapper::getDuration( const tools::Time& rTime, bool bSec, bool b100Sec ) const +{ + ::utl::ReadWriteGuard aGuard( aMutex, ReadWriteGuardMode::BlockCritical ); + 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( getTimeSep() ); + ImplAdd2UNum( aBuf, rTime.GetMin(), true ); + if ( bSec ) + { + aBuf.append( getTimeSep() ); + ImplAdd2UNum( aBuf, rTime.GetSec(), true ); + + if ( b100Sec ) + { + aBuf.append( getTime100SecSep() ); + ImplAdd9UNum( aBuf, rTime.GetNanoSec() ); + } + } + + return aBuf.makeStringAndClear(); +} + +// --- simple number formatting --------------------------------------- + +static size_t ImplGetNumberStringLengthGuess( const LocaleDataWrapper& rLoc, 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) * rLoc.getNumThousandSep().getLength()) + nDig) : + nDecimals) + rLoc.getNumDecimalSep().getLength() + 3; + return nGuess; +} + +OUString LocaleDataWrapper::getNum( sal_Int64 nNumber, sal_uInt16 nDecimals, + bool bUseThousandSep, bool bTrailingZeros ) const +{ + ::utl::ReadWriteGuard aGuard( aMutex, ReadWriteGuardMode::BlockCritical ); + // check if digits and separators will fit into fixed buffer or allocate + size_t nGuess = ImplGetNumberStringLengthGuess( *this, nDecimals ); + OUStringBuffer aBuf(int(nGuess + 16)); + + ImplAddFormatNum( aBuf, nNumber, nDecimals, + bUseThousandSep, bTrailingZeros ); + + return aBuf.makeStringAndClear(); +} + +OUString LocaleDataWrapper::getCurr( sal_Int64 nNumber, sal_uInt16 nDecimals, + const OUString& rCurrencySymbol, bool bUseThousandSep ) const +{ + ::utl::ReadWriteGuard aGuard( aMutex, ReadWriteGuardMode::BlockCritical ); + sal_Unicode cZeroChar = getCurrZeroChar(); + + // check if digits and separators will fit into fixed buffer or allocate + size_t nGuess = ImplGetNumberStringLengthGuess( *this, nDecimals ); + OUStringBuffer aNumBuf(int(nGuess + 16)); + OUStringBuffer aBuf(int(rCurrencySymbol.getLength() + 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( const OUString& rString, bool bUseGroupSep, + rtl_math_ConversionStatus* pStatus, sal_Int32* pParseEnd ) const +{ + const sal_Unicode cGroupSep = (bUseGroupSep ? getNumThousandSep()[0] : 0); + rtl_math_ConversionStatus eStatus = rtl_math_ConversionStatus_Ok; + sal_Int32 nParseEnd = 0; + double fValue = rtl::math::stringToDouble( rString, getNumDecimalSep()[0], cGroupSep, &eStatus, &nParseEnd); + bool bTryAlt = (nParseEnd < rString.getLength() && !getNumDecimalSepAlt().isEmpty() && + rString[nParseEnd] == getNumDecimalSepAlt().toChar()); + // Try re-parsing with alternative if that was the reason to stop. + if (bTryAlt) + fValue = rtl::math::stringToDouble( rString, getNumDecimalSepAlt().toChar(), cGroupSep, &eStatus, &nParseEnd); + if (pStatus) + *pStatus = eStatus; + if (pParseEnd) + *pParseEnd = nParseEnd; + 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 ? getNumThousandSep()[0] : 0); + rtl_math_ConversionStatus eStatus = rtl_math_ConversionStatus_Ok; + const sal_Unicode* pParseEnd = nullptr; + double fValue = rtl_math_uStringToDouble( pBegin, pEnd, getNumDecimalSep()[0], cGroupSep, &eStatus, &pParseEnd); + bool bTryAlt = (pParseEnd < pEnd && !getNumDecimalSepAlt().isEmpty() && + *pParseEnd == getNumDecimalSepAlt().toChar()); + // Try re-parsing with alternative if that was the reason to stop. + if (bTryAlt) + fValue = rtl_math_uStringToDouble( pBegin, pEnd, getNumDecimalSepAlt().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(const OUString& rDebugMsg) const +{ + ::utl::ReadWriteGuard aGuard( aMutex, ReadWriteGuardMode::BlockCritical ); + OUStringBuffer aDebugMsg(rDebugMsg); + aDebugMsg.append('\n'); + aDebugMsg.append(maLanguageTag.getBcp47()); + aDebugMsg.append(" requested\n"); + LanguageTag aLoaded = getLoadedLanguageTag(); + aDebugMsg.append(aLoaded.getBcp47()); + aDebugMsg.append(" loaded"); + return aDebugMsg.makeStringAndClear(); +} + +// static +void LocaleDataWrapper::outputCheckMessage( const OUString& 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 css::uno::Sequence< css::i18n::Calendar2 >(0); +} + +// --- XLocaleData4 ---------------------------------------------------------- + +css::uno::Sequence< OUString > LocaleDataWrapper::getDateAcceptancePatterns() const +{ + ::utl::ReadWriteGuard aGuard( aMutex ); + + if (aDateAcceptancePatterns.hasElements()) + return aDateAcceptancePatterns; + + aGuard.changeReadToWrite(); + + try + { + const_cast<LocaleDataWrapper*>(this)->aDateAcceptancePatterns = + xLD->getDateAcceptancePatterns( getMyLocale() ); + return aDateAcceptancePatterns; + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "getDateAcceptancePatterns" ); + } + return css::uno::Sequence< OUString >(0); +} + +// --- Override layer -------------------------------------------------------- + +void LocaleDataWrapper::setDateAcceptancePatterns( + const css::uno::Sequence< OUString > & rPatterns ) +{ + ::utl::ReadWriteGuard aGuard( aMutex, ReadWriteGuardMode::Write ); + + if (!aDateAcceptancePatterns.hasElements() || !rPatterns.hasElements()) + { + try + { + aDateAcceptancePatterns = xLD->getDateAcceptancePatterns( getMyLocale() ); + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( "unotools.i18n", "setDateAcceptancePatterns" ); + } + if (!rPatterns.hasElements()) + return; // just a reset + if (!aDateAcceptancePatterns.hasElements()) + { + aDateAcceptancePatterns = rPatterns; + return; + } + } + + // Never overwrite the locale's full date pattern! The first. + if (aDateAcceptancePatterns[0] == rPatterns[0]) + aDateAcceptancePatterns = rPatterns; // sane + else + { + // Copy existing full date pattern and append the sequence passed. + /* TODO: could check for duplicates and shrink target sequence */ + Sequence< OUString > aTmp( rPatterns.getLength() + 1 ); + aTmp[0] = aDateAcceptancePatterns[0]; + std::copy(rPatterns.begin(), rPatterns.end(), std::next(aTmp.begin())); + aDateAcceptancePatterns = aTmp; + } +} + +/* 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..26fcbd7fa --- /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 <sal/log.hxx> +#include <unotools/nativenumberwrapper.hxx> +#include <com/sun/star/i18n/NativeNumberSupplier2.hpp> + +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& ) + { + SAL_WARN( "unotools.i18n", "getNativeNumberString: Exception caught!" ); + } + 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& ) + { + SAL_WARN( "unotools.i18n", "getNativeNumberStringParams: Exception caught!" ); + } + 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& ) + { + SAL_WARN( "unotools.i18n", "convertToXmlAttributes: Exception caught!" ); + } + return i18n::NativeNumberXmlAttributes(); +} + +sal_Int16 +NativeNumberWrapper::convertFromXmlAttributes( + const i18n::NativeNumberXmlAttributes& rAttr ) const +{ + try + { + if ( xNNS.is() ) + return xNNS->convertFromXmlAttributes( rAttr ); + } + catch ( const uno::Exception& ) + { + SAL_WARN( "unotools.i18n", "convertFromXmlAttributes: Exception caught!" ); + } + return 0; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/unotools/source/i18n/readwritemutexguard.cxx b/unotools/source/i18n/readwritemutexguard.cxx new file mode 100644 index 000000000..134f06c35 --- /dev/null +++ b/unotools/source/i18n/readwritemutexguard.cxx @@ -0,0 +1,109 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <unotools/readwritemutexguard.hxx> +#include <tools/debug.hxx> + +namespace utl { + +ReadWriteGuard::ReadWriteGuard( ReadWriteMutex& rMutexP, + ReadWriteGuardMode nRequestMode ) + : rMutex( rMutexP ) +{ + // don't do anything until a pending write completed (or another + // ReadWriteGuard leaves the ctor phase) + ::osl::MutexGuard aGuard( rMutex.maWriteMutex ); + nMode = nRequestMode; + if ( nMode & ReadWriteGuardMode::Write ) + { + rMutex.maWriteMutex.acquire(); + // wait for any read to complete +// TODO: set up a waiting thread instead of a loop + bool bWait = true; + do + { + rMutex.maMutex.acquire(); + bWait = (rMutex.nReadCount != 0); + if ( nMode & ReadWriteGuardMode::CriticalChange ) + bWait |= (rMutex.nBlockCriticalCount != 0); + rMutex.maMutex.release(); + } while ( bWait ); + } + else if ( nMode & ReadWriteGuardMode::BlockCritical ) + { + rMutex.maMutex.acquire(); + ++rMutex.nBlockCriticalCount; + rMutex.maMutex.release(); + } + else + { + rMutex.maMutex.acquire(); + ++rMutex.nReadCount; + rMutex.maMutex.release(); + } +} + +ReadWriteGuard::~ReadWriteGuard() +{ + if ( nMode & ReadWriteGuardMode::Write ) + rMutex.maWriteMutex.release(); + else if ( nMode & ReadWriteGuardMode::BlockCritical ) + { + rMutex.maMutex.acquire(); + --rMutex.nBlockCriticalCount; + rMutex.maMutex.release(); + } + else + { + rMutex.maMutex.acquire(); + --rMutex.nReadCount; + rMutex.maMutex.release(); + } +} + +void ReadWriteGuard::changeReadToWrite() +{ + bool bOk = !(nMode & (ReadWriteGuardMode::Write | ReadWriteGuardMode::BlockCritical)); + DBG_ASSERT( bOk, "ReadWriteGuard::changeReadToWrite: can't" ); + if ( bOk ) + { + // MUST release read before acquiring write mutex or dead lock would + // occur if there was a write in another thread waiting for this read + // to complete. + rMutex.maMutex.acquire(); + --rMutex.nReadCount; + rMutex.maMutex.release(); + + rMutex.maWriteMutex.acquire(); + nMode |= ReadWriteGuardMode::Write; + // wait for any other read to complete +// TODO: set up a waiting thread instead of a loop + bool bWait = true; + do + { + rMutex.maMutex.acquire(); + bWait = (rMutex.nReadCount != 0); + rMutex.maMutex.release(); + } while ( bWait ); + } +} + +} // namespace utl + +/* 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..a1d74ec1b --- /dev/null +++ b/unotools/source/i18n/resmgr.cxx @@ -0,0 +1,280 @@ +/* -*- 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 . + */ + +// 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_LIBCXX +#define _LIBCPP_ENABLE_CXX17_REMOVED_AUTO_PTR +#elif defined _MSC_VER +#define _HAS_AUTO_PTR_ETC 1 +#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> +#include <memory> + +#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 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(const char* pPrefixName, const LanguageTag& rLocale) + { + static std::unordered_map<OString, std::locale> aCache; + OString sIdentifier = rLocale.getGlibcLocaleString(".UTF-8").toUtf8(); + OString sUnique = sIdentifier + pPrefixName; + 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); + OString sPath(OUStringToOString(path, osl_getThreadTextEncoding())); +#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(pPrefixName, sPath.getStr()); + // tdf#131069 gtk, and anything sane, always wants utf-8 strings as output + bind_textdomain_codeset(pPrefixName, "UTF-8"); +#endif + gen.add_messages_domain(pPrefixName); + +#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(const char* pContextAndId, const std::locale &loc) + { + OString sContext; + const char *pId = strchr(pContextAndId, '\004'); + if (!pId) + pId = pContextAndId; + else + { + sContext = OString(pContextAndId, pId - pContextAndId); + ++pId; + assert(!strchr(pId, '\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(pContextAndId).replace('\004', '|'))); + return OUString::fromUtf8(sKeyId) + u"\u2016" + createFromUtf8(pId, strlen(pId)); + } + + //otherwise translate it + const std::string ret = boost::locale::pgettext(sContext.getStr(), pId, 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(const char* pContextAndIds, int n, const std::locale &loc) + { + OString sContextIdId(pContextAndIds); + std::vector<OString> aContextIdId; + sal_Int32 nIndex = 0; + do + { + aContextIdId.push_back(sContextIdId.getToken(0, '\004', nIndex)); + } + while (nIndex >= 0); + assert(aContextIdId.size() == 3 && "should be using get, not nget"); + + //if it's a key id locale, generate it here + if (std::use_facet<boost::locale::info>(loc).language() == "qtz") + { + OString sKeyId(genKeyId(aContextIdId[0] + "|" + aContextIdId[1])); + int nForm = n == 0 ? 1 : 2; + return OUString::fromUtf8(sKeyId) + u"\u2016" + createFromUtf8(aContextIdId[nForm].getStr(), aContextIdId[nForm].getLength()); + } + + //otherwise translate it + const std::string ret = boost::locale::npgettext(aContextIdId[0].getStr(), aContextIdId[1].getStr(), aContextIdId[2].getStr(), 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; + } +} + +/* 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..42b995f70 --- /dev/null +++ b/unotools/source/i18n/textsearch.cxx @@ -0,0 +1,401 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 <sal/log.hxx> +#include <unotools/charclass.hxx> +#include <comphelper/processfactory.hxx> +#include <unotools/textsearch.hxx> +#include <rtl/instance.hxx> +#include <rtl/ustrbuf.hxx> + +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 + { + ::osl::Mutex mutex; + i18nutil::SearchOptions2 Options; + css::uno::Reference< css::util::XTextSearch2 > xTextSearch; + }; + + struct theCachedTextSearch + : public rtl::Static< CachedTextSearch, theCachedTextSearch > {}; +} + +Reference<XTextSearch2> TextSearch::getXTextSearch( const i18nutil::SearchOptions2& rPara ) +{ + CachedTextSearch &rCache = theCachedTextSearch::get(); + + osl::MutexGuard aGuard(rCache.mutex); + + if ( lcl_Equals(rCache.Options, rPara) ) + return rCache.xTextSearch; + + Reference< XComponentContext > xContext = ::comphelper::getProcessComponentContext(); + rCache.xTextSearch.set( ::TextSearch2::create(xContext) ); + rCache.xTextSearch->setOptions2( rPara.toUnoSearchOptions2() ); + rCache.Options = rPara; + + return rCache.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& ) + { + SAL_WARN( "unotools.i18n", "SearchForward: Exception caught!" ); + } + 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& ) + { + SAL_WARN( "unotools.i18n", "SearchBackward: Exception caught!" ); + } + return bRet; +} + +void TextSearch::ReplaceBackReferences( OUString& rReplaceStr, const OUString &rStr, const SearchResult& rResult ) const +{ + if( rResult.subRegExpressions > 0 ) + { + 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(std::u16string_view(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(std::u16string_view(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..5deb0e5a6 --- /dev/null +++ b/unotools/source/i18n/transliterationwrapper.cxx @@ -0,0 +1,232 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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/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& ) + { + SAL_WARN( "unotools.i18n", "transliterate: Exception caught!" ); + } + } + 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& ) + { + SAL_WARN( "unotools.i18n", "transliterate: Exception caught!" ); + } + } + 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: */ |