summaryrefslogtreecommitdiffstats
path: root/xmloff/source/style/xmlnumfe.cxx
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 05:54:39 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 05:54:39 +0000
commit267c6f2ac71f92999e969232431ba04678e7437e (patch)
tree358c9467650e1d0a1d7227a21dac2e3d08b622b2 /xmloff/source/style/xmlnumfe.cxx
parentInitial commit. (diff)
downloadlibreoffice-267c6f2ac71f92999e969232431ba04678e7437e.tar.xz
libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.zip
Adding upstream version 4:24.2.0.upstream/4%24.2.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'xmloff/source/style/xmlnumfe.cxx')
-rw-r--r--xmloff/source/style/xmlnumfe.cxx2138
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: */