/* -*- 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 #include #include #include #include #include using namespace ::com::sun::star; using namespace ::xmloff::token; using namespace ::svt; typedef std::set< sal_uInt32 > SvXMLuInt32Set; namespace { struct SvXMLEmbeddedTextEntry { sal_uInt16 nSourcePos; // position in NumberFormat (to skip later) sal_Int32 nFormatPos; // resulting position in embedded-text element OUString aText; bool isBlankWidth; // "_x" SvXMLEmbeddedTextEntry( sal_uInt16 nSP, sal_Int32 nFP, OUString aT, bool bBW = false ) : nSourcePos(nSP), nFormatPos(nFP), aText(std::move(aT)), isBlankWidth( bBW ) {} }; } class SvXMLEmbeddedTextEntryArr { typedef std::vector DataType; DataType maData; public: void push_back( SvXMLEmbeddedTextEntry const& r ) { maData.push_back(r); } const SvXMLEmbeddedTextEntry& operator[] ( size_t i ) const { return maData[i]; } size_t size() const { return maData.size(); } }; class SvXMLNumUsedList_Impl { SvXMLuInt32Set aUsed; SvXMLuInt32Set aWasUsed; SvXMLuInt32Set::iterator aCurrentUsedPos; sal_uInt32 nUsedCount; sal_uInt32 nWasUsedCount; public: SvXMLNumUsedList_Impl(); void SetUsed( sal_uInt32 nKey ); bool IsUsed( sal_uInt32 nKey ) const; bool IsWasUsed( sal_uInt32 nKey ) const; void Export(); bool GetFirstUsed(sal_uInt32& nKey); bool GetNextUsed(sal_uInt32& nKey); uno::Sequence GetWasUsed() const; void SetWasUsed(const uno::Sequence& rWasUsed); }; //! SvXMLNumUsedList_Impl should be optimized! SvXMLNumUsedList_Impl::SvXMLNumUsedList_Impl() : nUsedCount(0), nWasUsedCount(0) { } void SvXMLNumUsedList_Impl::SetUsed( sal_uInt32 nKey ) { if ( !IsWasUsed(nKey) ) { std::pair aPair = aUsed.insert( nKey ); if (aPair.second) nUsedCount++; } } bool SvXMLNumUsedList_Impl::IsUsed( sal_uInt32 nKey ) const { SvXMLuInt32Set::const_iterator aItr = aUsed.find(nKey); return (aItr != aUsed.end()); } bool SvXMLNumUsedList_Impl::IsWasUsed( sal_uInt32 nKey ) const { SvXMLuInt32Set::const_iterator aItr = aWasUsed.find(nKey); return (aItr != aWasUsed.end()); } void SvXMLNumUsedList_Impl::Export() { SvXMLuInt32Set::const_iterator aItr = aUsed.begin(); while (aItr != aUsed.end()) { std::pair aPair = aWasUsed.insert( *aItr ); if (aPair.second) nWasUsedCount++; ++aItr; } aUsed.clear(); nUsedCount = 0; } bool SvXMLNumUsedList_Impl::GetFirstUsed(sal_uInt32& nKey) { bool bRet(false); aCurrentUsedPos = aUsed.begin(); if(nUsedCount) { DBG_ASSERT(aCurrentUsedPos != aUsed.end(), "something went wrong"); nKey = *aCurrentUsedPos; bRet = true; } return bRet; } bool SvXMLNumUsedList_Impl::GetNextUsed(sal_uInt32& nKey) { bool bRet(false); if (aCurrentUsedPos != aUsed.end()) { ++aCurrentUsedPos; if (aCurrentUsedPos != aUsed.end()) { nKey = *aCurrentUsedPos; bRet = true; } } return bRet; } uno::Sequence SvXMLNumUsedList_Impl::GetWasUsed() const { return comphelper::containerToSequence(aWasUsed); } void SvXMLNumUsedList_Impl::SetWasUsed(const uno::Sequence& rWasUsed) { DBG_ASSERT(nWasUsedCount == 0, "WasUsed should be empty"); for (const auto nWasUsed : rWasUsed) { std::pair aPair = aWasUsed.insert( nWasUsed ); if (aPair.second) nWasUsedCount++; } } SvXMLNumFmtExport::SvXMLNumFmtExport( SvXMLExport& rExp, const uno::Reference< util::XNumberFormatsSupplier >& rSupp ) : m_rExport( rExp ), m_sPrefix( u"N"_ustr ), m_pFormatter( nullptr ), m_bHasText( false ) { // supplier must be SvNumberFormatsSupplierObj SvNumberFormatsSupplierObj* pObj = comphelper::getFromUnoTunnel( rSupp ); if (pObj) m_pFormatter = pObj->GetNumberFormatter(); if ( m_pFormatter ) { m_pLocaleData.reset( new LocaleDataWrapper( m_pFormatter->GetComponentContext(), m_pFormatter->GetLanguageTag() ) ); } else { LanguageTag aLanguageTag( MsLangId::getConfiguredSystemLanguage() ); m_pLocaleData.reset( new LocaleDataWrapper( m_rExport.getComponentContext(), std::move(aLanguageTag) ) ); } m_pUsedList.reset(new SvXMLNumUsedList_Impl); } SvXMLNumFmtExport::SvXMLNumFmtExport( SvXMLExport& rExp, const css::uno::Reference< css::util::XNumberFormatsSupplier >& rSupp, OUString aPrefix ) : m_rExport( rExp ), m_sPrefix(std::move( aPrefix )), m_pFormatter( nullptr ), m_bHasText( false ) { // supplier must be SvNumberFormatsSupplierObj SvNumberFormatsSupplierObj* pObj = comphelper::getFromUnoTunnel( rSupp ); if (pObj) m_pFormatter = pObj->GetNumberFormatter(); if ( m_pFormatter ) { m_pLocaleData.reset( new LocaleDataWrapper( m_pFormatter->GetComponentContext(), m_pFormatter->GetLanguageTag() ) ); } else { LanguageTag aLanguageTag( MsLangId::getConfiguredSystemLanguage() ); m_pLocaleData.reset( new LocaleDataWrapper( m_rExport.getComponentContext(), std::move(aLanguageTag) ) ); } m_pUsedList.reset(new SvXMLNumUsedList_Impl); } SvXMLNumFmtExport::~SvXMLNumFmtExport() { } // helper methods static OUString lcl_CreateStyleName( sal_Int32 nKey, sal_Int32 nPart, bool bDefPart, std::u16string_view rPrefix ) { if (bDefPart) return rPrefix + OUString::number(nKey); else return rPrefix + OUString::number(nKey) + "P" + OUString::number( nPart ); } void SvXMLNumFmtExport::AddCalendarAttr_Impl( const OUString& rCalendar ) { if ( !rCalendar.isEmpty() ) { m_rExport.AddAttribute( XML_NAMESPACE_NUMBER, XML_CALENDAR, rCalendar ); } } void SvXMLNumFmtExport::AddStyleAttr_Impl( bool bLong ) { if ( bLong ) // short is default { m_rExport.AddAttribute( XML_NAMESPACE_NUMBER, XML_STYLE, XML_LONG ); } } void SvXMLNumFmtExport::AddLanguageAttr_Impl( LanguageType nLang ) { if ( nLang != LANGUAGE_SYSTEM ) { m_rExport.AddLanguageTagAttributes( XML_NAMESPACE_NUMBER, XML_NAMESPACE_NUMBER, LanguageTag( nLang), false); } } // methods to write individual elements within a format void SvXMLNumFmtExport::AddToTextElement_Impl( std::u16string_view rString ) { // append to sTextContent, write element in FinishTextElement_Impl // to avoid several text elements following each other m_sTextContent.append( rString ); // Also empty string leads to a number:text element as it may separate // keywords of the same letter (e.g. MM""MMM) that otherwise would be // concatenated when reading back in. m_bHasText = true; } void SvXMLNumFmtExport::FinishTextElement_Impl(bool bUseExtensionNS) { if ( m_bHasText ) { if ( !m_sBlankWidthString.isEmpty() ) { // Export only for 1.3 with extensions and later. SvtSaveOptions::ODFSaneDefaultVersion eVersion = m_rExport.getSaneDefaultVersion(); if (eVersion > SvtSaveOptions::ODFSVER_013 && ( (eVersion & SvtSaveOptions::ODFSVER_EXTENDED) != 0 )) { m_rExport.AddAttribute( XML_NAMESPACE_LO_EXT, XML_BLANK_WIDTH_CHAR, m_sBlankWidthString.makeStringAndClear() ); } } sal_uInt16 nNS = bUseExtensionNS ? XML_NAMESPACE_LO_EXT : XML_NAMESPACE_NUMBER; SvXMLElementExport aElem( m_rExport, nNS, XML_TEXT, true, false ); m_rExport.Characters( m_sTextContent.makeStringAndClear() ); m_bHasText = false; } } void SvXMLNumFmtExport::WriteColorElement_Impl( const Color& rColor ) { FinishTextElement_Impl(); OUStringBuffer aColStr( 7 ); ::sax::Converter::convertColor( aColStr, rColor ); m_rExport.AddAttribute( XML_NAMESPACE_FO, XML_COLOR, aColStr.makeStringAndClear() ); SvXMLElementExport aElem( m_rExport, XML_NAMESPACE_STYLE, XML_TEXT_PROPERTIES, true, false ); } void SvXMLNumFmtExport::WriteCurrencyElement_Impl( const OUString& rString, std::u16string_view rExt ) { FinishTextElement_Impl(); if ( !rExt.empty() ) { // rExt should be a 16-bit hex value max FFFF which may contain a // leading "-" separator (that is not a minus sign, but toInt32 can be // used to parse it, with post-processing as necessary): sal_Int32 nLang = o3tl::toInt32(rExt, 16); if ( nLang < 0 ) nLang = -nLang; SAL_WARN_IF(nLang > 0xFFFF, "xmloff.style", "Out of range Lang Id: " << nLang << " from input string: " << OUString(rExt)); AddLanguageAttr_Impl( LanguageType(nLang & 0xFFFF) ); // adds to pAttrList } SvXMLElementExport aElem( m_rExport, XML_NAMESPACE_NUMBER, XML_CURRENCY_SYMBOL, true, false ); m_rExport.Characters( rString ); } void SvXMLNumFmtExport::WriteBooleanElement_Impl() { FinishTextElement_Impl(); SvXMLElementExport aElem( m_rExport, XML_NAMESPACE_NUMBER, XML_BOOLEAN, true, false ); } void SvXMLNumFmtExport::WriteTextContentElement_Impl() { FinishTextElement_Impl(); SvXMLElementExport aElem( m_rExport, XML_NAMESPACE_NUMBER, XML_TEXT_CONTENT, true, false ); } // date elements void SvXMLNumFmtExport::WriteDayElement_Impl( const OUString& rCalendar, bool bLong ) { FinishTextElement_Impl(); AddCalendarAttr_Impl( rCalendar ); // adds to pAttrList AddStyleAttr_Impl( bLong ); // adds to pAttrList SvXMLElementExport aElem( m_rExport, XML_NAMESPACE_NUMBER, XML_DAY, true, false ); } void SvXMLNumFmtExport::WriteMonthElement_Impl( const OUString& rCalendar, bool bLong, bool bText ) { FinishTextElement_Impl(); AddCalendarAttr_Impl( rCalendar ); // adds to pAttrList AddStyleAttr_Impl( bLong ); // adds to pAttrList if ( bText ) { m_rExport.AddAttribute( XML_NAMESPACE_NUMBER, XML_TEXTUAL, XML_TRUE ); } SvXMLElementExport aElem( m_rExport, XML_NAMESPACE_NUMBER, XML_MONTH, true, false ); } void SvXMLNumFmtExport::WriteYearElement_Impl( const OUString& rCalendar, bool bLong ) { FinishTextElement_Impl(); AddCalendarAttr_Impl( rCalendar ); // adds to pAttrList AddStyleAttr_Impl( bLong ); // adds to pAttrList SvXMLElementExport aElem( m_rExport, XML_NAMESPACE_NUMBER, XML_YEAR, true, false ); } void SvXMLNumFmtExport::WriteEraElement_Impl( const OUString& rCalendar, bool bLong ) { FinishTextElement_Impl(); AddCalendarAttr_Impl( rCalendar ); // adds to pAttrList AddStyleAttr_Impl( bLong ); // adds to pAttrList SvXMLElementExport aElem( m_rExport, XML_NAMESPACE_NUMBER, XML_ERA, true, false ); } void SvXMLNumFmtExport::WriteDayOfWeekElement_Impl( const OUString& rCalendar, bool bLong ) { FinishTextElement_Impl(); AddCalendarAttr_Impl( rCalendar ); // adds to pAttrList AddStyleAttr_Impl( bLong ); // adds to pAttrList SvXMLElementExport aElem( m_rExport, XML_NAMESPACE_NUMBER, XML_DAY_OF_WEEK, true, false ); } void SvXMLNumFmtExport::WriteWeekElement_Impl( const OUString& rCalendar ) { FinishTextElement_Impl(); AddCalendarAttr_Impl( rCalendar ); // adds to pAttrList SvXMLElementExport aElem( m_rExport, XML_NAMESPACE_NUMBER, XML_WEEK_OF_YEAR, true, false ); } void SvXMLNumFmtExport::WriteQuarterElement_Impl( const OUString& rCalendar, bool bLong ) { FinishTextElement_Impl(); AddCalendarAttr_Impl( rCalendar ); // adds to pAttrList AddStyleAttr_Impl( bLong ); // adds to pAttrList SvXMLElementExport aElem( m_rExport, XML_NAMESPACE_NUMBER, XML_QUARTER, true, false ); } // time elements void SvXMLNumFmtExport::WriteHoursElement_Impl( bool bLong ) { FinishTextElement_Impl(); AddStyleAttr_Impl( bLong ); // adds to pAttrList SvXMLElementExport aElem( m_rExport, XML_NAMESPACE_NUMBER, XML_HOURS, true, false ); } void SvXMLNumFmtExport::WriteMinutesElement_Impl( bool bLong ) { FinishTextElement_Impl(); AddStyleAttr_Impl( bLong ); // adds to pAttrList SvXMLElementExport aElem( m_rExport, XML_NAMESPACE_NUMBER, XML_MINUTES, true, false ); } void SvXMLNumFmtExport::WriteRepeatedElement_Impl( sal_Unicode nChar ) { // Export only for 1.2 with extensions or 1.3 and later. SvtSaveOptions::ODFSaneDefaultVersion eVersion = m_rExport.getSaneDefaultVersion(); if (eVersion > SvtSaveOptions::ODFSVER_012) { FinishTextElement_Impl(eVersion < SvtSaveOptions::ODFSVER_013); // OFFICE-3765 For 1.2+ use loext namespace, for 1.3 use number namespace. SvXMLElementExport aElem( m_rExport, ((eVersion < SvtSaveOptions::ODFSVER_013) ? XML_NAMESPACE_LO_EXT : XML_NAMESPACE_NUMBER), XML_FILL_CHARACTER, true, false ); m_rExport.Characters( OUString( nChar ) ); } } namespace { void lcl_WriteBlankWidthString( std::u16string_view rBlankWidthChar, OUStringBuffer& rBlankWidthString, OUStringBuffer& rTextContent ) { // export "_x" if ( rBlankWidthString.isEmpty() ) { rBlankWidthString.append( rBlankWidthChar ); if ( !rTextContent.isEmpty() ) { // add position in rTextContent rBlankWidthString.append( rTextContent.getLength() ); } } else { // add "_" as separator if there are several blank width char rBlankWidthString.append( "_" ); rBlankWidthString.append( rBlankWidthChar ); rBlankWidthString.append( rTextContent.getLength() ); } // for previous versions, turn "_x" into the number of spaces used for x in InsertBlanks in the NumberFormat if ( !rBlankWidthChar.empty() ) { OUString aBlanks; SvNumberformat::InsertBlanks( aBlanks, 0, rBlankWidthChar[0] ); rTextContent.append( aBlanks ); } } } void SvXMLNumFmtExport::WriteSecondsElement_Impl( bool bLong, sal_uInt16 nDecimals ) { FinishTextElement_Impl(); AddStyleAttr_Impl( bLong ); // adds to pAttrList if ( nDecimals > 0 ) { m_rExport.AddAttribute( XML_NAMESPACE_NUMBER, XML_DECIMAL_PLACES, OUString::number( nDecimals ) ); } SvXMLElementExport aElem( m_rExport, XML_NAMESPACE_NUMBER, XML_SECONDS, true, false ); } void SvXMLNumFmtExport::WriteAMPMElement_Impl() { FinishTextElement_Impl(); SvXMLElementExport aElem( m_rExport, XML_NAMESPACE_NUMBER, XML_AM_PM, true, false ); } // numbers void SvXMLNumFmtExport::WriteIntegerElement_Impl( sal_Int32 nInteger, sal_Int32 nBlankInteger, bool bGrouping ) { // integer digits: '0' and '?' if ( nInteger >= 0 ) // negative = automatic { m_rExport.AddAttribute( XML_NAMESPACE_NUMBER, XML_MIN_INTEGER_DIGITS, OUString::number( nInteger ) ); } SvtSaveOptions::ODFSaneDefaultVersion eVersion = m_rExport.getSaneDefaultVersion(); // blank integer digits: '?' if ( nBlankInteger > 0 && ( (eVersion & SvtSaveOptions::ODFSVER_EXTENDED) != 0 ) ) { m_rExport.AddAttribute( XML_NAMESPACE_LO_EXT, XML_MAX_BLANK_INTEGER_DIGITS, OUString::number( nBlankInteger ) ); } // (automatic) grouping separator if ( bGrouping ) { m_rExport.AddAttribute( XML_NAMESPACE_NUMBER, XML_GROUPING, XML_TRUE ); } } void SvXMLNumFmtExport::WriteEmbeddedEntries_Impl( const SvXMLEmbeddedTextEntryArr& rEmbeddedEntries ) { auto nEntryCount = rEmbeddedEntries.size(); SvtSaveOptions::ODFSaneDefaultVersion eVersion = m_rExport.getSaneDefaultVersion(); for (decltype(nEntryCount) nEntry=0; nEntry < nEntryCount; ++nEntry) { const SvXMLEmbeddedTextEntry* pObj = &rEmbeddedEntries[nEntry]; // position attribute // position == 0 is between first integer digit and decimal separator // position < 0 is inside decimal part m_rExport.AddAttribute( XML_NAMESPACE_NUMBER, XML_POSITION, OUString::number( pObj->nFormatPos ) ); // text as element content OUStringBuffer aContent; OUStringBuffer aBlankWidthString; do { pObj = &rEmbeddedEntries[nEntry]; if ( pObj->isBlankWidth ) { // (#i20396# the spaces may also be in embedded-text elements) lcl_WriteBlankWidthString( pObj->aText, aBlankWidthString, aContent ); } else { // The array can contain several elements for the same position in the number. // Literal texts are merged into a single embedded-text element. aContent.append( pObj->aText ); } ++nEntry; } while ( nEntry < nEntryCount && rEmbeddedEntries[nEntry].nFormatPos == pObj->nFormatPos ); --nEntry; // Export only for 1.3 with extensions and later. if ( !aBlankWidthString.isEmpty() && eVersion > SvtSaveOptions::ODFSVER_013 && ( (eVersion & SvtSaveOptions::ODFSVER_EXTENDED) != 0 ) ) m_rExport.AddAttribute( XML_NAMESPACE_LO_EXT, XML_BLANK_WIDTH_CHAR, aBlankWidthString.makeStringAndClear() ); SvXMLElementExport aChildElem( m_rExport, XML_NAMESPACE_NUMBER, XML_EMBEDDED_TEXT, true, false ); m_rExport.Characters( aContent.makeStringAndClear() ); } } void SvXMLNumFmtExport::WriteNumberElement_Impl( sal_Int32 nDecimals, sal_Int32 nMinDecimals, sal_Int32 nInteger, sal_Int32 nBlankInteger, const OUString& rDashStr, bool bGrouping, sal_Int32 nTrailingThousands, const SvXMLEmbeddedTextEntryArr& rEmbeddedEntries ) { FinishTextElement_Impl(); // decimals if ( nDecimals >= 0 ) // negative = automatic { m_rExport.AddAttribute( XML_NAMESPACE_NUMBER, XML_DECIMAL_PLACES, OUString::number( nDecimals ) ); } if ( nMinDecimals >= 0 ) // negative = automatic { // Export only for 1.2 with extensions or 1.3 and later. SvtSaveOptions::ODFSaneDefaultVersion eVersion = m_rExport.getSaneDefaultVersion(); if (eVersion > SvtSaveOptions::ODFSVER_012) { // OFFICE-3860 For 1.2+ use loext namespace, for 1.3 use number namespace. m_rExport.AddAttribute( ((eVersion < SvtSaveOptions::ODFSVER_013) ? XML_NAMESPACE_LO_EXT : XML_NAMESPACE_NUMBER), XML_MIN_DECIMAL_PLACES, OUString::number( nMinDecimals ) ); } } // decimal replacement (dashes) or variable decimals (#) if ( !rDashStr.isEmpty() || nMinDecimals < nDecimals ) { // full variable decimals means an empty replacement string m_rExport.AddAttribute( XML_NAMESPACE_NUMBER, XML_DECIMAL_REPLACEMENT, rDashStr ); } WriteIntegerElement_Impl( nInteger, nBlankInteger, bGrouping ); // display-factor if there are trailing thousands separators if ( nTrailingThousands ) { // each separator character removes three digits double fFactor = ::rtl::math::pow10Exp( 1.0, 3 * nTrailingThousands ); OUStringBuffer aFactStr; ::sax::Converter::convertDouble( aFactStr, fFactor ); m_rExport.AddAttribute( XML_NAMESPACE_NUMBER, XML_DISPLAY_FACTOR, aFactStr.makeStringAndClear() ); } SvXMLElementExport aElem( m_rExport, XML_NAMESPACE_NUMBER, XML_NUMBER, true, true ); // number:embedded-text as child elements WriteEmbeddedEntries_Impl( rEmbeddedEntries ); } void SvXMLNumFmtExport::WriteScientificElement_Impl( sal_Int32 nDecimals, sal_Int32 nMinDecimals, sal_Int32 nInteger, sal_Int32 nBlankInteger, bool bGrouping, sal_Int32 nExp, sal_Int32 nExpInterval, bool bExpSign, bool bExponentLowercase, sal_Int32 nBlankExp, const SvXMLEmbeddedTextEntryArr& rEmbeddedEntries ) { FinishTextElement_Impl(); // decimals if ( nDecimals >= 0 ) // negative = automatic { m_rExport.AddAttribute( XML_NAMESPACE_NUMBER, XML_DECIMAL_PLACES, OUString::number( nDecimals ) ); } SvtSaveOptions::ODFSaneDefaultVersion eVersion = m_rExport.getSaneDefaultVersion(); if ( nMinDecimals >= 0 ) // negative = automatic { // Export only for 1.2 with extensions or 1.3 and later. if (eVersion > SvtSaveOptions::ODFSVER_012) { // OFFICE-3860 For 1.2+ use loext namespace, for 1.3 use number namespace. m_rExport.AddAttribute( ((eVersion < SvtSaveOptions::ODFSVER_013) ? XML_NAMESPACE_LO_EXT : XML_NAMESPACE_NUMBER), XML_MIN_DECIMAL_PLACES, OUString::number( nMinDecimals ) ); } } WriteIntegerElement_Impl( nInteger, nBlankInteger, bGrouping ); // exponent digits if ( nExp >= 0 ) { m_rExport.AddAttribute( XML_NAMESPACE_NUMBER, XML_MIN_EXPONENT_DIGITS, OUString::number( nExp ) ); } // exponent interval for engineering notation if ( nExpInterval >= 0 ) { // Export only for 1.2 with extensions or 1.3 and later. if (eVersion > SvtSaveOptions::ODFSVER_012) { // OFFICE-1828 For 1.2+ use loext namespace, for 1.3 use number namespace. m_rExport.AddAttribute( ((eVersion < SvtSaveOptions::ODFSVER_013) ? XML_NAMESPACE_LO_EXT : XML_NAMESPACE_NUMBER), XML_EXPONENT_INTERVAL, OUString::number( nExpInterval ) ); } } // exponent sign // Export only for 1.2 with extensions or 1.3 and later. if (eVersion > SvtSaveOptions::ODFSVER_012) { // OFFICE-3860 For 1.2+ use loext namespace, for 1.3 use number namespace. m_rExport.AddAttribute( ((eVersion < SvtSaveOptions::ODFSVER_013) ? XML_NAMESPACE_LO_EXT : XML_NAMESPACE_NUMBER), XML_FORCED_EXPONENT_SIGN, bExpSign? XML_TRUE : XML_FALSE ); } // exponent string // Export only for 1.x with extensions if (eVersion & SvtSaveOptions::ODFSVER_EXTENDED) { if (bExponentLowercase) m_rExport.AddAttribute( XML_NAMESPACE_LO_EXT, XML_EXPONENT_LOWERCASE, XML_TRUE ); if (nBlankExp > 0) { if (nBlankExp >= nExp) nBlankExp = nExp - 1; // preserve at least one '0' in exponent m_rExport.AddAttribute( XML_NAMESPACE_LO_EXT, XML_BLANK_EXPONENT_DIGITS, OUString::number( nBlankExp ) ); } } SvXMLElementExport aElem( m_rExport, XML_NAMESPACE_NUMBER, XML_SCIENTIFIC_NUMBER, true, false ); // number:embedded-text as child elements // Export only for 1.x with extensions if (eVersion & SvtSaveOptions::ODFSVER_EXTENDED) WriteEmbeddedEntries_Impl( rEmbeddedEntries ); } void SvXMLNumFmtExport::WriteFractionElement_Impl( sal_Int32 nInteger, sal_Int32 nBlankInteger, bool bGrouping, const SvNumberformat& rFormat, sal_uInt16 nPart ) { FinishTextElement_Impl(); WriteIntegerElement_Impl( nInteger, nBlankInteger, bGrouping ); const OUString aNumeratorString = rFormat.GetNumeratorString( nPart ); const OUString aDenominatorString = rFormat.GetDenominatorString( nPart ); const OUString aIntegerFractionDelimiterString = rFormat.GetIntegerFractionDelimiterString( nPart ); sal_Int32 nMaxNumeratorDigits = aNumeratorString.getLength(); // Count '0' as '?' sal_Int32 nMinNumeratorDigits = aNumeratorString.replaceAll("0","?").indexOf('?'); sal_Int32 nZerosNumeratorDigits = aNumeratorString.indexOf('0'); if ( nMinNumeratorDigits >= 0 ) nMinNumeratorDigits = nMaxNumeratorDigits - nMinNumeratorDigits; else nMinNumeratorDigits = 0; if ( nZerosNumeratorDigits >= 0 ) nZerosNumeratorDigits = nMaxNumeratorDigits - nZerosNumeratorDigits; else nZerosNumeratorDigits = 0; sal_Int32 nMaxDenominatorDigits = aDenominatorString.getLength(); sal_Int32 nMinDenominatorDigits = aDenominatorString.replaceAll("0","?").indexOf('?'); sal_Int32 nZerosDenominatorDigits = aDenominatorString.indexOf('0'); if ( nMinDenominatorDigits >= 0 ) nMinDenominatorDigits = nMaxDenominatorDigits - nMinDenominatorDigits; else nMinDenominatorDigits = 0; if ( nZerosDenominatorDigits >= 0 ) nZerosDenominatorDigits = nMaxDenominatorDigits - nZerosDenominatorDigits; else nZerosDenominatorDigits = 0; sal_Int32 nDenominator = aDenominatorString.toInt32(); SvtSaveOptions::ODFSaneDefaultVersion eVersion = m_rExport.getSaneDefaultVersion(); // integer/fraction delimiter if ( !aIntegerFractionDelimiterString.isEmpty() && aIntegerFractionDelimiterString != " " && ((eVersion & SvtSaveOptions::ODFSVER_EXTENDED) != 0) ) { // Export only for 1.2/1.3 with extensions. m_rExport.AddAttribute( XML_NAMESPACE_LO_EXT, XML_INTEGER_FRACTION_DELIMITER, aIntegerFractionDelimiterString ); } // numerator digits if ( nMinNumeratorDigits == 0 ) // at least one digit to keep compatibility with previous versions nMinNumeratorDigits++; m_rExport.AddAttribute( XML_NAMESPACE_NUMBER, XML_MIN_NUMERATOR_DIGITS, OUString::number( nMinNumeratorDigits ) ); // Export only for 1.2/1.3 with extensions. if ((eVersion & SvtSaveOptions::ODFSVER_EXTENDED) != 0) { // For extended ODF use loext namespace m_rExport.AddAttribute( XML_NAMESPACE_LO_EXT, XML_MAX_NUMERATOR_DIGITS, OUString::number( nMaxNumeratorDigits ) ); } if ( nZerosNumeratorDigits && ((eVersion & SvtSaveOptions::ODFSVER_EXTENDED) != 0) ) m_rExport.AddAttribute( XML_NAMESPACE_LO_EXT, XML_ZEROS_NUMERATOR_DIGITS, OUString::number( nZerosNumeratorDigits ) ); if ( nDenominator ) { m_rExport.AddAttribute( XML_NAMESPACE_NUMBER, XML_DENOMINATOR_VALUE, OUString::number( nDenominator) ); } // it's not necessary to export nDenominatorDigits // if we have a forced denominator else { if ( nMinDenominatorDigits == 0 ) // at least one digit to keep compatibility with previous versions nMinDenominatorDigits++; m_rExport.AddAttribute( XML_NAMESPACE_NUMBER, XML_MIN_DENOMINATOR_DIGITS, OUString::number( nMinDenominatorDigits ) ); if (eVersion > SvtSaveOptions::ODFSVER_012) { // OFFICE-3695 For 1.2+ use loext namespace, for 1.3 use number namespace. m_rExport.AddAttribute( ((eVersion < SvtSaveOptions::ODFSVER_013) ? XML_NAMESPACE_LO_EXT : XML_NAMESPACE_NUMBER), XML_MAX_DENOMINATOR_VALUE, OUString::number( pow ( 10.0, nMaxDenominatorDigits ) - 1 ) ); // 9, 99 or 999 } if ( nZerosDenominatorDigits && ((eVersion & SvtSaveOptions::ODFSVER_EXTENDED) != 0) ) m_rExport.AddAttribute( XML_NAMESPACE_LO_EXT, XML_ZEROS_DENOMINATOR_DIGITS, OUString::number( nZerosDenominatorDigits ) ); } SvXMLElementExport aElem( m_rExport, XML_NAMESPACE_NUMBER, XML_FRACTION, true, false ); } // mapping (condition) void SvXMLNumFmtExport::WriteMapElement_Impl( sal_Int32 nOp, double fLimit, sal_Int32 nKey, sal_Int32 nPart ) { FinishTextElement_Impl(); if ( nOp == NUMBERFORMAT_OP_NO ) return; // style namespace OUStringBuffer aCondStr(20); aCondStr.append( "value()" ); //! define constant switch ( nOp ) { case NUMBERFORMAT_OP_EQ: aCondStr.append( '=' ); break; case NUMBERFORMAT_OP_NE: aCondStr.append( "!=" ); break; case NUMBERFORMAT_OP_LT: aCondStr.append( '<' ); break; case NUMBERFORMAT_OP_LE: aCondStr.append( "<=" ); break; case NUMBERFORMAT_OP_GT: aCondStr.append( '>' ); break; case NUMBERFORMAT_OP_GE: aCondStr.append( ">=" ); break; default: OSL_FAIL("unknown operator"); } ::rtl::math::doubleToUStringBuffer( aCondStr, fLimit, rtl_math_StringFormat_Automatic, rtl_math_DecimalPlaces_Max, '.', true ); m_rExport.AddAttribute( XML_NAMESPACE_STYLE, XML_CONDITION, aCondStr.makeStringAndClear() ); m_rExport.AddAttribute( XML_NAMESPACE_STYLE, XML_APPLY_STYLE_NAME, m_rExport.EncodeStyleName( lcl_CreateStyleName( nKey, nPart, false, m_sPrefix ) ) ); SvXMLElementExport aElem( m_rExport, XML_NAMESPACE_STYLE, XML_MAP, true, false ); } // for old (automatic) currency formats: parse currency symbol from text static sal_Int32 lcl_FindSymbol( const OUString& sUpperStr, std::u16string_view sCurString ) { // search for currency symbol // Quoting as in ImpSvNumberformatScan::Symbol_Division sal_Int32 nCPos = 0; while (nCPos >= 0) { nCPos = sUpperStr.indexOf( sCurString, nCPos ); if (nCPos >= 0) { // in Quotes? sal_Int32 nQ = SvNumberformat::GetQuoteEnd( sUpperStr, nCPos ); if ( nQ < 0 ) { // dm can be escaped as "dm or \d sal_Unicode c; if ( nCPos == 0 ) return nCPos; // found c = sUpperStr[nCPos-1]; if ( c != '"' && c != '\\') { return nCPos; // found } else { nCPos++; // continue } } else { nCPos = nQ + 1; // continue after quote end } } } return -1; } bool SvXMLNumFmtExport::WriteTextWithCurrency_Impl( const OUString& rString, const css::lang::Locale& rLocale ) { // returns true if currency element was written bool bRet = false; LanguageTag aLanguageTag( rLocale ); m_pFormatter->ChangeIntl( aLanguageTag.getLanguageType( false) ); OUString sCurString, sDummy; m_pFormatter->GetCompatibilityCurrency( sCurString, sDummy ); OUString sUpperStr = m_pFormatter->GetCharClass()->uppercase(rString); sal_Int32 nPos = lcl_FindSymbol( sUpperStr, sCurString ); if ( nPos >= 0 ) { sal_Int32 nLength = rString.getLength(); sal_Int32 nCurLen = sCurString.getLength(); sal_Int32 nCont = nPos + nCurLen; // text before currency symbol if ( nPos > 0 ) { AddToTextElement_Impl( rString.subView( 0, nPos ) ); } // currency symbol (empty string -> default) WriteCurrencyElement_Impl( u""_ustr, u"" ); bRet = true; // text after currency symbol if ( nCont < nLength ) { AddToTextElement_Impl( rString.subView( nCont, nLength-nCont ) ); } } else { AddToTextElement_Impl( rString ); // simple text } return bRet; // true: currency element written } static OUString lcl_GetDefaultCalendar( SvNumberFormatter const * pFormatter, LanguageType nLang ) { // get name of first non-gregorian calendar for the language OUString aCalendar; CalendarWrapper* pCalendar = pFormatter->GetCalendar(); if (pCalendar) { lang::Locale aLocale( LanguageTag::convertToLocale( nLang ) ); const uno::Sequence aCals = pCalendar->getAllCalendars( aLocale ); auto pCal = std::find_if(aCals.begin(), aCals.end(), [](const OUString& rCal) { return rCal != "gregorian"; }); if (pCal != aCals.end()) aCalendar = *pCal; } return aCalendar; } static bool lcl_IsInEmbedded( const SvXMLEmbeddedTextEntryArr& rEmbeddedEntries, sal_uInt16 nPos ) { auto nCount = rEmbeddedEntries.size(); for (decltype(nCount) i=0; i no default date format bEnd = true; // end of format reached break; case NF_SYMBOLTYPE_STRING: case NF_SYMBOLTYPE_DATESEP: case NF_SYMBOLTYPE_TIMESEP: case NF_SYMBOLTYPE_TIME100SECSEP: // text is ignored, except at the end break; // same mapping as in SvXMLNumFormatContext::AddNfKeyword: case NF_KEY_NN: eDateDOW = XML_DEA_SHORT; break; case NF_KEY_NNN: case NF_KEY_NNNN: eDateDOW = XML_DEA_LONG; break; case NF_KEY_D: eDateDay = XML_DEA_SHORT; break; case NF_KEY_DD: eDateDay = XML_DEA_LONG; break; case NF_KEY_M: eDateMonth = XML_DEA_SHORT; break; case NF_KEY_MM: eDateMonth = XML_DEA_LONG; break; case NF_KEY_MMM: eDateMonth = XML_DEA_TEXTSHORT; break; case NF_KEY_MMMM: eDateMonth = XML_DEA_TEXTLONG; break; case NF_KEY_YY: eDateYear = XML_DEA_SHORT; break; case NF_KEY_YYYY: eDateYear = XML_DEA_LONG; break; case NF_KEY_H: eDateHours = XML_DEA_SHORT; break; case NF_KEY_HH: eDateHours = XML_DEA_LONG; break; case NF_KEY_MI: eDateMins = XML_DEA_SHORT; break; case NF_KEY_MMI: eDateMins = XML_DEA_LONG; break; case NF_KEY_S: eDateSecs = XML_DEA_SHORT; break; case NF_KEY_SS: 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: bDateNoDefault = true; // any other element -> no default format } nLastType = nElemType; ++nPos; } if ( bDateNoDefault ) return false; // additional elements else { NfIndexTableOffset eFound = static_cast(SvXMLNumFmtDefaults::GetDefaultDateFormat( eDateDOW, eDateDay, eDateMonth, eDateYear, eDateHours, eDateMins, eDateSecs, bSystemDate )); return ( eFound == eBuiltIn ); } } // export one part (condition) void SvXMLNumFmtExport::ExportPart_Impl( const SvNumberformat& rFormat, sal_uInt32 nKey, sal_uInt32 nRealKey, sal_uInt16 nPart, bool bDefPart ) { //! for the default part, pass the conditions from the other parts! // element name NfIndexTableOffset eBuiltIn = SvNumberFormatter::GetIndexTableOffset( nRealKey ); SvNumFormatType nFmtType = SvNumFormatType::ALL; bool bThousand = false; sal_uInt16 nPrecision = 0; sal_uInt16 nLeading = 0; rFormat.GetNumForInfo( nPart, nFmtType, bThousand, nPrecision, nLeading); nFmtType &= ~SvNumFormatType::DEFINED; // special treatment of builtin formats that aren't detected by normal parsing // (the same formats that get the type set in SvNumberFormatter::ImpGenerateFormats) if ( eBuiltIn == NF_NUMBER_STANDARD ) nFmtType = SvNumFormatType::NUMBER; else if ( eBuiltIn == NF_BOOLEAN ) nFmtType = SvNumFormatType::LOGICAL; else if ( eBuiltIn == NF_TEXT ) nFmtType = SvNumFormatType::TEXT; // #101606# An empty subformat is a valid number-style resulting in an // empty display string for the condition of the subformat. XMLTokenEnum eType = XML_TOKEN_INVALID; switch ( nFmtType ) { // Type UNDEFINED likely is a crappy format string for that we could // not decide on any format type (and maybe could try harder?), but the // resulting XMLTokenEnum should be something valid, so make that // number-style. case SvNumFormatType::UNDEFINED: SAL_WARN("xmloff.style","UNDEFINED number format: '" << rFormat.GetFormatstring() << "'"); [[fallthrough]]; // Type is 0 if a format contains no recognized elements // (like text only) - this is handled as a number-style. case SvNumFormatType::ALL: case SvNumFormatType::EMPTY: case SvNumFormatType::NUMBER: case SvNumFormatType::SCIENTIFIC: case SvNumFormatType::FRACTION: eType = XML_NUMBER_STYLE; break; case SvNumFormatType::PERCENT: eType = XML_PERCENTAGE_STYLE; break; case SvNumFormatType::CURRENCY: eType = XML_CURRENCY_STYLE; break; case SvNumFormatType::DATE: case SvNumFormatType::DATETIME: eType = XML_DATE_STYLE; break; case SvNumFormatType::TIME: eType = XML_TIME_STYLE; break; case SvNumFormatType::TEXT: eType = XML_TEXT_STYLE; break; case SvNumFormatType::LOGICAL: eType = XML_BOOLEAN_STYLE; break; default: break; } SAL_WARN_IF( eType == XML_TOKEN_INVALID, "xmloff.style", "unknown format type" ); OUString sAttrValue; bool bUserDef( rFormat.GetType() & SvNumFormatType::DEFINED ); // common attributes for format // format name (generated from key) - style namespace m_rExport.AddAttribute( XML_NAMESPACE_STYLE, XML_NAME, lcl_CreateStyleName( nKey, nPart, bDefPart, m_sPrefix ) ); // "volatile" attribute for styles used only in maps if ( !bDefPart ) m_rExport.AddAttribute( XML_NAMESPACE_STYLE, XML_VOLATILE, XML_TRUE ); // language / country LanguageType nLang = rFormat.GetLanguage(); AddLanguageAttr_Impl( nLang ); // adds to pAttrList // title (comment) // titles for builtin formats are not written sAttrValue = rFormat.GetComment(); if ( !sAttrValue.isEmpty() && bUserDef && bDefPart ) { m_rExport.AddAttribute( XML_NAMESPACE_NUMBER, XML_TITLE, sAttrValue ); } // automatic ordering for currency and date formats // only used for some built-in formats bool bAutoOrder = ( eBuiltIn == NF_CURRENCY_1000INT || eBuiltIn == NF_CURRENCY_1000DEC2 || eBuiltIn == NF_CURRENCY_1000INT_RED || eBuiltIn == NF_CURRENCY_1000DEC2_RED || eBuiltIn == NF_CURRENCY_1000DEC2_DASHED || eBuiltIn == NF_DATE_SYSTEM_SHORT || eBuiltIn == NF_DATE_SYSTEM_LONG || eBuiltIn == NF_DATE_SYS_MMYY || eBuiltIn == NF_DATE_SYS_DDMMM || eBuiltIn == NF_DATE_SYS_DDMMYYYY || eBuiltIn == NF_DATE_SYS_DDMMYY || eBuiltIn == NF_DATE_SYS_DMMMYY || eBuiltIn == NF_DATE_SYS_DMMMYYYY || eBuiltIn == NF_DATE_SYS_DMMMMYYYY || eBuiltIn == NF_DATE_SYS_NNDMMMYY || eBuiltIn == NF_DATE_SYS_NNDMMMMYYYY || eBuiltIn == NF_DATE_SYS_NNNNDMMMMYYYY || eBuiltIn == NF_DATETIME_SYSTEM_SHORT_HHMM || eBuiltIn == NF_DATETIME_SYS_DDMMYYYY_HHMM || eBuiltIn == NF_DATETIME_SYS_DDMMYYYY_HHMMSS ); // format source (for date and time formats) // only used for some built-in formats bool bSystemDate = ( eBuiltIn == NF_DATE_SYSTEM_SHORT || eBuiltIn == NF_DATE_SYSTEM_LONG || eBuiltIn == NF_DATETIME_SYSTEM_SHORT_HHMM ); bool bLongSysDate = ( eBuiltIn == NF_DATE_SYSTEM_LONG ); // check if the format definition matches the key if ( bAutoOrder && ( nFmtType == SvNumFormatType::DATE || nFmtType == SvNumFormatType::DATETIME ) && !lcl_IsDefaultDateFormat( rFormat, bSystemDate, eBuiltIn ) ) { bAutoOrder = bSystemDate = bLongSysDate = false; // don't write automatic-order attribute then } if ( bAutoOrder && ( nFmtType == SvNumFormatType::CURRENCY || nFmtType == SvNumFormatType::DATE || nFmtType == SvNumFormatType::DATETIME ) ) { // #85109# format type must be checked to avoid dtd errors if // locale data contains other format types at the built-in positions m_rExport.AddAttribute( XML_NAMESPACE_NUMBER, XML_AUTOMATIC_ORDER, XML_TRUE ); } if ( bSystemDate && bAutoOrder && ( nFmtType == SvNumFormatType::DATE || nFmtType == SvNumFormatType::DATETIME ) ) { // #85109# format type must be checked to avoid dtd errors if // locale data contains other format types at the built-in positions m_rExport.AddAttribute( XML_NAMESPACE_NUMBER, XML_FORMAT_SOURCE, XML_LANGUAGE ); } // overflow for time formats as in [hh]:mm // controlled by bThousand from number format info // default for truncate-on-overflow is true if ( nFmtType == SvNumFormatType::TIME && bThousand ) { m_rExport.AddAttribute( XML_NAMESPACE_NUMBER, XML_TRUNCATE_ON_OVERFLOW, XML_FALSE ); } // Native number transliteration css::i18n::NativeNumberXmlAttributes2 aAttr; rFormat.GetNatNumXml( aAttr, nPart, m_pFormatter->GetNatNum() ); if ( !aAttr.Format.isEmpty() ) { assert(aAttr.Spellout.isEmpty()); // mutually exclusive /* FIXME-BCP47: ODF defines no transliteration-script or * transliteration-rfc-language-tag */ LanguageTag aLanguageTag( aAttr.Locale); OUString aLanguage, aScript, aCountry; aLanguageTag.getIsoLanguageScriptCountry( aLanguage, aScript, aCountry); m_rExport.AddAttribute( XML_NAMESPACE_NUMBER, XML_TRANSLITERATION_FORMAT, aAttr.Format ); m_rExport.AddAttribute( XML_NAMESPACE_NUMBER, XML_TRANSLITERATION_LANGUAGE, aLanguage ); m_rExport.AddAttribute( XML_NAMESPACE_NUMBER, XML_TRANSLITERATION_COUNTRY, aCountry ); m_rExport.AddAttribute( XML_NAMESPACE_NUMBER, XML_TRANSLITERATION_STYLE, aAttr.Style ); } SvtSaveOptions::ODFSaneDefaultVersion eVersion = m_rExport.getSaneDefaultVersion(); if ( !aAttr.Spellout.isEmpty() ) { const bool bWriteSpellout = aAttr.Format.isEmpty(); assert(bWriteSpellout); // mutually exclusive // Export only for 1.2 and later with extensions // Also ensure that duplicated transliteration-language and // transliteration-country attributes never escape into the wild with // releases. if ( (eVersion & SvtSaveOptions::ODFSVER_EXTENDED) && bWriteSpellout ) { /* FIXME-BCP47: ODF defines no transliteration-script or * transliteration-rfc-language-tag */ LanguageTag aLanguageTag( aAttr.Locale); OUString aLanguage, aScript, aCountry; aLanguageTag.getIsoLanguageScriptCountry( aLanguage, aScript, aCountry); // For 1.2/1.3+ use loext namespace. m_rExport.AddAttribute( /*((eVersion < SvtSaveOptions::ODFSVER_) ? */ XML_NAMESPACE_LO_EXT /*: XML_NAMESPACE_NUMBER)*/, XML_TRANSLITERATION_SPELLOUT, aAttr.Spellout ); m_rExport.AddAttribute( XML_NAMESPACE_NUMBER, XML_TRANSLITERATION_LANGUAGE, aLanguage ); m_rExport.AddAttribute( XML_NAMESPACE_NUMBER, XML_TRANSLITERATION_COUNTRY, aCountry ); } } // The element SvXMLElementExport aElem( m_rExport, XML_NAMESPACE_NUMBER, eType, true, true ); // color (properties element) const Color* pCol = rFormat.GetColor( nPart ); if (pCol) WriteColorElement_Impl(*pCol); // detect if there is "real" content, excluding color and maps //! move to implementation of Write... methods? bool bAnyContent = false; // format elements SvXMLEmbeddedTextEntryArr aEmbeddedEntries; if ( eBuiltIn == NF_NUMBER_STANDARD ) { // default number format contains just one number element WriteNumberElement_Impl( -1, -1, 1, -1, OUString(), false, 0, aEmbeddedEntries ); bAnyContent = true; } else if ( eBuiltIn == NF_BOOLEAN ) { // boolean format contains just one boolean element WriteBooleanElement_Impl(); bAnyContent = true; } else if (eType == XML_BOOLEAN_STYLE) { // may contain only and // elements. sal_uInt16 nPos = 0; bool bEnd = false; while (!bEnd) { const short nElemType = rFormat.GetNumForType( nPart, nPos ); switch (nElemType) { case 0: bEnd = true; // end of format reached if (m_bHasText && m_sTextContent.isEmpty()) m_bHasText = false; // don't write trailing empty text break; case NF_SYMBOLTYPE_STRING: { const OUString* pElemStr = rFormat.GetNumForString( nPart, nPos ); if (pElemStr) AddToTextElement_Impl( *pElemStr ); } break; case NF_KEY_BOOLEAN: WriteBooleanElement_Impl(); bAnyContent = true; break; } ++nPos; } } else { // first loop to collect attributes bool bDecDashes = false; bool bExpFound = false; bool bCurrFound = false; bool bInInteger = true; bool bExpSign = true; bool bExponentLowercase = false; // 'e' or 'E' for scientific notation bool bDecAlign = false; // decimal alignment with "?" sal_Int32 nExpDigits = 0; // '0' and '?' in exponent sal_Int32 nBlankExp = 0; // only '?' in exponent sal_Int32 nIntegerSymbols = 0; // for embedded-text, including "#" sal_Int32 nTrailingThousands = 0; // thousands-separators after all digits sal_Int32 nMinDecimals = nPrecision; sal_Int32 nBlankInteger = 0; OUString sCurrExt; OUString aCalendar; bool bImplicitOtherCalendar = false; bool bExplicitCalendar = false; sal_uInt16 nPos = 0; bool bEnd = false; while (!bEnd) { short nElemType = rFormat.GetNumForType( nPart, nPos ); const OUString* pElemStr = rFormat.GetNumForString( nPart, nPos ); switch ( nElemType ) { case 0: bEnd = true; // end of format reached break; case NF_SYMBOLTYPE_DIGIT: if ( bExpFound && pElemStr ) { nExpDigits += pElemStr->getLength(); for ( sal_Int32 i = pElemStr->getLength()-1; i >= 0 ; i-- ) { if ( (*pElemStr)[i] == '?' ) nBlankExp ++; } } else if ( !bDecDashes && pElemStr && (*pElemStr)[0] == '-' ) { bDecDashes = true; nMinDecimals = 0; } else if ( nFmtType != SvNumFormatType::FRACTION && !bInInteger && pElemStr ) { for ( sal_Int32 i = pElemStr->getLength()-1; i >= 0 ; i-- ) { sal_Unicode aChar = (*pElemStr)[i]; if ( aChar == '#' || aChar == '?' ) { nMinDecimals --; if ( aChar == '?' ) bDecAlign = true; } else break; } } if ( bInInteger && pElemStr ) { nIntegerSymbols += pElemStr->getLength(); for ( sal_Int32 i = pElemStr->getLength()-1; i >= 0 ; i-- ) { if ( (*pElemStr)[i] == '?' ) nBlankInteger ++; } } nTrailingThousands = 0; break; case NF_SYMBOLTYPE_FRACBLANK: case NF_SYMBOLTYPE_DECSEP: bInInteger = false; break; case NF_SYMBOLTYPE_THSEP: if (pElemStr) nTrailingThousands += pElemStr->getLength(); // is reset to 0 if digits follow break; case NF_SYMBOLTYPE_EXP: bExpFound = true; // following digits are exponent digits bInInteger = false; if ( pElemStr && ( pElemStr->getLength() == 1 || ( pElemStr->getLength() == 2 && (*pElemStr)[1] == '-' ) ) ) bExpSign = false; // for 0.00E0 or 0.00E-00 if ( pElemStr && (*pElemStr)[0] == 'e' ) bExponentLowercase = true; // for 0.00e+00 break; case NF_SYMBOLTYPE_CURRENCY: bCurrFound = true; break; case NF_SYMBOLTYPE_CURREXT: if (pElemStr) sCurrExt = *pElemStr; break; // E, EE, R, RR: select non-gregorian calendar // AAA, AAAA: calendar is switched at the position of the element case NF_KEY_EC: case NF_KEY_EEC: case NF_KEY_R: case NF_KEY_RR: if (aCalendar.isEmpty()) { aCalendar = lcl_GetDefaultCalendar( m_pFormatter, nLang ); bImplicitOtherCalendar = true; } break; } ++nPos; } // collect strings for embedded-text (must be known before number element is written) bool bAllowEmbedded = ( nFmtType == SvNumFormatType::ALL || nFmtType == SvNumFormatType::NUMBER || nFmtType == SvNumFormatType::CURRENCY || // Export only for 1.x with extensions ( nFmtType == SvNumFormatType::SCIENTIFIC && (eVersion & SvtSaveOptions::ODFSVER_EXTENDED) )|| nFmtType == SvNumFormatType::PERCENT ); if ( bAllowEmbedded ) { sal_Int32 nDigitsPassed = 0; sal_Int32 nEmbeddedPositionsMax = nIntegerSymbols; // Enable embedded text in decimal part only if there's a decimal part if ( nPrecision ) nEmbeddedPositionsMax += nPrecision + 1; // Enable embedded text in exponent in scientific number if ( nFmtType == SvNumFormatType::SCIENTIFIC ) nEmbeddedPositionsMax += 1 + nExpDigits; nPos = 0; bEnd = false; bExpFound = false; while (!bEnd) { short nElemType = rFormat.GetNumForType( nPart, nPos ); const OUString* pElemStr = rFormat.GetNumForString( nPart, nPos ); switch ( nElemType ) { case 0: bEnd = true; // end of format reached break; case NF_SYMBOLTYPE_DIGIT: if ( pElemStr ) nDigitsPassed += pElemStr->getLength(); break; case NF_SYMBOLTYPE_EXP: bExpFound = true; [[fallthrough]]; case NF_SYMBOLTYPE_DECSEP: nDigitsPassed++; break; case NF_SYMBOLTYPE_STRING: case NF_SYMBOLTYPE_BLANK: case NF_SYMBOLTYPE_PERCENT: if ( 0 < nDigitsPassed && nDigitsPassed < nEmbeddedPositionsMax && pElemStr ) { // text (literal or underscore) within the integer (>=0) or decimal (<0) part of a number:number element OUString aEmbeddedStr; bool bSaveBlankWidthSymbol = false; if ( nElemType == NF_SYMBOLTYPE_STRING || nElemType == NF_SYMBOLTYPE_PERCENT ) { aEmbeddedStr = *pElemStr; } else if (pElemStr->getLength() >= 2) { if ( eVersion > SvtSaveOptions::ODFSVER_013 && ( (eVersion & SvtSaveOptions::ODFSVER_EXTENDED) != 0 ) ) { aEmbeddedStr = pElemStr->copy( 1, 1 ); bSaveBlankWidthSymbol = true; } else // turn "_x" into the number of spaces used for x in InsertBlanks in the NumberFormat SvNumberformat::InsertBlanks( aEmbeddedStr, 0, (*pElemStr)[1] ); } sal_Int32 nEmbedPos = nIntegerSymbols - nDigitsPassed; aEmbeddedEntries.push_back( SvXMLEmbeddedTextEntry( nPos, nEmbedPos, aEmbeddedStr, bSaveBlankWidthSymbol )); // exponent sign is required with embedded text in exponent if ( bExpFound && !bExpSign ) bExpSign = true; } break; } ++nPos; } } // final loop to write elements bool bNumWritten = false; bool bCurrencyWritten = false; short nPrevType = 0; nPos = 0; bEnd = false; while (!bEnd) { short nElemType = rFormat.GetNumForType( nPart, nPos ); const OUString* pElemStr = rFormat.GetNumForString( nPart, nPos ); switch ( nElemType ) { case 0: bEnd = true; // end of format reached if (m_bHasText && m_sTextContent.isEmpty()) m_bHasText = false; // don't write trailing empty text break; case NF_SYMBOLTYPE_STRING: case NF_SYMBOLTYPE_DATESEP: case NF_SYMBOLTYPE_TIMESEP: case NF_SYMBOLTYPE_TIME100SECSEP: case NF_SYMBOLTYPE_PERCENT: if (pElemStr) { if ( ( nElemType == NF_SYMBOLTYPE_TIME100SECSEP ) && ( nPrevType == NF_KEY_S || nPrevType == NF_KEY_SS || ( nPos > 0 && (*rFormat.GetNumForString( nPart, nPos-1 ))[0] == ']' && ( nFmtType == SvNumFormatType::TIME || nFmtType == SvNumFormatType::DATETIME ) ) ) && nPrecision > 0 ) { // decimal separator after seconds or [SS] is implied by // "decimal-places" attribute and must not be written // as text element //! difference between '.' and ',' is lost here } else if ( lcl_IsInEmbedded( aEmbeddedEntries, nPos ) ) { // text is written as embedded-text child of the number, // don't create a text element } else if ( nFmtType == SvNumFormatType::CURRENCY && !bCurrFound && !bCurrencyWritten ) { // automatic currency symbol is implemented as part of // normal text -> search for the symbol bCurrencyWritten = WriteTextWithCurrency_Impl( *pElemStr, LanguageTag::convertToLocale( nLang ) ); bAnyContent = true; } else AddToTextElement_Impl( *pElemStr ); } break; case NF_SYMBOLTYPE_BLANK: if ( pElemStr && !lcl_IsInEmbedded( aEmbeddedEntries, nPos ) ) { if ( pElemStr->getLength() == 2 ) { OUString aBlankWidthChar = pElemStr->copy( 1 ); lcl_WriteBlankWidthString( aBlankWidthChar, m_sBlankWidthString, m_sTextContent ); m_bHasText = true; } } break; case NF_KEY_GENERAL : WriteNumberElement_Impl( -1, -1, 1, -1, OUString(), false, 0, aEmbeddedEntries ); bAnyContent = true; break; case NF_KEY_CCC: if (pElemStr) { if ( bCurrencyWritten ) AddToTextElement_Impl( *pElemStr ); // never more than one currency element else { //! must be different from short automatic format //! but should still be empty (meaning automatic) // pElemStr is "CCC" WriteCurrencyElement_Impl( *pElemStr, u"" ); bAnyContent = true; bCurrencyWritten = true; } } break; case NF_SYMBOLTYPE_CURRENCY: if (pElemStr) { if ( bCurrencyWritten ) AddToTextElement_Impl( *pElemStr ); // never more than one currency element else { WriteCurrencyElement_Impl( *pElemStr, sCurrExt ); bAnyContent = true; bCurrencyWritten = true; } } break; case NF_SYMBOLTYPE_DIGIT: if (!bNumWritten) // write number part { switch ( nFmtType ) { // for type 0 (not recognized as a special type), // write a "normal" number case SvNumFormatType::ALL: case SvNumFormatType::NUMBER: case SvNumFormatType::CURRENCY: case SvNumFormatType::PERCENT: { // decimals // only some built-in formats have automatic decimals sal_Int32 nDecimals = nPrecision; // from GetFormatSpecialInfo if ( eBuiltIn == NF_NUMBER_STANDARD || eBuiltIn == NF_CURRENCY_1000DEC2 || eBuiltIn == NF_CURRENCY_1000DEC2_RED || eBuiltIn == NF_CURRENCY_1000DEC2_CCC || eBuiltIn == NF_CURRENCY_1000DEC2_DASHED ) nDecimals = -1; // integer digits // only one built-in format has automatic integer digits sal_Int32 nInteger = nLeading; if ( eBuiltIn == NF_NUMBER_SYSTEM ) { nInteger = -1; nBlankInteger = -1; } // string for decimal replacement // has to be taken from nPrecision // (positive number even for automatic decimals) OUStringBuffer sDashStr; if (bDecDashes && nPrecision > 0) comphelper::string::padToLength(sDashStr, nPrecision, '-'); // "?" in decimal part are replaced by space character if (bDecAlign && nPrecision > 0) sDashStr = " "; WriteNumberElement_Impl(nDecimals, nMinDecimals, nInteger, nBlankInteger, sDashStr.makeStringAndClear(), bThousand, nTrailingThousands, aEmbeddedEntries); bAnyContent = true; } break; case SvNumFormatType::SCIENTIFIC: // #i43959# for scientific numbers, count all integer symbols ("0", "?" and "#") // as integer digits: use nIntegerSymbols instead of nLeading // nIntegerSymbols represents exponent interval (for engineering notation) WriteScientificElement_Impl( nPrecision, nMinDecimals, nLeading, nBlankInteger, bThousand, nExpDigits, nIntegerSymbols, bExpSign, bExponentLowercase, nBlankExp, aEmbeddedEntries ); bAnyContent = true; break; case SvNumFormatType::FRACTION: { sal_Int32 nInteger = nLeading; if ( rFormat.GetNumForNumberElementCount( nPart ) == 3 ) { // If there is only two numbers + fraction in format string // the fraction doesn't have an integer part, and no // min-integer-digits attribute must be written. nInteger = -1; nBlankInteger = -1; } WriteFractionElement_Impl( nInteger, nBlankInteger, bThousand, rFormat, nPart ); bAnyContent = true; } break; default: break; } bNumWritten = true; } break; case NF_SYMBOLTYPE_DECSEP: if ( pElemStr && nPrecision == 0 ) { // A decimal separator after the number, without following decimal digits, // isn't modelled as part of the number element, so it's written as text // (the distinction between a quoted and non-quoted, locale-dependent // character is lost here). AddToTextElement_Impl( *pElemStr ); } break; case NF_SYMBOLTYPE_DEL: if ( pElemStr && *pElemStr == "@" ) { WriteTextContentElement_Impl(); bAnyContent = true; } break; case NF_SYMBOLTYPE_CALENDAR: if ( pElemStr ) { aCalendar = *pElemStr; bExplicitCalendar = true; } break; // date elements: case NF_KEY_D: case NF_KEY_DD: { bool bLong = ( nElemType == NF_KEY_DD ); WriteDayElement_Impl( aCalendar, ( bSystemDate ? bLongSysDate : bLong ) ); bAnyContent = true; } break; case NF_KEY_DDD: case NF_KEY_DDDD: case NF_KEY_NN: case NF_KEY_NNN: case NF_KEY_NNNN: case NF_KEY_AAA: case NF_KEY_AAAA: { OUString aCalAttr = aCalendar; if ( nElemType == NF_KEY_AAA || nElemType == NF_KEY_AAAA ) { // calendar attribute for AAA and AAAA is switched only for this element if (aCalAttr.isEmpty()) aCalAttr = lcl_GetDefaultCalendar( m_pFormatter, nLang ); } bool bLong = ( nElemType == NF_KEY_NNN || nElemType == NF_KEY_NNNN || nElemType == NF_KEY_DDDD || nElemType == NF_KEY_AAAA ); WriteDayOfWeekElement_Impl( aCalAttr, ( bSystemDate ? bLongSysDate : bLong ) ); bAnyContent = true; if ( nElemType == NF_KEY_NNNN ) { // write additional text element for separator m_pLocaleData.reset( new LocaleDataWrapper( m_pFormatter->GetComponentContext(), LanguageTag( nLang ) ) ); AddToTextElement_Impl( m_pLocaleData->getLongDateDayOfWeekSep() ); } } break; case NF_KEY_M: case NF_KEY_MM: case NF_KEY_MMM: case NF_KEY_MMMM: case NF_KEY_MMMMM: //! first letter of month name, no attribute available { bool bLong = ( nElemType == NF_KEY_MM || nElemType == NF_KEY_MMMM ); bool bText = ( nElemType == NF_KEY_MMM || nElemType == NF_KEY_MMMM || nElemType == NF_KEY_MMMMM ); WriteMonthElement_Impl( aCalendar, ( bSystemDate ? bLongSysDate : bLong ), bText ); bAnyContent = true; } break; case NF_KEY_YY: case NF_KEY_YYYY: case NF_KEY_EC: case NF_KEY_EEC: case NF_KEY_R: //! R acts as EE, no attribute available { //! distinguish EE and R // Calendar attribute for E and EE and R is set in // first loop. If set and not an explicit calendar and // YY or YYYY is encountered, switch temporarily to // Gregorian. bool bLong = ( nElemType == NF_KEY_YYYY || nElemType == NF_KEY_EEC || nElemType == NF_KEY_R ); WriteYearElement_Impl( ((bImplicitOtherCalendar && !bExplicitCalendar && (nElemType == NF_KEY_YY || nElemType == NF_KEY_YYYY)) ? u"gregorian"_ustr : aCalendar), (bSystemDate ? bLongSysDate : bLong)); bAnyContent = true; } break; case NF_KEY_G: case NF_KEY_GG: case NF_KEY_GGG: case NF_KEY_RR: //! RR acts as GGGEE, no attribute available { //! distinguish GG and GGG and RR bool bLong = ( nElemType == NF_KEY_GGG || nElemType == NF_KEY_RR ); WriteEraElement_Impl( aCalendar, ( bSystemDate ? bLongSysDate : bLong ) ); bAnyContent = true; if ( nElemType == NF_KEY_RR ) { // calendar attribute for RR is set in first loop WriteYearElement_Impl( aCalendar, ( bSystemDate || bLongSysDate ) ); } } break; case NF_KEY_Q: case NF_KEY_QQ: { bool bLong = ( nElemType == NF_KEY_QQ ); WriteQuarterElement_Impl( aCalendar, ( bSystemDate ? bLongSysDate : bLong ) ); bAnyContent = true; } break; case NF_KEY_WW: WriteWeekElement_Impl( aCalendar ); bAnyContent = true; break; // time elements (bSystemDate is not used): case NF_KEY_H: case NF_KEY_HH: WriteHoursElement_Impl( nElemType == NF_KEY_HH ); bAnyContent = true; break; case NF_KEY_MI: case NF_KEY_MMI: WriteMinutesElement_Impl( nElemType == NF_KEY_MMI ); bAnyContent = true; break; case NF_KEY_S: case NF_KEY_SS: WriteSecondsElement_Impl( ( nElemType == NF_KEY_SS ), nPrecision ); bAnyContent = true; break; case NF_KEY_AMPM: case NF_KEY_AP: WriteAMPMElement_Impl(); // short/long? bAnyContent = true; break; case NF_SYMBOLTYPE_STAR : // export only if ODF 1.2 extensions are enabled if (m_rExport.getSaneDefaultVersion() > SvtSaveOptions::ODFSVER_012) { if ( pElemStr && pElemStr->getLength() > 1 ) WriteRepeatedElement_Impl( (*pElemStr)[1] ); } break; } nPrevType = nElemType; ++nPos; } } if ( !m_sTextContent.isEmpty() ) bAnyContent = true; // element written in FinishTextElement_Impl FinishTextElement_Impl(); // final text element - before maps if ( !bAnyContent ) { // for an empty format, write an empty text element SvXMLElementExport aTElem( m_rExport, XML_NAMESPACE_NUMBER, XML_TEXT, true, false ); } // mapping (conditions) must be last elements if (!bDefPart) return; SvNumberformatLimitOps eOp1, eOp2; double fLimit1, fLimit2; rFormat.GetConditions( eOp1, fLimit1, eOp2, fLimit2 ); WriteMapElement_Impl( eOp1, fLimit1, nKey, 0 ); WriteMapElement_Impl( eOp2, fLimit2, nKey, 1 ); if ( !rFormat.HasTextFormat() ) return; // 4th part is for text -> make an "all other numbers" condition for the 3rd part // by reversing the 2nd condition. // For a trailing text format like 0;@ that has no conditions // use a "less or equal than biggest" condition for the number // part, ODF can't store subformats (style maps) without // conditions. SvNumberformatLimitOps eOp3 = NUMBERFORMAT_OP_NO; double fLimit3 = fLimit2; sal_uInt16 nLastPart = 2; SvNumberformatLimitOps eOpLast = eOp2; if (eOp2 == NUMBERFORMAT_OP_NO) { eOpLast = eOp1; fLimit3 = fLimit1; nLastPart = (eOp1 == NUMBERFORMAT_OP_NO) ? 0 : 1; } switch ( eOpLast ) { case NUMBERFORMAT_OP_EQ: eOp3 = NUMBERFORMAT_OP_NE; break; case NUMBERFORMAT_OP_NE: eOp3 = NUMBERFORMAT_OP_EQ; break; case NUMBERFORMAT_OP_LT: eOp3 = NUMBERFORMAT_OP_GE; break; case NUMBERFORMAT_OP_LE: eOp3 = NUMBERFORMAT_OP_GT; break; case NUMBERFORMAT_OP_GT: eOp3 = NUMBERFORMAT_OP_LE; break; case NUMBERFORMAT_OP_GE: eOp3 = NUMBERFORMAT_OP_LT; break; case NUMBERFORMAT_OP_NO: eOp3 = NUMBERFORMAT_OP_LE; fLimit3 = DBL_MAX; break; } if ( fLimit1 == fLimit2 && ( ( eOp1 == NUMBERFORMAT_OP_LT && eOp2 == NUMBERFORMAT_OP_GT ) || ( eOp1 == NUMBERFORMAT_OP_GT && eOp2 == NUMBERFORMAT_OP_LT ) ) ) { // For x, add =x as last condition // (just for readability, <=x would be valid, too) eOp3 = NUMBERFORMAT_OP_EQ; } WriteMapElement_Impl( eOp3, fLimit3, nKey, nLastPart ); } // export one format void SvXMLNumFmtExport::ExportFormat_Impl( const SvNumberformat& rFormat, sal_uInt32 nKey, sal_uInt32 nRealKey ) { const sal_uInt16 XMLNUM_MAX_PARTS = 4; bool bParts[XMLNUM_MAX_PARTS] = { false, false, false, false }; sal_uInt16 nUsedParts = 0; for (sal_uInt16 nPart=0; nPart no entries sal_uInt32 nKey; const SvNumberformat* pFormat = nullptr; bool bNext(m_pUsedList->GetFirstUsed(nKey)); while(bNext) { // ODF has its notation of system formats, so obtain the "real" already // substituted format but use the original key for style name. sal_uInt32 nRealKey = nKey; pFormat = m_pFormatter->GetSubstitutedEntry( nKey, nRealKey); if(pFormat) ExportFormat_Impl( *pFormat, nKey, nRealKey ); bNext = m_pUsedList->GetNextUsed(nKey); } if (!bIsAutoStyle) { std::vector aLanguages; m_pFormatter->GetUsedLanguages( aLanguages ); for (const auto& nLang : aLanguages) { sal_uInt32 nDefaultIndex = 0; SvNumberFormatTable& rTable = m_pFormatter->GetEntryTable( SvNumFormatType::DEFINED, nDefaultIndex, nLang ); for (const auto& rTableEntry : rTable) { nKey = rTableEntry.first; pFormat = rTableEntry.second; if (!m_pUsedList->IsUsed(nKey)) { DBG_ASSERT((pFormat->GetType() & SvNumFormatType::DEFINED), "a not user defined numberformat found"); sal_uInt32 nRealKey = nKey; if (pFormat->IsSubstituted()) { pFormat = m_pFormatter->GetSubstitutedEntry( nKey, nRealKey); // export the "real" format assert(pFormat); } // user-defined and used formats are exported ExportFormat_Impl( *pFormat, nKey, nRealKey ); // if it is a user-defined Format it will be added else nothing will happen m_pUsedList->SetUsed(nKey); } } } } m_pUsedList->Export(); } OUString SvXMLNumFmtExport::GetStyleName( sal_uInt32 nKey ) { if(m_pUsedList->IsUsed(nKey) || m_pUsedList->IsWasUsed(nKey)) return lcl_CreateStyleName( nKey, 0, true, m_sPrefix ); else { OSL_FAIL("There is no written Data-Style"); return OUString(); } } void SvXMLNumFmtExport::SetUsed( sal_uInt32 nKey ) { SAL_WARN_IF( m_pFormatter == nullptr, "xmloff.style", "missing formatter" ); if( !m_pFormatter ) return; if (m_pFormatter->GetEntry(nKey)) m_pUsedList->SetUsed( nKey ); else { OSL_FAIL("no existing Numberformat found with this key"); } } uno::Sequence SvXMLNumFmtExport::GetWasUsed() const { if (m_pUsedList) return m_pUsedList->GetWasUsed(); return uno::Sequence(); } void SvXMLNumFmtExport::SetWasUsed(const uno::Sequence& rWasUsed) { if (m_pUsedList) m_pUsedList->SetWasUsed(rWasUsed); } static const SvNumberformat* lcl_GetFormat( SvNumberFormatter const * pFormatter, sal_uInt32 nKey ) { return ( pFormatter != nullptr ) ? pFormatter->GetEntry( nKey ) : nullptr; } sal_uInt32 SvXMLNumFmtExport::ForceSystemLanguage( sal_uInt32 nKey ) { sal_uInt32 nRet = nKey; const SvNumberformat* pFormat = lcl_GetFormat( m_pFormatter, nKey ); if( pFormat != nullptr ) { SAL_WARN_IF( m_pFormatter == nullptr, "xmloff.style", "format without formatter?" ); SvNumFormatType nType = pFormat->GetType(); sal_uInt32 nNewKey = m_pFormatter->GetFormatForLanguageIfBuiltIn( nKey, LANGUAGE_SYSTEM ); if( nNewKey != nKey ) { nRet = nNewKey; } else { OUString aFormatString( pFormat->GetFormatstring() ); sal_Int32 nErrorPos; m_pFormatter->PutandConvertEntry( aFormatString, nErrorPos, nType, nNewKey, pFormat->GetLanguage(), LANGUAGE_SYSTEM, true); // success? Then use new key. if( nErrorPos == 0 ) nRet = nNewKey; } } return nRet; } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */