diff options
Diffstat (limited to 'xmloff/source/style/xmlnumfe.cxx')
-rw-r--r-- | xmloff/source/style/xmlnumfe.cxx | 2138 |
1 files changed, 2138 insertions, 0 deletions
diff --git a/xmloff/source/style/xmlnumfe.cxx b/xmloff/source/style/xmlnumfe.cxx new file mode 100644 index 0000000000..406c22236a --- /dev/null +++ b/xmloff/source/style/xmlnumfe.cxx @@ -0,0 +1,2138 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <comphelper/sequence.hxx> +#include <comphelper/string.hxx> +#include <svl/numformat.hxx> +#include <svl/zforlist.hxx> +#include <svl/zformat.hxx> +#include <svl/numuno.hxx> +#include <i18nlangtag/mslangid.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <tools/debug.hxx> +#include <rtl/math.hxx> +#include <unotools/calendarwrapper.hxx> +#include <unotools/charclass.hxx> +#include <com/sun/star/lang/Locale.hpp> +#include <rtl/ustrbuf.hxx> +#include <sal/log.hxx> +#include <osl/diagnose.h> +#include <tools/color.hxx> +#include <sax/tools/converter.hxx> + +#include <com/sun/star/i18n/NativeNumberXmlAttributes2.hpp> + +#include <utility> +#include <xmloff/xmlnumfe.hxx> +#include <xmloff/xmlnamespace.hxx> +#include <xmloff/xmlnumfi.hxx> + +#include <svl/nfsymbol.hxx> +#include <xmloff/xmltoken.hxx> +#include <xmloff/xmlexp.hxx> +#include <o3tl/string_view.hxx> + +#include <float.h> +#include <set> +#include <string_view> +#include <vector> + +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<SvXMLEmbeddedTextEntry> 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<sal_Int32> GetWasUsed() const; + void SetWasUsed(const uno::Sequence<sal_Int32>& 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<SvXMLuInt32Set::iterator, bool> 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<SvXMLuInt32Set::const_iterator, bool> 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<sal_Int32> SvXMLNumUsedList_Impl::GetWasUsed() const +{ + return comphelper::containerToSequence<sal_Int32>(aWasUsed); +} + +void SvXMLNumUsedList_Impl::SetWasUsed(const uno::Sequence<sal_Int32>& rWasUsed) +{ + DBG_ASSERT(nWasUsedCount == 0, "WasUsed should be empty"); + for (const auto nWasUsed : rWasUsed) + { + std::pair<SvXMLuInt32Set::const_iterator, bool> aPair = aWasUsed.insert( nWasUsed ); + if (aPair.second) + nWasUsedCount++; + } +} + +SvXMLNumFmtExport::SvXMLNumFmtExport( + SvXMLExport& rExp, + const uno::Reference< util::XNumberFormatsSupplier >& rSupp ) : + m_rExport( rExp ), + m_sPrefix( OUString("N") ), + m_pFormatter( nullptr ), + m_bHasText( false ) +{ + // supplier must be SvNumberFormatsSupplierObj + SvNumberFormatsSupplierObj* pObj = + comphelper::getFromUnoTunnel<SvNumberFormatsSupplierObj>( 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<SvNumberFormatsSupplierObj>( 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"" ); + 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<OUString> 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<nCount; i++) + if ( rEmbeddedEntries[i].nSourcePos == nPos ) + return true; + + return false; // not found +} + +static bool lcl_IsDefaultDateFormat( const SvNumberformat& rFormat, bool bSystemDate, NfIndexTableOffset eBuiltIn ) +{ + // make an extra loop to collect date elements, to check if it is a default format + // before adding the automatic-order attribute + + SvXMLDateElementAttributes eDateDOW = XML_DEA_NONE; + SvXMLDateElementAttributes eDateDay = XML_DEA_NONE; + SvXMLDateElementAttributes eDateMonth = XML_DEA_NONE; + SvXMLDateElementAttributes eDateYear = XML_DEA_NONE; + SvXMLDateElementAttributes eDateHours = XML_DEA_NONE; + SvXMLDateElementAttributes eDateMins = XML_DEA_NONE; + SvXMLDateElementAttributes eDateSecs = XML_DEA_NONE; + bool bDateNoDefault = false; + + sal_uInt16 nPos = 0; + bool bEnd = false; + short nLastType = 0; + while (!bEnd) + { + short nElemType = rFormat.GetNumForType( 0, nPos ); + switch ( nElemType ) + { + case 0: + if ( nLastType == NF_SYMBOLTYPE_STRING ) + bDateNoDefault = true; // text at the end -> 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<NfIndexTableOffset>(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 = m_pFormatter->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 ); + 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) + { + // <number:boolean-style> may contain only <number:boolean> and + // <number:text> 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)) ? "gregorian" : 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 and >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<XMLNUM_MAX_PARTS; ++nPart) + { + if (rFormat.GetNumForInfoScannedType( nPart) != SvNumFormatType::UNDEFINED) + { + bParts[nPart] = true; + nUsedParts = nPart + 1; + } + } + + SvNumberformatLimitOps eOp1, eOp2; + double fLimit1, fLimit2; + rFormat.GetConditions( eOp1, fLimit1, eOp2, fLimit2 ); + + // if conditions are set, even empty formats must be written + + if ( eOp1 != NUMBERFORMAT_OP_NO ) + { + bParts[1] = true; + if (nUsedParts < 2) + nUsedParts = 2; + } + if ( eOp2 != NUMBERFORMAT_OP_NO ) + { + bParts[2] = true; + if (nUsedParts < 3) + nUsedParts = 3; + } + if ( rFormat.HasTextFormat() ) + { + bParts[3] = true; + if (nUsedParts < 4) + nUsedParts = 4; + } + + for (sal_uInt16 nPart=0; nPart<XMLNUM_MAX_PARTS; ++nPart) + { + if (bParts[nPart]) + { + bool bDefault = ( nPart+1 == nUsedParts ); // last = default + ExportPart_Impl( rFormat, nKey, nRealKey, nPart, bDefault ); + } + } +} + +// export method called by application + +void SvXMLNumFmtExport::Export( bool bIsAutoStyle ) +{ + if ( !m_pFormatter ) + return; // no formatter -> 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<LanguageType> 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<sal_Int32> SvXMLNumFmtExport::GetWasUsed() const +{ + if (m_pUsedList) + return m_pUsedList->GetWasUsed(); + return uno::Sequence<sal_Int32>(); +} + +void SvXMLNumFmtExport::SetWasUsed(const uno::Sequence<sal_Int32>& 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: */ |