/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace ::com::sun::star; using namespace ::xmloff::token; namespace { struct SvXMLNumFmtEntry { OUString aName; sal_uInt32 nKey; bool bRemoveAfterUse; SvXMLNumFmtEntry( OUString aN, sal_uInt32 nK, bool bR ) : aName(std::move(aN)), nKey(nK), bRemoveAfterUse(bR) {} }; } class SvXMLNumImpData { SvNumberFormatter* pFormatter; std::unique_ptr pLocaleData; std::vector m_NameEntries; uno::Reference< uno::XComponentContext > m_xContext; public: SvXMLNumImpData( SvNumberFormatter* pFmt, const uno::Reference& rxContext ); SvNumberFormatter* GetNumberFormatter() const { return pFormatter; } const LocaleDataWrapper& GetLocaleData( LanguageType nLang ); sal_uInt32 GetKeyForName( std::u16string_view rName ); void AddKey( sal_uInt32 nKey, const OUString& rName, bool bRemoveAfterUse ); void SetUsed( sal_uInt32 nKey ); void RemoveVolatileFormats(); }; struct SvXMLNumberInfo { sal_Int32 nDecimals = -1; sal_Int32 nInteger = -1; /// Total min number of digits in integer part ('0' + '?') sal_Int32 nBlankInteger = -1; /// Number of '?' in integer part sal_Int32 nExpDigits = -1; /// Number of '0' and '?' in exponent sal_Int32 nBlankExp = -1; /// Number of '?' in exponent sal_Int32 nExpInterval = -1; sal_Int32 nMinNumerDigits = -1; sal_Int32 nMinDenomDigits = -1; sal_Int32 nMaxNumerDigits = -1; sal_Int32 nMaxDenomDigits = -1; sal_Int32 nFracDenominator = -1; sal_Int32 nMinDecimalDigits = -1; sal_Int32 nZerosNumerDigits = -1; sal_Int32 nZerosDenomDigits = -1; bool bGrouping = false; bool bDecReplace = false; bool bExpSign = true; bool bExponentLowercase = false; /// Exponent is 'e' instead of 'E' bool bDecAlign = false; double fDisplayFactor = 1.0; OUString aIntegerFractionDelimiter; std::map m_EmbeddedElements; }; namespace { enum class SvXMLStyleTokens; class SvXMLNumFmtElementContext : public SvXMLImportContext { SvXMLNumFormatContext& rParent; SvXMLStyleTokens nType; OUStringBuffer aContent; SvXMLNumberInfo aNumInfo; LanguageType nElementLang; bool bLong; bool bTextual; OUString sCalendar; OUString sBlankWidthString; public: SvXMLNumFmtElementContext( SvXMLImport& rImport, sal_Int32 nElement, SvXMLNumFormatContext& rParentContext, SvXMLStyleTokens nNewType, const css::uno::Reference< css::xml::sax::XFastAttributeList>& xAttrList ); virtual css::uno::Reference< css::xml::sax::XFastContextHandler > SAL_CALL createFastChildContext( sal_Int32 nElement, const css::uno::Reference< css::xml::sax::XFastAttributeList >& AttrList ) override; virtual void SAL_CALL characters( const OUString& rChars ) override; virtual void SAL_CALL endFastElement(sal_Int32 nElement) override; void AddEmbeddedElement( sal_Int32 nFormatPos, std::u16string_view rContent, std::u16string_view rBlankWidthString ); }; class SvXMLNumFmtEmbeddedTextContext : public SvXMLImportContext { SvXMLNumFmtElementContext& rParent; OUStringBuffer aContent; sal_Int32 nTextPosition; OUString aBlankWidthString; public: SvXMLNumFmtEmbeddedTextContext( SvXMLImport& rImport, sal_Int32 nElement, SvXMLNumFmtElementContext& rParentContext, const css::uno::Reference< css::xml::sax::XFastAttributeList>& xAttrList ); virtual void SAL_CALL characters( const OUString& rChars ) override; virtual void SAL_CALL endFastElement(sal_Int32 nElement) override; }; class SvXMLNumFmtMapContext : public SvXMLImportContext { SvXMLNumFormatContext& rParent; OUString sCondition; OUString sName; public: SvXMLNumFmtMapContext( SvXMLImport& rImport, sal_Int32 nElement, SvXMLNumFormatContext& rParentContext, const css::uno::Reference< css::xml::sax::XFastAttributeList>& xAttrList ); virtual void SAL_CALL endFastElement(sal_Int32 nElement) override; }; class SvXMLNumFmtPropContext : public SvXMLImportContext { SvXMLNumFormatContext& rParent; Color m_nColor; bool bColSet; public: SvXMLNumFmtPropContext( SvXMLImport& rImport, sal_Int32 nElement, SvXMLNumFormatContext& rParentContext, const css::uno::Reference< css::xml::sax::XFastAttributeList>& xAttrList ); virtual void SAL_CALL endFastElement(sal_Int32 nElement) override; }; enum class SvXMLStyleTokens { Text, FillCharacter, Number, ScientificNumber, Fraction, CurrencySymbol, Day, Month, Year, Era, DayOfWeek, WeekOfYear, Quarter, Hours, AmPm, Minutes, Seconds, Boolean, TextContent }; } // standard colors #define XML_NUMF_COLORCOUNT 10 const Color aNumFmtStdColors[XML_NUMF_COLORCOUNT] = { COL_BLACK, COL_LIGHTBLUE, COL_LIGHTGREEN, COL_LIGHTCYAN, COL_LIGHTRED, COL_LIGHTMAGENTA, COL_BROWN, COL_GRAY, COL_YELLOW, COL_WHITE }; // token maps // maps for SvXMLUnitConverter::convertEnum const SvXMLEnumMapEntry aStyleValueMap[] = { { XML_SHORT, false }, { XML_LONG, true }, { XML_TOKEN_INVALID, false } }; const SvXMLEnumMapEntry aFormatSourceMap[] = { { XML_FIXED, false }, { XML_LANGUAGE, true }, { XML_TOKEN_INVALID, false } }; namespace { struct SvXMLDefaultDateFormat { NfIndexTableOffset eFormat; SvXMLDateElementAttributes eDOW; SvXMLDateElementAttributes eDay; SvXMLDateElementAttributes eMonth; SvXMLDateElementAttributes eYear; SvXMLDateElementAttributes eHours; SvXMLDateElementAttributes eMins; SvXMLDateElementAttributes eSecs; bool bSystem; }; } const SvXMLDefaultDateFormat aDefaultDateFormats[] = { // format day-of-week day month year hours minutes seconds format-source { NF_DATE_SYSTEM_SHORT, XML_DEA_NONE, XML_DEA_ANY, XML_DEA_ANY, XML_DEA_ANY, XML_DEA_NONE, XML_DEA_NONE, XML_DEA_NONE, true }, { NF_DATE_SYSTEM_LONG, XML_DEA_ANY, XML_DEA_ANY, XML_DEA_ANY, XML_DEA_ANY, XML_DEA_NONE, XML_DEA_NONE, XML_DEA_NONE, true }, { NF_DATE_SYS_MMYY, XML_DEA_NONE, XML_DEA_NONE, XML_DEA_LONG, XML_DEA_SHORT, XML_DEA_NONE, XML_DEA_NONE, XML_DEA_NONE, false }, { NF_DATE_SYS_DDMMM, XML_DEA_NONE, XML_DEA_LONG, XML_DEA_TEXTSHORT, XML_DEA_NONE, XML_DEA_NONE, XML_DEA_NONE, XML_DEA_NONE, false }, { NF_DATE_SYS_DDMMYYYY, XML_DEA_NONE, XML_DEA_LONG, XML_DEA_LONG, XML_DEA_LONG, XML_DEA_NONE, XML_DEA_NONE, XML_DEA_NONE, false }, { NF_DATE_SYS_DDMMYY, XML_DEA_NONE, XML_DEA_LONG, XML_DEA_LONG, XML_DEA_SHORT, XML_DEA_NONE, XML_DEA_NONE, XML_DEA_NONE, false }, { NF_DATE_SYS_DMMMYY, XML_DEA_NONE, XML_DEA_SHORT, XML_DEA_TEXTSHORT, XML_DEA_SHORT, XML_DEA_NONE, XML_DEA_NONE, XML_DEA_NONE, false }, { NF_DATE_SYS_DMMMYYYY, XML_DEA_NONE, XML_DEA_SHORT, XML_DEA_TEXTSHORT, XML_DEA_LONG, XML_DEA_NONE, XML_DEA_NONE, XML_DEA_NONE, false }, { NF_DATE_SYS_DMMMMYYYY, XML_DEA_NONE, XML_DEA_SHORT, XML_DEA_TEXTLONG, XML_DEA_LONG, XML_DEA_NONE, XML_DEA_NONE, XML_DEA_NONE, false }, { NF_DATE_SYS_NNDMMMYY, XML_DEA_SHORT, XML_DEA_SHORT, XML_DEA_TEXTSHORT, XML_DEA_SHORT, XML_DEA_NONE, XML_DEA_NONE, XML_DEA_NONE, false }, { NF_DATE_SYS_NNDMMMMYYYY, XML_DEA_SHORT, XML_DEA_SHORT, XML_DEA_TEXTLONG, XML_DEA_LONG, XML_DEA_NONE, XML_DEA_NONE, XML_DEA_NONE, false }, { NF_DATE_SYS_NNNNDMMMMYYYY, XML_DEA_LONG, XML_DEA_SHORT, XML_DEA_TEXTLONG, XML_DEA_LONG, XML_DEA_NONE, XML_DEA_NONE, XML_DEA_NONE, false }, { NF_DATETIME_SYS_DDMMYYYY_HHMM, XML_DEA_NONE, XML_DEA_ANY, XML_DEA_ANY, XML_DEA_LONG, XML_DEA_ANY, XML_DEA_ANY, XML_DEA_NONE, false }, { NF_DATETIME_SYSTEM_SHORT_HHMM, XML_DEA_NONE, XML_DEA_ANY, XML_DEA_ANY, XML_DEA_ANY, XML_DEA_ANY, XML_DEA_ANY, XML_DEA_NONE, true }, { NF_DATETIME_SYS_DDMMYYYY_HHMMSS, XML_DEA_NONE, XML_DEA_ANY, XML_DEA_ANY, XML_DEA_ANY, XML_DEA_ANY, XML_DEA_ANY, XML_DEA_ANY, false } }; // SvXMLNumImpData SvXMLNumImpData::SvXMLNumImpData( SvNumberFormatter* pFmt, const uno::Reference& rxContext ) : pFormatter(pFmt), m_xContext(rxContext) { SAL_WARN_IF( !rxContext.is(), "xmloff", "got no service manager" ); } sal_uInt32 SvXMLNumImpData::GetKeyForName( std::u16string_view rName ) { for (const auto& rObj : m_NameEntries) { if (rObj.aName == rName) return rObj.nKey; // found } return NUMBERFORMAT_ENTRY_NOT_FOUND; } void SvXMLNumImpData::AddKey( sal_uInt32 nKey, const OUString& rName, bool bRemoveAfterUse ) { if ( bRemoveAfterUse ) { // if there is already an entry for this key without the bRemoveAfterUse flag, // clear the flag for this entry, too for (const auto& rObj : m_NameEntries) { if (rObj.nKey == nKey && !rObj.bRemoveAfterUse) { bRemoveAfterUse = false; // clear flag for new entry break; } } } else { // call SetUsed to clear the bRemoveAfterUse flag for other entries for this key SetUsed( nKey ); } m_NameEntries.emplace_back(rName, nKey, bRemoveAfterUse); } void SvXMLNumImpData::SetUsed( sal_uInt32 nKey ) { for (auto& rObj : m_NameEntries) { if (rObj.nKey == nKey) { rObj.bRemoveAfterUse = false; // used -> don't remove // continue searching - there may be several entries for the same key // (with different names), the format must not be deleted if any one of // them is used } } } void SvXMLNumImpData::RemoveVolatileFormats() { // remove temporary (volatile) formats from NumberFormatter // called at the end of each import (styles and content), so volatile formats // from styles can't be used in content if ( !pFormatter ) return; for (const auto& rObj : m_NameEntries) { if (rObj.bRemoveAfterUse ) { const SvNumberformat* pFormat = pFormatter->GetEntry(rObj.nKey); if (pFormat && (pFormat->GetType() & SvNumFormatType::DEFINED)) pFormatter->DeleteEntry(rObj.nKey); } } } const LocaleDataWrapper& SvXMLNumImpData::GetLocaleData( LanguageType nLang ) { if ( !pLocaleData || pLocaleData->getLanguageTag() != LanguageTag(nLang) ) pLocaleData = std::make_unique( pFormatter ? pFormatter->GetComponentContext() : m_xContext, LanguageTag( nLang ) ); return *pLocaleData; } // SvXMLNumFmtMapContext SvXMLNumFmtMapContext::SvXMLNumFmtMapContext( SvXMLImport& rImport, sal_Int32 /*nElement*/, SvXMLNumFormatContext& rParentContext, const uno::Reference& xAttrList ) : SvXMLImportContext( rImport ), rParent( rParentContext ) { for( auto &aIter : sax_fastparser::castToFastAttributeList( xAttrList ) ) { OUString sValue = aIter.toString(); switch(aIter.getToken()) { case XML_ELEMENT(STYLE, XML_CONDITION): sCondition = sValue; break; case XML_ELEMENT(STYLE, XML_APPLY_STYLE_NAME): sName = sValue; break; default: XMLOFF_WARN_UNKNOWN("xmloff", aIter); } } } void SvXMLNumFmtMapContext::endFastElement(sal_Int32 ) { rParent.AddCondition( sCondition, sName ); } // SvXMLNumFmtPropContext SvXMLNumFmtPropContext::SvXMLNumFmtPropContext( SvXMLImport& rImport, sal_Int32 /*nElement*/, SvXMLNumFormatContext& rParentContext, const uno::Reference& xAttrList ) : SvXMLImportContext( rImport ), rParent( rParentContext ), m_nColor( 0 ), bColSet( false ) { for( auto &aIter : sax_fastparser::castToFastAttributeList( xAttrList ) ) { switch ( aIter.getToken()) { case XML_ELEMENT(FO, XML_COLOR): case XML_ELEMENT(FO_COMPAT, XML_COLOR): bColSet = ::sax::Converter::convertColor( m_nColor, aIter.toView() ); break; default: XMLOFF_WARN_UNKNOWN("xmloff", aIter); } } } void SvXMLNumFmtPropContext::endFastElement(sal_Int32 ) { if (bColSet) rParent.AddColor( m_nColor ); } // SvXMLNumFmtEmbeddedTextContext SvXMLNumFmtEmbeddedTextContext::SvXMLNumFmtEmbeddedTextContext( SvXMLImport& rImport, sal_Int32 /*nElement*/, SvXMLNumFmtElementContext& rParentContext, const uno::Reference& xAttrList ) : SvXMLImportContext( rImport ), rParent( rParentContext ), nTextPosition( 0 ) { sal_Int32 nAttrVal; for( auto &aIter : sax_fastparser::castToFastAttributeList( xAttrList ) ) { if ( aIter.getToken() == XML_ELEMENT(NUMBER, XML_POSITION) ) { if (::sax::Converter::convertNumber( nAttrVal, aIter.toView() )) nTextPosition = nAttrVal; } else if ( aIter.getToken() == XML_ELEMENT(LO_EXT, XML_BLANK_WIDTH_CHAR) || aIter.getToken() == XML_ELEMENT(NUMBER, XML_BLANK_WIDTH_CHAR) ) { aBlankWidthString = aIter.toString(); } else XMLOFF_WARN_UNKNOWN("xmloff", aIter); } } void SvXMLNumFmtEmbeddedTextContext::characters( const OUString& rChars ) { aContent.append( rChars ); } void SvXMLNumFmtEmbeddedTextContext::endFastElement(sal_Int32 ) { rParent.AddEmbeddedElement( nTextPosition, aContent.makeStringAndClear(), aBlankWidthString ); } static bool lcl_ValidChar( sal_Unicode cChar, const SvXMLNumFormatContext& rParent ) { SvXMLStylesTokens nFormatType = rParent.GetType(); // Treat space equal to non-breaking space separator. const sal_Unicode cNBSP = 0x00A0; sal_Unicode cTS; if ( ( nFormatType == SvXMLStylesTokens::NUMBER_STYLE || nFormatType == SvXMLStylesTokens::CURRENCY_STYLE || nFormatType == SvXMLStylesTokens::PERCENTAGE_STYLE ) && (cChar == (cTS = rParent.GetLocaleData().getNumThousandSep()[0]) || (cChar == ' ' && cTS == cNBSP)) ) { // #i22394# Extra occurrences of thousands separator must be quoted, so they // aren't mis-interpreted as display-factor. // This must be limited to the format types that can contain a number element, // because the same character can be a date separator that should not be quoted // in date formats. return false; // force quotes } // see ImpSvNumberformatScan::Next_Symbol // All format types except BOOLEAN may contain minus sign or delimiter. if ( cChar == '-' ) return nFormatType != SvXMLStylesTokens::BOOLEAN_STYLE; if ( ( cChar == ' ' || cChar == '/' || cChar == '.' || cChar == ',' || cChar == ':' || cChar == '\'' ) && ( nFormatType == SvXMLStylesTokens::CURRENCY_STYLE || nFormatType == SvXMLStylesTokens::DATE_STYLE || nFormatType == SvXMLStylesTokens::TIME_STYLE ) ) // other formats do not require delimiter tdf#97837 return true; // percent sign must be used without quotes for percentage styles only if ( nFormatType == SvXMLStylesTokens::PERCENTAGE_STYLE && cChar == '%' ) return true; // don't put quotes around single parentheses (often used for negative numbers) if ( ( nFormatType == SvXMLStylesTokens::NUMBER_STYLE || nFormatType == SvXMLStylesTokens::CURRENCY_STYLE || nFormatType == SvXMLStylesTokens::PERCENTAGE_STYLE ) && ( cChar == '(' || cChar == ')' ) ) return true; return false; } static void lcl_EnquoteIfNecessary( OUStringBuffer& rContent, const SvXMLNumFormatContext& rParent ) { bool bQuote = true; sal_Int32 nLength = rContent.getLength(); const SvXMLStylesTokens nFormatType = rParent.GetType(); if (nFormatType != SvXMLStylesTokens::BOOLEAN_STYLE && ((nLength == 1 && lcl_ValidChar( rContent[0], rParent)) || (nLength == 2 && ((rContent[0] == ' ' && rContent[1] == '-') || (rContent[1] == ' ' && lcl_ValidChar( rContent[0], rParent)))))) { // Don't quote single separator characters like space or percent, // or separator characters followed by space (used in date formats). // Or space followed by minus (used in currency formats) that would // lead to almost duplicated formats with built-in formats just with // the difference of quotes. bQuote = false; } else if ( nFormatType == SvXMLStylesTokens::PERCENTAGE_STYLE && nLength > 1 ) { // the percent character in percentage styles must be left out of quoting // (one occurrence is enough even if there are several percent characters in the string) sal_Int32 nPos = rContent.indexOf( '%' ); if ( nPos >= 0 ) { if ( nPos + 1 < nLength ) { if ( nPos + 2 == nLength && lcl_ValidChar( rContent[nPos + 1], rParent ) ) { // single character that doesn't need quoting } else { // quote text behind percent character rContent.insert( nPos + 1, '"' ); rContent.append( '"' ); } } if ( nPos > 0 ) { if ( nPos == 1 && lcl_ValidChar( rContent[0], rParent ) ) { // single character that doesn't need quoting } else { // quote text before percent character rContent.insert( nPos, '"' ); rContent.insert( 0, '"' ); } } bQuote = false; } // else: normal quoting (below) } if ( !bQuote ) return; // #i55469# quotes in the string itself have to be escaped bool bEscape = ( rContent.indexOf( '"' ) >= 0 ); if ( bEscape ) { // A quote is turned into "\"" - a quote to end quoted text, an escaped quote, // and a quote to resume quoting. OUString aInsert( "\"\\\"" ); sal_Int32 nPos = 0; while ( nPos < rContent.getLength() ) { if ( rContent[nPos] == '"' ) { rContent.insert( nPos, aInsert ); nPos += aInsert.getLength(); } ++nPos; } } // quote string literals rContent.insert( 0, '"' ); rContent.append( '"' ); // remove redundant double quotes at start or end if ( !bEscape ) return; if ( rContent.getLength() > 2 && rContent[0] == '"' && rContent[1] == '"' ) { rContent.remove(0, 2); } sal_Int32 nLen = rContent.getLength(); if ( nLen > 2 && rContent[nLen - 1] == '"' && rContent[nLen - 2] == '"' ) { rContent.truncate(nLen - 2); } } // SvXMLNumFmtElementContext SvXMLNumFmtElementContext::SvXMLNumFmtElementContext( SvXMLImport& rImport, sal_Int32 /*nElement*/, SvXMLNumFormatContext& rParentContext, SvXMLStyleTokens nNewType, const uno::Reference& xAttrList ) : SvXMLImportContext( rImport ), rParent( rParentContext ), nType( nNewType ), nElementLang( LANGUAGE_SYSTEM ), bLong( false ), bTextual( false ) { LanguageTagODF aLanguageTagODF; sal_Int32 nAttrVal; bool bAttrBool(false); bool bVarDecimals = false; bool bIsMaxDenominator = false; double fAttrDouble; for( auto &aIter : sax_fastparser::castToFastAttributeList( xAttrList ) ) { switch (aIter.getToken()) { case XML_ELEMENT(NUMBER, XML_DECIMAL_PLACES): if (::sax::Converter::convertNumber( nAttrVal, aIter.toView(), 0 )) { // fdo#58539 & gnome#627420: limit number of digits during import aNumInfo.nDecimals = std::min(nAttrVal, NF_MAX_FORMAT_SYMBOLS); } break; case XML_ELEMENT(LO_EXT, XML_MIN_DECIMAL_PLACES): case XML_ELEMENT(NUMBER, XML_MIN_DECIMAL_PLACES): if (::sax::Converter::convertNumber( nAttrVal, aIter.toView(), 0 )) aNumInfo.nMinDecimalDigits = nAttrVal; break; case XML_ELEMENT(NUMBER, XML_MIN_INTEGER_DIGITS): if (::sax::Converter::convertNumber( nAttrVal, aIter.toView(), 0 )) aNumInfo.nInteger = nAttrVal; break; case XML_ELEMENT(LO_EXT, XML_MAX_BLANK_INTEGER_DIGITS): case XML_ELEMENT(NUMBER, XML_MAX_BLANK_INTEGER_DIGITS): if (::sax::Converter::convertNumber( nAttrVal, aIter.toView(), 0 )) aNumInfo.nBlankInteger = nAttrVal; break; case XML_ELEMENT(NUMBER, XML_GROUPING): if (::sax::Converter::convertBool( bAttrBool, aIter.toView() )) aNumInfo.bGrouping = bAttrBool; break; case XML_ELEMENT(NUMBER, XML_DISPLAY_FACTOR): if (::sax::Converter::convertDouble( fAttrDouble, aIter.toView() )) aNumInfo.fDisplayFactor = fAttrDouble; break; case XML_ELEMENT(NUMBER, XML_DECIMAL_REPLACEMENT): if ( aIter.toView() == " " ) { aNumInfo.bDecAlign = true; // space replacement for "?" bVarDecimals = true; } else if ( aIter.isEmpty() ) bVarDecimals = true; // empty replacement string: variable decimals else // all other strings aNumInfo.bDecReplace = true; // decimal replacement with dashes break; case XML_ELEMENT(NUMBER, XML_MIN_EXPONENT_DIGITS): if (::sax::Converter::convertNumber( nAttrVal, aIter.toView(), 0 )) aNumInfo.nExpDigits = std::min(nAttrVal, NF_MAX_FORMAT_SYMBOLS); break; case XML_ELEMENT(NUMBER, XML_BLANK_EXPONENT_DIGITS): case XML_ELEMENT(LO_EXT, XML_BLANK_EXPONENT_DIGITS): if (::sax::Converter::convertNumber( nAttrVal, aIter.toView(), 0 )) aNumInfo.nBlankExp = std::min(nAttrVal, NF_MAX_FORMAT_SYMBOLS); break; case XML_ELEMENT(NUMBER, XML_EXPONENT_INTERVAL): case XML_ELEMENT(LO_EXT, XML_EXPONENT_INTERVAL): if (::sax::Converter::convertNumber( nAttrVal, aIter.toView(), 0 )) aNumInfo.nExpInterval = nAttrVal; break; case XML_ELEMENT(NUMBER, XML_FORCED_EXPONENT_SIGN): case XML_ELEMENT(LO_EXT, XML_FORCED_EXPONENT_SIGN): if (::sax::Converter::convertBool( bAttrBool, aIter.toView() )) aNumInfo.bExpSign = bAttrBool; break; case XML_ELEMENT(NUMBER, XML_EXPONENT_LOWERCASE): case XML_ELEMENT(LO_EXT, XML_EXPONENT_LOWERCASE): if (::sax::Converter::convertBool( bAttrBool, aIter.toView() )) aNumInfo.bExponentLowercase = bAttrBool; break; case XML_ELEMENT(NUMBER, XML_MIN_NUMERATOR_DIGITS): if (::sax::Converter::convertNumber( nAttrVal, aIter.toView(), 0 )) aNumInfo.nMinNumerDigits = nAttrVal; break; case XML_ELEMENT(NUMBER, XML_MIN_DENOMINATOR_DIGITS): if (::sax::Converter::convertNumber( nAttrVal, aIter.toView(), 0 )) aNumInfo.nMinDenomDigits = nAttrVal; break; case XML_ELEMENT(LO_EXT, XML_MAX_NUMERATOR_DIGITS): if (::sax::Converter::convertNumber( nAttrVal, aIter.toView(), 1 )) // at least one '#' aNumInfo.nMaxNumerDigits = nAttrVal; break; case XML_ELEMENT(NUMBER, XML_DENOMINATOR_VALUE): if (::sax::Converter::convertNumber( nAttrVal, aIter.toView(), 1 )) // 0 is not valid { aNumInfo.nFracDenominator = nAttrVal; bIsMaxDenominator = false; } break; case XML_ELEMENT(NUMBER, XML_MAX_DENOMINATOR_VALUE): // part of ODF 1.3 case XML_ELEMENT(LO_EXT, XML_MAX_DENOMINATOR_VALUE): if (::sax::Converter::convertNumber( nAttrVal, aIter.toView(), 1 ) && aNumInfo.nFracDenominator <= 0) { // if denominator value not yet defined aNumInfo.nFracDenominator = nAttrVal; bIsMaxDenominator = true; } break; case XML_ELEMENT(LO_EXT, XML_ZEROS_NUMERATOR_DIGITS): case XML_ELEMENT(NUMBER, XML_ZEROS_NUMERATOR_DIGITS): if (::sax::Converter::convertNumber( nAttrVal, aIter.toView(), 0 )) aNumInfo.nZerosNumerDigits = nAttrVal; break; case XML_ELEMENT(NUMBER, XML_ZEROS_DENOMINATOR_DIGITS): case XML_ELEMENT(LO_EXT, XML_ZEROS_DENOMINATOR_DIGITS): if (::sax::Converter::convertNumber( nAttrVal, aIter.toView(), 0 )) aNumInfo.nZerosDenomDigits = nAttrVal; break; case XML_ELEMENT(NUMBER, XML_INTEGER_FRACTION_DELIMITER): case XML_ELEMENT(LO_EXT, XML_INTEGER_FRACTION_DELIMITER): aNumInfo.aIntegerFractionDelimiter = aIter.toString(); break; case XML_ELEMENT(NUMBER, XML_RFC_LANGUAGE_TAG): aLanguageTagODF.maRfcLanguageTag = aIter.toString(); break; case XML_ELEMENT(NUMBER, XML_LANGUAGE): aLanguageTagODF.maLanguage = aIter.toString(); break; case XML_ELEMENT(NUMBER, XML_SCRIPT): aLanguageTagODF.maScript = aIter.toString(); break; case XML_ELEMENT(NUMBER, XML_COUNTRY): aLanguageTagODF.maCountry = aIter.toString(); break; case XML_ELEMENT(NUMBER, XML_STYLE): SvXMLUnitConverter::convertEnum( bLong, aIter.toView(), aStyleValueMap ); break; case XML_ELEMENT(NUMBER, XML_TEXTUAL): if (::sax::Converter::convertBool( bAttrBool, aIter.toView() )) bTextual = bAttrBool; break; case XML_ELEMENT(NUMBER, XML_CALENDAR): sCalendar = aIter.toString(); break; case XML_ELEMENT(NUMBER, XML_BLANK_WIDTH_CHAR): case XML_ELEMENT(LO_EXT, XML_BLANK_WIDTH_CHAR): sBlankWidthString = aIter.toString(); break; default: XMLOFF_WARN_UNKNOWN("xmloff", aIter); } } if ( aNumInfo.nBlankInteger > aNumInfo.nInteger ) aNumInfo.nInteger = aNumInfo.nBlankInteger; if ( aNumInfo.nMinDecimalDigits == -1) { if ( bVarDecimals || aNumInfo.bDecReplace ) aNumInfo.nMinDecimalDigits = 0; else aNumInfo.nMinDecimalDigits = aNumInfo.nDecimals; } if ( aNumInfo.nExpDigits > 0 && aNumInfo.nBlankExp >= aNumInfo.nExpDigits ) aNumInfo.nBlankExp = aNumInfo.nExpDigits - 1; // at least one '0' in exponent if ( aNumInfo.nZerosDenomDigits > 0 ) { // nMin = count of '0' and '?' if ( aNumInfo.nMinDenomDigits < aNumInfo.nZerosDenomDigits ) aNumInfo.nMinDenomDigits = aNumInfo.nZerosDenomDigits; } else aNumInfo.nZerosDenomDigits = 0; if ( aNumInfo.nMinDenomDigits >= 0 ) if ( aNumInfo.nMaxDenomDigits < aNumInfo.nMinDenomDigits ) aNumInfo.nMaxDenomDigits = ( aNumInfo.nMinDenomDigits ? aNumInfo.nMinDenomDigits : 1 ); if ( aNumInfo.nZerosNumerDigits > 0 ) { if ( aNumInfo.nMinNumerDigits < aNumInfo.nZerosNumerDigits ) aNumInfo.nMinNumerDigits = aNumInfo.nZerosNumerDigits; } else aNumInfo.nZerosNumerDigits = 0; if ( aNumInfo.nMinNumerDigits >= 0 ) if ( aNumInfo.nMaxNumerDigits < aNumInfo.nMinNumerDigits ) aNumInfo.nMaxNumerDigits = ( aNumInfo.nMinNumerDigits ? aNumInfo.nMinNumerDigits : 1 ); if ( bIsMaxDenominator && aNumInfo.nFracDenominator > 0 ) { aNumInfo.nMaxDenomDigits = floor( log10( aNumInfo.nFracDenominator ) ) + 1; aNumInfo.nFracDenominator = -1; // Max denominator value only gives number of digits at denominator } if ( aNumInfo.nMaxDenomDigits > 0 ) { if ( aNumInfo.nMinDenomDigits < 0 ) aNumInfo.nMinDenomDigits = 0; else if ( aNumInfo.nMinDenomDigits > aNumInfo.nMaxDenomDigits ) aNumInfo.nMinDenomDigits = aNumInfo.nMaxDenomDigits; } if ( !aLanguageTagODF.isEmpty() ) { nElementLang = aLanguageTagODF.getLanguageTag().getLanguageType( false); if ( nElementLang == LANGUAGE_DONTKNOW ) nElementLang = LANGUAGE_SYSTEM; //! error handling for unknown locales? } if ( aNumInfo.aIntegerFractionDelimiter.isEmpty() ) aNumInfo.aIntegerFractionDelimiter = " "; } css::uno::Reference< css::xml::sax::XFastContextHandler > SvXMLNumFmtElementContext::createFastChildContext( sal_Int32 nElement, const css::uno::Reference< css::xml::sax::XFastAttributeList >& xAttrList ) { // only number:number and number:scientific-number supports number:embedded-text child element if ( ( nType == SvXMLStyleTokens::Number || nType == SvXMLStyleTokens::ScientificNumber ) && nElement == XML_ELEMENT(NUMBER, XML_EMBEDDED_TEXT) ) { return new SvXMLNumFmtEmbeddedTextContext( GetImport(), nElement, *this, xAttrList ); } else XMLOFF_WARN_UNKNOWN_ELEMENT("xmloff", nElement); return nullptr; } void SvXMLNumFmtElementContext::characters( const OUString& rChars ) { aContent.append( rChars ); } namespace { void lcl_InsertBlankWidthChars( std::u16string_view rBlankWidthString, OUStringBuffer& rContent ) { sal_Int32 nShiftPosition = 1; // rContent starts with a quote const size_t nLenBlank = rBlankWidthString.size(); for ( size_t i = 0 ; i < nLenBlank ; i++ ) { sal_Unicode nChar = rBlankWidthString[ i ]; OUString aBlanks; SvNumberformat::InsertBlanks( aBlanks, 0, nChar ); sal_Int32 nPositionContent = 0; if ( ++i < nLenBlank ) { sal_Int32 nNext = rBlankWidthString.find( '_', i ); if ( static_cast( i ) < nNext ) { nPositionContent = o3tl::toInt32( rBlankWidthString.substr( i, nNext - i ) ); i = nNext; } else nPositionContent = o3tl::toInt32( rBlankWidthString.substr( i ) ); } nPositionContent += nShiftPosition; if ( nPositionContent >= 0 ) { rContent.remove( nPositionContent, aBlanks.getLength() ); if ( nPositionContent >= 1 && rContent[ nPositionContent-1 ] == '\"' ) { nPositionContent--; rContent.insert( nPositionContent, nChar ); rContent.insert( nPositionContent, '_' ); } else { rContent.insert( nPositionContent, '\"' ); rContent.insert( nPositionContent, nChar ); rContent.insert( nPositionContent, "\"_" ); nShiftPosition += 2; } // rContent length was modified: remove blanks, add "_x" nShiftPosition += 2 - aBlanks.getLength(); } } // remove empty string at the end of rContent if ( std::u16string_view( rContent ).substr( rContent.getLength() - 2 ) == u"\"\"" ) { sal_Int32 nLen = rContent.getLength(); if ( nLen >= 3 && rContent[ nLen-3 ] != '\\' ) rContent.truncate( nLen - 2 ); } } } void SvXMLNumFmtElementContext::AddEmbeddedElement( sal_Int32 nFormatPos, std::u16string_view rContentEmbedded, std::u16string_view rBlankWidthString ) { if ( rContentEmbedded.empty() ) return; OUStringBuffer aContentEmbedded( rContentEmbedded ); // #107805# always quote embedded strings - even space would otherwise // be recognized as thousands separator in French. aContentEmbedded.insert( 0, '"' ); aContentEmbedded.append( '"' ); if ( !rBlankWidthString.empty() ) lcl_InsertBlankWidthChars( rBlankWidthString, aContentEmbedded ); auto iterPair = aNumInfo.m_EmbeddedElements.emplace( nFormatPos, aContentEmbedded.toString() ); if (!iterPair.second) { // there's already an element at this position - append text to existing element if ( iterPair.first->second.endsWith( "\"" ) && aContentEmbedded[ 0 ] == '"' ) { // remove double quote iterPair.first->second = OUString::Concat( iterPair.first->second.subView( 0, iterPair.first->second.getLength() - 1 ) ) + aContentEmbedded.subView( 1, aContentEmbedded.getLength() - 1 ); } else iterPair.first->second += aContentEmbedded; } } void SvXMLNumFmtElementContext::endFastElement(sal_Int32 ) { bool bEffLong = bLong; switch (nType) { case SvXMLStyleTokens::Text: if ( rParent.HasLongDoW() && std::u16string_view(aContent) == rParent.GetLocaleData().getLongDateDayOfWeekSep() ) { // skip separator constant after long day of week // (NF_KEY_NNNN contains the separator) if ( rParent.ReplaceNfKeyword( NF_KEY_NNN, NF_KEY_NNNN ) ) { aContent.truncate(); } rParent.SetHasLongDoW( false ); // only once } if ( !aContent.isEmpty() ) { lcl_EnquoteIfNecessary( aContent, rParent ); if ( !sBlankWidthString.isEmpty() ) { lcl_InsertBlankWidthChars( sBlankWidthString, aContent ); sBlankWidthString = ""; } rParent.AddToCode( aContent ); aContent.setLength(0); } else { // Quoted empty text may be significant to separate. aContent.append("\"\""); rParent.AddToCode( aContent ); aContent.setLength(0); rParent.SetHasTrailingEmptyText(true); // *after* AddToCode() } break; case SvXMLStyleTokens::Number: rParent.AddNumber( aNumInfo ); break; case SvXMLStyleTokens::CurrencySymbol: rParent.AddCurrency( aContent.makeStringAndClear(), nElementLang ); break; case SvXMLStyleTokens::TextContent: rParent.AddToCode( '@'); break; case SvXMLStyleTokens::FillCharacter: if ( !aContent.isEmpty() ) { rParent.AddToCode( '*' ); rParent.AddToCode( aContent[0] ); } break; case SvXMLStyleTokens::Boolean: rParent.AddNfKeyword( NF_KEY_BOOLEAN ); break; case SvXMLStyleTokens::Day: rParent.UpdateCalendar( sCalendar ); //! I18N doesn't provide SYSTEM or extended date information yet rParent.AddNfKeyword( sal::static_int_cast< sal_uInt16 >( bEffLong ? NF_KEY_DD : NF_KEY_D ) ); break; case SvXMLStyleTokens::Month: rParent.UpdateCalendar( sCalendar ); //! I18N doesn't provide SYSTEM or extended date information yet rParent.AddNfKeyword( sal::static_int_cast< sal_uInt16 >( bTextual ? ( bEffLong ? NF_KEY_MMMM : NF_KEY_MMM ) : ( bEffLong ? NF_KEY_MM : NF_KEY_M ) ) ); break; case SvXMLStyleTokens::Year: //! I18N doesn't provide SYSTEM or extended date information yet { // Y after G (era) is replaced by E for a secondary calendar. // Do not replace for default calendar. // Also replace Y by E if we're switching to the secondary // calendar of a locale if it is known to implicitly use E. rParent.UpdateCalendar( sCalendar); const SvXMLNumFormatContext::ImplicitCalendar eCal = rParent.GetImplicitCalendarState(); if (eCal == SvXMLNumFormatContext::ImplicitCalendar::SECONDARY || eCal == SvXMLNumFormatContext::ImplicitCalendar::SECONDARY_FROM_OTHER) { rParent.AddNfKeyword( sal::static_int_cast< sal_uInt16 >( bEffLong ? NF_KEY_EEC : NF_KEY_EC ) ); } else { rParent.AddNfKeyword( sal::static_int_cast< sal_uInt16 >( bEffLong ? NF_KEY_YYYY : NF_KEY_YY ) ); } } break; case SvXMLStyleTokens::Era: rParent.UpdateCalendar( sCalendar ); //! I18N doesn't provide SYSTEM or extended date information yet rParent.AddNfKeyword( sal::static_int_cast< sal_uInt16 >( bEffLong ? NF_KEY_GGG : NF_KEY_G ) ); // HasEra flag is set break; case SvXMLStyleTokens::DayOfWeek: //! I18N doesn't provide SYSTEM or extended date information yet { // Implicit secondary calendar uses A keyword, default and // explicit calendar N keyword. rParent.UpdateCalendar( sCalendar); const SvXMLNumFormatContext::ImplicitCalendar eCal = rParent.GetImplicitCalendarState(); if (eCal == SvXMLNumFormatContext::ImplicitCalendar::SECONDARY || eCal == SvXMLNumFormatContext::ImplicitCalendar::SECONDARY_FROM_OTHER) { rParent.AddNfKeyword( sal::static_int_cast< sal_uInt16 >( bEffLong ? NF_KEY_AAAA : NF_KEY_AAA ) ); } else { rParent.AddNfKeyword( sal::static_int_cast< sal_uInt16 >( bEffLong ? NF_KEY_NNNN : NF_KEY_NN ) ); } } break; case SvXMLStyleTokens::WeekOfYear: rParent.UpdateCalendar( sCalendar ); rParent.AddNfKeyword( NF_KEY_WW ); break; case SvXMLStyleTokens::Quarter: rParent.UpdateCalendar( sCalendar ); rParent.AddNfKeyword( sal::static_int_cast< sal_uInt16 >( bEffLong ? NF_KEY_QQ : NF_KEY_Q ) ); break; case SvXMLStyleTokens::Hours: rParent.AddNfKeyword( sal::static_int_cast< sal_uInt16 >( bEffLong ? NF_KEY_HH : NF_KEY_H ) ); break; case SvXMLStyleTokens::AmPm: //! short/long? rParent.AddNfKeyword( NF_KEY_AMPM ); break; case SvXMLStyleTokens::Minutes: rParent.AddNfKeyword( sal::static_int_cast< sal_uInt16 >( bEffLong ? NF_KEY_MMI : NF_KEY_MI ) ); break; case SvXMLStyleTokens::Seconds: rParent.AddNfKeyword( sal::static_int_cast< sal_uInt16 >( bEffLong ? NF_KEY_SS : NF_KEY_S ) ); if ( aNumInfo.nDecimals > 0 ) { // manually add the decimal places rParent.AddToCode(rParent.GetLocaleData().getNumDecimalSep()); for (sal_Int32 i=0; i= 0 ) { // add integer part only if min-integer-digits attribute is there aNumInfo.nDecimals = 0; rParent.AddNumber( aNumInfo ); // number without decimals OUStringBuffer sIntegerFractionDelimiter(aNumInfo.aIntegerFractionDelimiter); lcl_EnquoteIfNecessary( sIntegerFractionDelimiter, rParent ); rParent.AddToCode( sIntegerFractionDelimiter ); // default is ' ' } //! build string and add at once sal_Int32 i; for (i=aNumInfo.nMaxNumerDigits; i > 0; i--) { if ( i > aNumInfo.nMinNumerDigits ) rParent.AddToCode( '#' ); else if ( i > aNumInfo.nZerosNumerDigits ) rParent.AddToCode( '?' ); else rParent.AddToCode( '0' ); } rParent.AddToCode( '/' ); if ( aNumInfo.nFracDenominator > 0 ) { rParent.AddToCode( OUString::number( aNumInfo.nFracDenominator ) ); } else { for (i=aNumInfo.nMaxDenomDigits; i > 0 ; i--) { if ( i > aNumInfo.nMinDenomDigits ) rParent.AddToCode( '#' ); else if ( i > aNumInfo.nZerosDenomDigits ) rParent.AddToCode( '?' ); else rParent.AddToCode( '0' ); } } } break; case SvXMLStyleTokens::ScientificNumber: { // exponential interval for engineering notation if( !aNumInfo.bGrouping && aNumInfo.nExpInterval > aNumInfo.nInteger ) { for (sal_Int32 i=aNumInfo.nInteger; i(rEntry.eFormat); } } return NF_INDEX_TABLE_ENTRIES; // invalid } // SvXMLNumFormatContext SvXMLNumFormatContext::SvXMLNumFormatContext( SvXMLImport& rImport, sal_Int32 /*nElement*/, SvXMLNumImpData* pNewData, SvXMLStylesTokens nNewType, const uno::Reference& xAttrList, SvXMLStylesContext& rStyles ) : SvXMLStyleContext( rImport ), m_pData( pNewData ), m_pStyles( &rStyles ), m_nType( nNewType ), m_nKey(-1), m_eImplicitCalendar(ImplicitCalendar::DEFAULT), m_nFormatLang( LANGUAGE_SYSTEM ), m_bAutoOrder( false ), m_bFromSystem( false ), m_bTruncate( true ), m_bAutoDec( false ), m_bAutoInt( false ), m_bHasExtraText( false ), m_bHasTrailingEmptyText( false ), m_bHasLongDoW( false ), m_bHasDateTime( false ), m_bRemoveAfterUse( false ), m_eDateDOW( XML_DEA_NONE ), m_eDateDay( XML_DEA_NONE ), m_eDateMonth( XML_DEA_NONE ), m_eDateYear( XML_DEA_NONE ), m_eDateHours( XML_DEA_NONE ), m_eDateMins( XML_DEA_NONE ), m_eDateSecs( XML_DEA_NONE ), m_bDateNoDefault( false ) { LanguageTagODF aLanguageTagODF; css::i18n::NativeNumberXmlAttributes aNatNumAttr; OUString aSpellout; bool bAttrBool(false); for( auto &aIter : sax_fastparser::castToFastAttributeList( xAttrList ) ) { switch (aIter.getToken()) { // attributes for a style case XML_ELEMENT(STYLE, XML_NAME): break; case XML_ELEMENT(NUMBER, XML_RFC_LANGUAGE_TAG): aLanguageTagODF.maRfcLanguageTag = aIter.toString(); break; case XML_ELEMENT(NUMBER, XML_LANGUAGE): aLanguageTagODF.maLanguage = aIter.toString(); break; case XML_ELEMENT(NUMBER, XML_SCRIPT): aLanguageTagODF.maScript = aIter.toString(); break; case XML_ELEMENT(NUMBER, XML_COUNTRY): aLanguageTagODF.maCountry = aIter.toString(); break; case XML_ELEMENT(NUMBER, XML_TITLE): m_sFormatTitle = aIter.toString(); break; case XML_ELEMENT(NUMBER, XML_AUTOMATIC_ORDER): if (::sax::Converter::convertBool( bAttrBool, aIter.toView() )) m_bAutoOrder = bAttrBool; break; case XML_ELEMENT(NUMBER, XML_FORMAT_SOURCE): SvXMLUnitConverter::convertEnum( m_bFromSystem, aIter.toView(), aFormatSourceMap ); break; case XML_ELEMENT(NUMBER, XML_TRUNCATE_ON_OVERFLOW): if (::sax::Converter::convertBool( bAttrBool, aIter.toView() )) m_bTruncate = bAttrBool; break; case XML_ELEMENT(STYLE, XML_VOLATILE): // volatile formats can be removed after importing // if not used in other styles if (::sax::Converter::convertBool( bAttrBool, aIter.toView() )) m_bRemoveAfterUse = bAttrBool; break; case XML_ELEMENT(NUMBER, XML_TRANSLITERATION_FORMAT): aNatNumAttr.Format = aIter.toString(); break; case XML_ELEMENT(LO_EXT, XML_TRANSLITERATION_SPELLOUT): case XML_ELEMENT(NUMBER, XML_TRANSLITERATION_SPELLOUT): aSpellout = aIter.toString(); break; case XML_ELEMENT(NUMBER, XML_TRANSLITERATION_LANGUAGE): aNatNumAttr.Locale.Language = aIter.toString(); break; case XML_ELEMENT(NUMBER, XML_TRANSLITERATION_COUNTRY): aNatNumAttr.Locale.Country = aIter.toString(); break; case XML_ELEMENT(NUMBER, XML_TRANSLITERATION_STYLE): aNatNumAttr.Style = aIter.toString(); break; default: XMLOFF_WARN_UNKNOWN("xmloff", aIter); } } if (!aLanguageTagODF.isEmpty()) { m_nFormatLang = aLanguageTagODF.getLanguageTag().getLanguageType( false); if ( m_nFormatLang == LANGUAGE_DONTKNOW ) m_nFormatLang = LANGUAGE_SYSTEM; //! error handling for unknown locales? } if (aNatNumAttr.Format.isEmpty() && aSpellout.isEmpty()) return; LanguageTag aLanguageTag( OUString(), aNatNumAttr.Locale.Language, std::u16string_view(), aNatNumAttr.Locale.Country); aNatNumAttr.Locale = aLanguageTag.getLocale( false); // NatNum12 spell out formula (cardinal, ordinal, ordinal-feminine etc.) if ( !aSpellout.isEmpty() ) { m_aFormatCode.append( "[NatNum12 " ); m_aFormatCode.append( aSpellout ); } else { SvNumberFormatter* pFormatter = m_pData->GetNumberFormatter(); if ( !pFormatter ) return; sal_Int32 nNatNum = pFormatter->GetNatNum()->convertFromXmlAttributes( aNatNumAttr ); m_aFormatCode.append( "[NatNum" ); m_aFormatCode.append( nNatNum ); } LanguageType eLang = aLanguageTag.getLanguageType( false ); if ( eLang == LANGUAGE_DONTKNOW ) eLang = LANGUAGE_SYSTEM; //! error handling for unknown locales? if ( eLang != m_nFormatLang && eLang != LANGUAGE_SYSTEM ) { m_aFormatCode.append( "][$-" ); // language code in upper hex: m_aFormatCode.append(OUString::number(static_cast(eLang), 16).toAsciiUpperCase()); } m_aFormatCode.append( ']' ); } SvXMLNumFormatContext::SvXMLNumFormatContext( SvXMLImport& rImport, const OUString& rName, const uno::Reference& /*xAttrList*/, const sal_Int32 nTempKey, LanguageType nLang, SvXMLStylesContext& rStyles ) : SvXMLStyleContext( rImport, XmlStyleFamily::DATA_STYLE ), m_pData( nullptr ), m_pStyles( &rStyles ), m_nType( SvXMLStylesTokens::NUMBER_STYLE ), m_nKey(nTempKey), m_eImplicitCalendar(ImplicitCalendar::DEFAULT), m_nFormatLang( nLang ), m_bAutoOrder( false ), m_bFromSystem( false ), m_bTruncate( true ), m_bAutoDec( false ), m_bAutoInt( false ), m_bHasExtraText( false ), m_bHasTrailingEmptyText( false ), m_bHasLongDoW( false ), m_bHasDateTime( false ), m_bRemoveAfterUse( false ), m_eDateDOW( XML_DEA_NONE ), m_eDateDay( XML_DEA_NONE ), m_eDateMonth( XML_DEA_NONE ), m_eDateYear( XML_DEA_NONE ), m_eDateHours( XML_DEA_NONE ), m_eDateMins( XML_DEA_NONE ), m_eDateSecs( XML_DEA_NONE ), m_bDateNoDefault( false ) { SetAttribute(XML_ELEMENT(STYLE, XML_NAME), rName); } SvXMLNumFormatContext::~SvXMLNumFormatContext() { } css::uno::Reference< css::xml::sax::XFastContextHandler > SvXMLNumFormatContext::createFastChildContext( sal_Int32 nElement, const css::uno::Reference< css::xml::sax::XFastAttributeList >& xAttrList ) { SvXMLImportContext* pContext = nullptr; switch (nElement) { case XML_ELEMENT(LO_EXT, XML_TEXT): case XML_ELEMENT(NUMBER, XML_TEXT): pContext = new SvXMLNumFmtElementContext( GetImport(), nElement, *this, SvXMLStyleTokens::Text, xAttrList ); break; case XML_ELEMENT(LO_EXT, XML_FILL_CHARACTER): case XML_ELEMENT(NUMBER, XML_FILL_CHARACTER): pContext = new SvXMLNumFmtElementContext( GetImport(), nElement, *this, SvXMLStyleTokens::FillCharacter, xAttrList ); break; case XML_ELEMENT(NUMBER, XML_NUMBER): pContext = new SvXMLNumFmtElementContext( GetImport(), nElement, *this, SvXMLStyleTokens::Number, xAttrList ); break; case XML_ELEMENT(NUMBER, XML_SCIENTIFIC_NUMBER): pContext = new SvXMLNumFmtElementContext( GetImport(), nElement, *this, SvXMLStyleTokens::ScientificNumber, xAttrList ); break; case XML_ELEMENT(NUMBER, XML_FRACTION): pContext = new SvXMLNumFmtElementContext( GetImport(), nElement, *this, SvXMLStyleTokens::Fraction, xAttrList ); break; case XML_ELEMENT(NUMBER, XML_CURRENCY_SYMBOL): pContext = new SvXMLNumFmtElementContext( GetImport(), nElement, *this, SvXMLStyleTokens::CurrencySymbol, xAttrList ); break; case XML_ELEMENT(NUMBER, XML_DAY): pContext = new SvXMLNumFmtElementContext( GetImport(), nElement, *this, SvXMLStyleTokens::Day, xAttrList ); break; case XML_ELEMENT(NUMBER, XML_MONTH): pContext = new SvXMLNumFmtElementContext( GetImport(), nElement, *this, SvXMLStyleTokens::Month, xAttrList ); break; case XML_ELEMENT(NUMBER, XML_YEAR): pContext = new SvXMLNumFmtElementContext( GetImport(), nElement, *this, SvXMLStyleTokens::Year, xAttrList ); break; case XML_ELEMENT(NUMBER, XML_ERA): pContext = new SvXMLNumFmtElementContext( GetImport(), nElement, *this, SvXMLStyleTokens::Era, xAttrList ); break; case XML_ELEMENT(NUMBER, XML_DAY_OF_WEEK): pContext = new SvXMLNumFmtElementContext( GetImport(), nElement, *this, SvXMLStyleTokens::DayOfWeek, xAttrList ); break; case XML_ELEMENT(NUMBER, XML_WEEK_OF_YEAR): pContext = new SvXMLNumFmtElementContext( GetImport(), nElement, *this, SvXMLStyleTokens::WeekOfYear, xAttrList ); break; case XML_ELEMENT(NUMBER, XML_QUARTER): pContext = new SvXMLNumFmtElementContext( GetImport(), nElement, *this, SvXMLStyleTokens::Quarter, xAttrList ); break; case XML_ELEMENT(NUMBER, XML_HOURS): pContext = new SvXMLNumFmtElementContext( GetImport(), nElement, *this, SvXMLStyleTokens::Hours, xAttrList ); break; case XML_ELEMENT(NUMBER, XML_AM_PM): pContext = new SvXMLNumFmtElementContext( GetImport(), nElement, *this, SvXMLStyleTokens::AmPm, xAttrList ); break; case XML_ELEMENT(NUMBER, XML_MINUTES): pContext = new SvXMLNumFmtElementContext( GetImport(), nElement, *this, SvXMLStyleTokens::Minutes, xAttrList ); break; case XML_ELEMENT(NUMBER, XML_SECONDS): pContext = new SvXMLNumFmtElementContext( GetImport(), nElement, *this, SvXMLStyleTokens::Seconds, xAttrList ); break; case XML_ELEMENT(NUMBER, XML_BOOLEAN): pContext = new SvXMLNumFmtElementContext( GetImport(), nElement, *this, SvXMLStyleTokens::Boolean, xAttrList ); break; case XML_ELEMENT(NUMBER, XML_TEXT_CONTENT): pContext = new SvXMLNumFmtElementContext( GetImport(), nElement, *this, SvXMLStyleTokens::TextContent, xAttrList ); break; case XML_ELEMENT(STYLE, XML_TEXT_PROPERTIES): pContext = new SvXMLNumFmtPropContext( GetImport(), nElement, *this, xAttrList ); break; case XML_ELEMENT(STYLE, XML_MAP): { // SvXMLNumFmtMapContext::EndElement adds to aMyConditions, // so there's no need for an extra flag pContext = new SvXMLNumFmtMapContext( GetImport(), nElement, *this, xAttrList ); } break; } if( !pContext ) { SAL_WARN("xmloff.core", "No context for unknown-element " << SvXMLImport::getPrefixAndNameFromToken(nElement)); pContext = new SvXMLImportContext(GetImport()); } return pContext; } sal_Int32 SvXMLNumFormatContext::GetKey() { if (m_nKey > -1) { if (m_bRemoveAfterUse) { // format is used -> don't remove m_bRemoveAfterUse = false; if (m_pData) m_pData->SetUsed(m_nKey); // Add to import's list of keys now - CreateAndInsert didn't add // the style if bRemoveAfterUse was set. GetImport().AddNumberStyle( m_nKey, GetName() ); } return m_nKey; } else { // reset bRemoveAfterUse before CreateAndInsert, so AddKey is called without bRemoveAfterUse set m_bRemoveAfterUse = false; CreateAndInsert(true); return m_nKey; } } sal_Int32 SvXMLNumFormatContext::PrivateGetKey() { // used for map elements in CreateAndInsert - don't reset bRemoveAfterUse flag if (m_nKey > -1) return m_nKey; else { CreateAndInsert(true); return m_nKey; } } sal_Int32 SvXMLNumFormatContext::CreateAndInsert( css::uno::Reference< css::util::XNumberFormatsSupplier > const & xFormatsSupplier ) { if (m_nKey <= -1) { SvNumberFormatter* pFormatter = nullptr; SvNumberFormatsSupplierObj* pObj = comphelper::getFromUnoTunnel( xFormatsSupplier ); if (pObj) pFormatter = pObj->GetNumberFormatter(); if ( pFormatter ) return CreateAndInsert( pFormatter ); else return -1; } else return m_nKey; } void SvXMLNumFormatContext::CreateAndInsert(bool /*bOverwrite*/) { if (m_nKey <= -1) CreateAndInsert(m_pData->GetNumberFormatter()); } sal_Int32 SvXMLNumFormatContext::CreateAndInsert(SvNumberFormatter* pFormatter) { if (!pFormatter) { OSL_FAIL("no number formatter"); return -1; } sal_uInt32 nIndex = NUMBERFORMAT_ENTRY_NOT_FOUND; for (size_t i = 0; i < m_aMyConditions.size(); i++) { SvXMLNumFormatContext* pStyle = const_cast( static_cast(m_pStyles->FindStyleChildContext( XmlStyleFamily::DATA_STYLE, m_aMyConditions[i].sMapName))); if (this == pStyle) { SAL_INFO("xmloff.style", "invalid style:map references containing style"); pStyle = nullptr; } if (pStyle) { if (pStyle->PrivateGetKey() > -1) // don't reset pStyle's bRemoveAfterUse flag AddCondition(i); } } sal_Int32 nBufLen; if ( m_aFormatCode.isEmpty() ) { // insert empty format as empty string (with quotes) // #93901# this check has to be done before inserting the conditions m_aFormatCode.append("\"\""); // "" } else if (m_bHasTrailingEmptyText && (nBufLen = m_aFormatCode.getLength()) >= 3) { // Remove a trailing empty text. Earlier this may had been written to // file, like in "General;General" written with elements for // 'General"";General""' (whyever); when reading, empty text was // ignored, which it isn't anymore, so get rid of those. if (m_aFormatCode[nBufLen-1] == '"' && m_aFormatCode[nBufLen-2] == '"') m_aFormatCode.truncate( nBufLen - 2); } m_aFormatCode.insert( 0, m_aConditions ); m_aConditions.setLength(0); OUString sFormat = m_aFormatCode.makeStringAndClear(); // test special cases if ( m_bAutoDec ) // automatic decimal places { // #99391# adjust only if the format contains no text elements, no conditions // and no color definition (detected by the '[' at the start) if ( m_nType == SvXMLStylesTokens::NUMBER_STYLE && !m_bHasExtraText && m_aMyConditions.empty() && sFormat.toChar() != '[' ) nIndex = pFormatter->GetStandardIndex( m_nFormatLang ); } if ( m_bAutoInt ) // automatic integer digits { //! only if two decimal places was set? if ( m_nType == SvXMLStylesTokens::NUMBER_STYLE && !m_bHasExtraText && m_aMyConditions.empty() && sFormat.toChar() != '[' ) nIndex = pFormatter->GetFormatIndex( NF_NUMBER_SYSTEM, m_nFormatLang ); } if ( m_nType == SvXMLStylesTokens::BOOLEAN_STYLE && !m_bHasExtraText && m_aMyConditions.empty() && sFormat.toChar() != '[' ) nIndex = pFormatter->GetFormatIndex( NF_BOOLEAN, m_nFormatLang ); // check for default date formats if ( m_nType == SvXMLStylesTokens::DATE_STYLE && m_bAutoOrder && !m_bDateNoDefault ) { NfIndexTableOffset eFormat = static_cast(SvXMLNumFmtDefaults::GetDefaultDateFormat( m_eDateDOW, m_eDateDay, m_eDateMonth, m_eDateYear, m_eDateHours, m_eDateMins, m_eDateSecs, m_bFromSystem )); if ( eFormat < NF_INDEX_TABLE_RESERVED_START ) { // #109651# if a date format has the automatic-order attribute and // contains exactly the elements of one of the default date formats, // use that default format, with the element order and separators // from the current locale settings nIndex = pFormatter->GetFormatIndex( eFormat, m_nFormatLang ); } } if ( nIndex == NUMBERFORMAT_ENTRY_NOT_FOUND && !sFormat.isEmpty() ) { // insert by format string OUString aFormatStr( sFormat ); nIndex = pFormatter->GetEntryKey( aFormatStr, m_nFormatLang ); if ( nIndex == NUMBERFORMAT_ENTRY_NOT_FOUND ) { sal_Int32 nErrPos = 0; SvNumFormatType l_nType = SvNumFormatType::ALL; bool bOk = pFormatter->PutEntry( aFormatStr, nErrPos, l_nType, nIndex, m_nFormatLang ); if ( !bOk && nErrPos == 0 && aFormatStr != sFormat ) { // if the string was modified by PutEntry, look for an existing format // with the modified string nIndex = pFormatter->GetEntryKey( aFormatStr, m_nFormatLang ); if ( nIndex != NUMBERFORMAT_ENTRY_NOT_FOUND ) bOk = true; } if (!bOk) nIndex = NUMBERFORMAT_ENTRY_NOT_FOUND; } } //! I18N doesn't provide SYSTEM or extended date information yet if ( nIndex != NUMBERFORMAT_ENTRY_NOT_FOUND && !m_bAutoOrder ) { // use fixed-order formats instead of SYS... if bAutoOrder is false // (only if the format strings are equal for the locale) NfIndexTableOffset eOffset = SvNumberFormatter::GetIndexTableOffset( nIndex ); if ( eOffset == NF_DATE_SYS_DMMMYYYY ) { sal_uInt32 nNewIndex = pFormatter->GetFormatIndex( NF_DATE_DIN_DMMMYYYY, m_nFormatLang ); const SvNumberformat* pOldEntry = pFormatter->GetEntry( nIndex ); const SvNumberformat* pNewEntry = pFormatter->GetEntry( nNewIndex ); if ( pOldEntry && pNewEntry && pOldEntry->GetFormatstring() == pNewEntry->GetFormatstring() ) nIndex = nNewIndex; } else if ( eOffset == NF_DATE_SYS_DMMMMYYYY ) { sal_uInt32 nNewIndex = pFormatter->GetFormatIndex( NF_DATE_DIN_DMMMMYYYY, m_nFormatLang ); const SvNumberformat* pOldEntry = pFormatter->GetEntry( nIndex ); const SvNumberformat* pNewEntry = pFormatter->GetEntry( nNewIndex ); if ( pOldEntry && pNewEntry && pOldEntry->GetFormatstring() == pNewEntry->GetFormatstring() ) nIndex = nNewIndex; } } if ((nIndex != NUMBERFORMAT_ENTRY_NOT_FOUND) && !m_sFormatTitle.isEmpty()) { SvNumberformat* pFormat = const_cast(pFormatter->GetEntry( nIndex )); if (pFormat) { pFormat->SetComment(m_sFormatTitle); } } if ( nIndex == NUMBERFORMAT_ENTRY_NOT_FOUND ) { OSL_FAIL("invalid number format"); nIndex = pFormatter->GetStandardIndex( m_nFormatLang ); } m_pData->AddKey( nIndex, GetName(), m_bRemoveAfterUse ); m_nKey = nIndex; // Add to import's list of keys (shared between styles and content import) // only if not volatile - formats are removed from NumberFormatter at the // end of each import (in SvXMLNumFmtHelper dtor). // If bRemoveAfterUse is reset later in GetKey, AddNumberStyle is called there. if (!m_bRemoveAfterUse) GetImport().AddNumberStyle( m_nKey, GetName() ); return m_nKey; } const LocaleDataWrapper& SvXMLNumFormatContext::GetLocaleData() const { return m_pData->GetLocaleData( m_nFormatLang ); } void SvXMLNumFormatContext::AddToCode( sal_Unicode c ) { m_aFormatCode.append( c ); m_bHasExtraText = true; } void SvXMLNumFormatContext::AddToCode( std::u16string_view rString ) { m_aFormatCode.append( rString ); m_bHasExtraText = true; m_bHasTrailingEmptyText = false; // is set by caller again if so } void SvXMLNumFormatContext::AddNumber( const SvXMLNumberInfo& rInfo ) { SvNumberFormatter* pFormatter = m_pData->GetNumberFormatter(); if (!pFormatter) return; // store special conditions m_bAutoDec = ( rInfo.nDecimals < 0 ); m_bAutoInt = ( rInfo.nInteger < 0 ); sal_uInt16 nPrec = 0; sal_uInt16 nLeading = 0; if ( rInfo.nDecimals >= 0 ) // < 0 : Default nPrec = static_cast(rInfo.nDecimals); if ( rInfo.nInteger >= 0 ) // < 0 : Default nLeading = static_cast(rInfo.nInteger); if ( m_bAutoDec ) { if ( m_nType == SvXMLStylesTokens::CURRENCY_STYLE ) { // for currency formats, "automatic decimals" is used for the automatic // currency format with (fixed) decimals from the locale settings const LocaleDataWrapper& rLoc = m_pData->GetLocaleData( m_nFormatLang ); nPrec = rLoc.getCurrDigits(); } else { // for other types, "automatic decimals" means dynamic determination of // decimals, as achieved with the "general" keyword m_aFormatCode.append( pFormatter->GetStandardName( m_nFormatLang ) ); return; } } if ( m_bAutoInt ) { //!... } sal_uInt16 nGenPrec = nPrec; if ( rInfo.nMinDecimalDigits >= 0 ) nGenPrec = rInfo.nMinDecimalDigits; if ( rInfo.bDecReplace ) nGenPrec = 0; // generate format without decimals... bool bGrouping = rInfo.bGrouping; size_t const nEmbeddedCount = rInfo.m_EmbeddedElements.size(); if ( nEmbeddedCount && rInfo.m_EmbeddedElements.rbegin()->first > 0 ) bGrouping = false; // grouping and embedded characters in integer part can't be used together sal_uInt32 nStdIndex = pFormatter->GetStandardIndex( m_nFormatLang ); OUStringBuffer aNumStr(pFormatter->GenerateFormat( nStdIndex, m_nFormatLang, bGrouping, false, nGenPrec, nLeading )); if ( rInfo.nExpDigits >= 0 && nLeading == 0 && !bGrouping && nEmbeddedCount == 0 ) { // #i43959# For scientific numbers, "#" in the integer part forces a digit, // so it has to be removed if nLeading is 0 (".00E+0", not "#.00E+0"). aNumStr.stripStart('#'); } if ( rInfo.nBlankInteger > 0 ) { // Replace nBlankInteger '0' by '?' sal_Int32 nIndex = 0; sal_Int32 nBlanks = rInfo.nBlankInteger; sal_Int32 nIntegerEnd = aNumStr.indexOf( pFormatter->GetNumDecimalSep() ); if ( nIntegerEnd < 0 ) nIntegerEnd = aNumStr.getLength(); while ( nIndex < nIntegerEnd && nBlanks > 0 ) { if ( aNumStr[nIndex] == '0' ) { aNumStr[nIndex] = '?'; nBlanks--; } nIndex++; } } if ( bGrouping && rInfo.nExpInterval > rInfo.nInteger ) { sal_Int32 nIndex = 0; sal_Int32 nDigits = rInfo.nInteger; sal_Int32 nIntegerEnd = aNumStr.indexOf( pFormatter->GetNumDecimalSep() ); if ( nIntegerEnd < 0 ) nIntegerEnd = aNumStr.getLength(); while ( nIndex >= 0 && nIndex < nIntegerEnd ) { if ( ( nIndex = aNumStr.indexOf( '#', nIndex ) ) >= 0 ) { nDigits ++; nIndex ++; } else nIndex = -1; } while ( rInfo.nExpInterval > nDigits ) { nDigits++; aNumStr.insert( 0, '#' ); } } if ( ( rInfo.bDecReplace || rInfo.nMinDecimalDigits < rInfo.nDecimals ) && nPrec ) // add decimal replacement (dashes) { // add dashes for explicit decimal replacement, # or ? for variable decimals sal_Unicode cAdd = rInfo.bDecReplace ? '-' : ( rInfo.bDecAlign ? '?': '#' ); if ( rInfo.nMinDecimalDigits == 0 ) aNumStr.append( m_pData->GetLocaleData( m_nFormatLang ).getNumDecimalSep() ); for ( sal_uInt16 i=rInfo.nMinDecimalDigits; i 0 ) { nExpPos = aNumStr.getLength(); aNumStr.append( rInfo.bExponentLowercase ? u"e" : u"E" ); // exponent sign is required with embedded text in exponent if ( rInfo.bExpSign || ( nEmbeddedCount && ( rInfo.nDecimals + 1 < -rInfo.m_EmbeddedElements.begin()->first ) ) ) { aNumStr.append( u"+" ); } for (sal_Int32 i=0; i=0) and decimal (position <0) part // nZeroPos is the string position where format position 0 is inserted sal_Int32 nZeroPos = aNumStr.indexOf( m_pData->GetLocaleData( m_nFormatLang ).getNumDecimalSep() ); if ( nZeroPos < 0 ) { nZeroPos = aNumStr.getLength(); } // m_EmbeddedElements is sorted - last entry has the largest position (leftmost) sal_Int32 const nLastFormatPos = rInfo.m_EmbeddedElements.rbegin()->first; if ( nLastFormatPos >= nZeroPos ) { // add '#' characters so all embedded texts are really embedded in digits // (there always has to be a digit before the leftmost embedded text) sal_Int32 nAddCount = nLastFormatPos + 1 - nZeroPos; for(sal_Int32 index = 0; index < nAddCount; ++index) { aNumStr.insert(0, '#'); } nZeroPos = nZeroPos + nAddCount; if ( nExpPos > 0 ) nExpPos = nExpPos + nAddCount; } // m_EmbeddedElements is sorted with ascending positions - loop is from right to left for (auto const& it : rInfo.m_EmbeddedElements) { sal_Int32 const nFormatPos = it.first; sal_Int32 nInsertPos = nZeroPos - nFormatPos; if ( nExpPos > 0 && nInsertPos > nExpPos ) nInsertPos ++; if ( 0 <= nInsertPos && nInsertPos <= aNumStr.getLength() ) { aNumStr.insert( nInsertPos, it.second ); } } } m_aFormatCode.append( aNumStr ); // add extra thousands separators for display factor if (rInfo.fDisplayFactor == 1.0 || rInfo.fDisplayFactor <= 0.0) return; // test for 1.0 is just for optimization - nSepCount would be 0 // one separator for each factor of 1000 sal_Int32 nSepCount = static_cast(::rtl::math::round( log10(rInfo.fDisplayFactor) / 3.0 )); if ( nSepCount > 0 ) { OUString aSep = m_pData->GetLocaleData( m_nFormatLang ).getNumThousandSep(); for ( sal_Int32 i=0; iGetNumberFormatter(); if ( pFormatter ) { pFormatter->ChangeIntl( m_nFormatLang ); OUString sCurString, sDummy; pFormatter->GetCompatibilityCurrency( sCurString, sDummy ); aSymbol = sCurString; bAutomatic = true; } } else if ( nLang == LANGUAGE_SYSTEM && aSymbol == "CCC" ) { // "CCC" is used for automatic long symbol bAutomatic = true; } if ( bAutomatic ) { // remove unnecessary quotes before automatic symbol (formats like "-(0DM)") // otherwise the currency symbol isn't recognized (#94048#) sal_Int32 nLength = m_aFormatCode.getLength(); if ( nLength > 1 && m_aFormatCode[nLength - 1] == '"' ) { // find start of quoted string // When SvXMLNumFmtElementContext::EndElement creates escaped quotes, // they must be handled here, too. sal_Int32 nFirst = nLength - 2; while ( nFirst >= 0 && m_aFormatCode[nFirst] != '"' ) --nFirst; if ( nFirst >= 0 ) { // remove both quotes from aFormatCode OUString aOld = m_aFormatCode.makeStringAndClear(); if ( nFirst > 0 ) m_aFormatCode.append( aOld.subView( 0, nFirst ) ); if ( nLength > nFirst + 2 ) m_aFormatCode.append( aOld.subView( nFirst + 1, nLength - nFirst - 2 ) ); } } } if (!bAutomatic) m_aFormatCode.append( "[$" ); // intro for "new" currency symbols m_aFormatCode.append( aSymbol ); if (!bAutomatic) { if ( nLang != LANGUAGE_SYSTEM ) { // '-' sign and language code in hex: m_aFormatCode.append("-" + OUString(OUString::number(sal_uInt16(nLang), 16)).toAsciiUpperCase()); } m_aFormatCode.append( ']' ); // end of "new" currency symbol } } void SvXMLNumFormatContext::AddNfKeyword( sal_uInt16 nIndex ) { SvNumberFormatter* pFormatter = m_pData->GetNumberFormatter(); if (!pFormatter) return; if ( nIndex == NF_KEY_NNNN ) { nIndex = NF_KEY_NNN; m_bHasLongDoW = true; // to remove string constant with separator } OUString sKeyword = pFormatter->GetKeyword( m_nFormatLang, nIndex ); if ( nIndex == NF_KEY_H || nIndex == NF_KEY_HH || nIndex == NF_KEY_MI || nIndex == NF_KEY_MMI || nIndex == NF_KEY_S || nIndex == NF_KEY_SS ) { if ( !m_bTruncate && !m_bHasDateTime ) { // with truncate-on-overflow = false, add "[]" to first time part m_aFormatCode.append("[" + sKeyword + "]"); } else { m_aFormatCode.append( sKeyword ); } m_bHasDateTime = true; } else { m_aFormatCode.append( sKeyword ); } // collect the date elements that the format contains, to recognize default date formats switch ( nIndex ) { case NF_KEY_NN: m_eDateDOW = XML_DEA_SHORT; break; case NF_KEY_NNN: case NF_KEY_NNNN: m_eDateDOW = XML_DEA_LONG; break; case NF_KEY_D: m_eDateDay = XML_DEA_SHORT; break; case NF_KEY_DD: m_eDateDay = XML_DEA_LONG; break; case NF_KEY_M: m_eDateMonth = XML_DEA_SHORT; break; case NF_KEY_MM: m_eDateMonth = XML_DEA_LONG; break; case NF_KEY_MMM: m_eDateMonth = XML_DEA_TEXTSHORT; break; case NF_KEY_MMMM: m_eDateMonth = XML_DEA_TEXTLONG; break; case NF_KEY_YY: m_eDateYear = XML_DEA_SHORT; break; case NF_KEY_YYYY: m_eDateYear = XML_DEA_LONG; break; case NF_KEY_H: m_eDateHours = XML_DEA_SHORT; break; case NF_KEY_HH: m_eDateHours = XML_DEA_LONG; break; case NF_KEY_MI: m_eDateMins = XML_DEA_SHORT; break; case NF_KEY_MMI: m_eDateMins = XML_DEA_LONG; break; case NF_KEY_S: m_eDateSecs = XML_DEA_SHORT; break; case NF_KEY_SS: m_eDateSecs = XML_DEA_LONG; break; case NF_KEY_AP: case NF_KEY_AMPM: break; // AM/PM may or may not be in date/time formats -> ignore by itself default: m_bDateNoDefault = true; // any other element -> no default format } } static bool lcl_IsAtEnd( OUStringBuffer& rBuffer, std::u16string_view rToken ) { sal_Int32 nBufLen = rBuffer.getLength(); sal_Int32 nTokLen = rToken.size(); if ( nTokLen > nBufLen ) return false; sal_Int32 nStartPos = nBufLen - nTokLen; for ( sal_Int32 nTokPos = 0; nTokPos < nTokLen; nTokPos++ ) if ( rToken[ nTokPos ] != rBuffer[nStartPos + nTokPos] ) return false; return true; } bool SvXMLNumFormatContext::ReplaceNfKeyword( sal_uInt16 nOld, sal_uInt16 nNew ) { // replaces one keyword with another if it is found at the end of the code SvNumberFormatter* pFormatter = m_pData->GetNumberFormatter(); if (!pFormatter) return false; OUString sOldStr = pFormatter->GetKeyword( m_nFormatLang, nOld ); if ( lcl_IsAtEnd( m_aFormatCode, sOldStr ) ) { // remove old keyword m_aFormatCode.setLength( m_aFormatCode.getLength() - sOldStr.getLength() ); // add new keyword OUString sNewStr = pFormatter->GetKeyword( m_nFormatLang, nNew ); m_aFormatCode.append( sNewStr ); return true; // changed } return false; // not found } void SvXMLNumFormatContext::AddCondition( const sal_Int32 nIndex ) { OUString rApplyName = m_aMyConditions[nIndex].sMapName; OUString rCondition = m_aMyConditions[nIndex].sCondition; SvNumberFormatter* pFormatter = m_pData->GetNumberFormatter(); sal_uInt32 l_nKey = m_pData->GetKeyForName( rApplyName ); OUString sRealCond; if ( !(pFormatter && l_nKey != NUMBERFORMAT_ENTRY_NOT_FOUND && rCondition.startsWith("value()", &sRealCond)) ) return; //! test for valid conditions //! test for default conditions bool bDefaultCond = false; //! collect all conditions first and adjust default to >=0, >0 or <0 depending on count //! allow blanks in conditions if ( m_aConditions.isEmpty() && m_aMyConditions.size() == 1 && sRealCond == ">=0" ) bDefaultCond = true; if ( m_nType == SvXMLStylesTokens::TEXT_STYLE && static_cast(nIndex) == m_aMyConditions.size() - 1 ) { // The last condition in a number format with a text part can only // be "all other numbers", the condition string must be empty. bDefaultCond = true; } if (!bDefaultCond) { // Convert != to <> sal_Int32 nPos = sRealCond.indexOf( "!=" ); if ( nPos >= 0 ) { sRealCond = sRealCond.replaceAt( nPos, 2, u"<>" ); } nPos = sRealCond.indexOf( '.' ); if ( nPos >= 0 ) { // #i8026# #103991# localize decimal separator const OUString& rDecSep = GetLocaleData().getNumDecimalSep(); if ( rDecSep.getLength() > 1 || rDecSep[0] != '.' ) { sRealCond = sRealCond.replaceAt( nPos, 1, rDecSep ); } } m_aConditions.append("[" + sRealCond + "]"); } const SvNumberformat* pFormat = pFormatter->GetEntry(l_nKey); if ( pFormat ) m_aConditions.append( pFormat->GetFormatstring() ); m_aConditions.append( ';' ); } void SvXMLNumFormatContext::AddCondition( const OUString& rCondition, const OUString& rApplyName ) { MyCondition aCondition; aCondition.sCondition = rCondition; aCondition.sMapName = rApplyName; m_aMyConditions.push_back(aCondition); } void SvXMLNumFormatContext::AddColor( Color const nColor ) { SvNumberFormatter* pFormatter = m_pData->GetNumberFormatter(); if (!pFormatter) return; OUStringBuffer aColName; for ( sal_uInt16 i=0; iGetKeyword( m_nFormatLang, sal::static_int_cast< sal_uInt16 >(NF_KEY_FIRSTCOLOR + i) ); break; } if ( !aColName.isEmpty() ) { aColName.insert( 0, '[' ); aColName.append( ']' ); m_aFormatCode.insert( 0, aColName ); } } void SvXMLNumFormatContext::UpdateCalendar( const OUString& rNewCalendar ) { if ( rNewCalendar == m_sCalendar ) return; if (rNewCalendar.isEmpty() || rNewCalendar == m_aImplicitCalendar[0]) { m_eImplicitCalendar = (m_eImplicitCalendar == ImplicitCalendar::OTHER ? ImplicitCalendar::DEFAULT_FROM_OTHER : ImplicitCalendar::DEFAULT); } else if (m_aImplicitCalendar[0].isEmpty() && rNewCalendar == GetLocaleData().getDefaultCalendar()->Name) { m_eImplicitCalendar = (m_eImplicitCalendar == ImplicitCalendar::OTHER ? ImplicitCalendar::DEFAULT_FROM_OTHER : ImplicitCalendar::DEFAULT); m_aImplicitCalendar[0] = rNewCalendar; } else if (rNewCalendar == m_aImplicitCalendar[1]) { m_eImplicitCalendar = (m_eImplicitCalendar == ImplicitCalendar::OTHER ? ImplicitCalendar::SECONDARY_FROM_OTHER : ImplicitCalendar::SECONDARY); } else if (m_aImplicitCalendar[1].isEmpty() && GetLocaleData().doesSecondaryCalendarUseEC( rNewCalendar)) { m_eImplicitCalendar = (m_eImplicitCalendar == ImplicitCalendar::OTHER ? ImplicitCalendar::SECONDARY_FROM_OTHER : ImplicitCalendar::SECONDARY); m_aImplicitCalendar[1] = rNewCalendar; } else { m_eImplicitCalendar = ImplicitCalendar::OTHER; } if (m_eImplicitCalendar != ImplicitCalendar::DEFAULT && m_eImplicitCalendar != ImplicitCalendar::SECONDARY) { // A switch from empty default calendar to named default calendar or // vice versa is not a switch. bool bSameDefault = false; if (m_sCalendar.isEmpty() || rNewCalendar.isEmpty()) { // As both are not equal, only one can be empty here, the other // can not. const OUString& rDefaultCalendar = GetLocaleData().getDefaultCalendar()->Name; // So if one is the named default calendar the other is the // empty default calendar. bSameDefault = (rNewCalendar == rDefaultCalendar || m_sCalendar == rDefaultCalendar); } if (!bSameDefault) { m_aFormatCode.append( "[~" ); // intro for calendar code if (rNewCalendar.isEmpty()) { // Empty calendar name here means switching to default calendar // from a different calendar. Needs to be explicitly stated in // format code. m_aFormatCode.append( GetLocaleData().getDefaultCalendar()->Name ); } else { m_aFormatCode.append( rNewCalendar ); } m_aFormatCode.append( ']' ); // end of calendar code } } m_sCalendar = rNewCalendar; } bool SvXMLNumFormatContext::IsSystemLanguage() const { return m_nFormatLang == LANGUAGE_SYSTEM; } // SvXMLNumFmtHelper SvXMLNumFmtHelper::SvXMLNumFmtHelper( const uno::Reference& rSupp, const uno::Reference& rxContext ) { SAL_WARN_IF( !rxContext.is(), "xmloff", "got no service manager" ); SvNumberFormatter* pFormatter = nullptr; SvNumberFormatsSupplierObj* pObj = comphelper::getFromUnoTunnel( rSupp ); if (pObj) pFormatter = pObj->GetNumberFormatter(); m_pData = std::make_unique( pFormatter, rxContext ); } SvXMLNumFmtHelper::SvXMLNumFmtHelper( SvNumberFormatter* pNumberFormatter, const uno::Reference& rxContext ) { SAL_WARN_IF( !rxContext.is(), "xmloff", "got no service manager" ); m_pData = std::make_unique( pNumberFormatter, rxContext ); } SvXMLNumFmtHelper::~SvXMLNumFmtHelper() { // remove temporary (volatile) formats from NumberFormatter m_pData->RemoveVolatileFormats(); } SvXMLStyleContext* SvXMLNumFmtHelper::CreateChildContext( SvXMLImport& rImport, sal_Int32 nElement, const css::uno::Reference< css::xml::sax::XFastAttributeList >& xAttrList, SvXMLStylesContext& rStyles ) { SvXMLStylesTokens nStyleToken; switch (nElement) { case XML_ELEMENT(NUMBER, XML_NUMBER_STYLE): nStyleToken = SvXMLStylesTokens::NUMBER_STYLE; break; case XML_ELEMENT(NUMBER, XML_CURRENCY_STYLE): nStyleToken = SvXMLStylesTokens::CURRENCY_STYLE; break; case XML_ELEMENT(NUMBER, XML_PERCENTAGE_STYLE): nStyleToken = SvXMLStylesTokens::PERCENTAGE_STYLE; break; case XML_ELEMENT(NUMBER, XML_DATE_STYLE): nStyleToken = SvXMLStylesTokens::DATE_STYLE; break; case XML_ELEMENT(NUMBER, XML_TIME_STYLE): nStyleToken = SvXMLStylesTokens::TIME_STYLE; break; case XML_ELEMENT(NUMBER, XML_BOOLEAN_STYLE): nStyleToken = SvXMLStylesTokens::BOOLEAN_STYLE; break; case XML_ELEMENT(NUMBER, XML_TEXT_STYLE): nStyleToken = SvXMLStylesTokens::TEXT_STYLE; break; default: // return NULL if not a data style, caller must handle other elements return nullptr; } return new SvXMLNumFormatContext( rImport, nElement, m_pData.get(), nStyleToken, xAttrList, rStyles ); } LanguageType SvXMLNumFmtHelper::GetLanguageForKey(sal_Int32 nKey) const { if (m_pData->GetNumberFormatter()) { const SvNumberformat* pEntry = m_pData->GetNumberFormatter()->GetEntry(nKey); if (pEntry) return pEntry->GetLanguage(); } return LANGUAGE_SYSTEM; } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */