/* -*- 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( u"N"_ustr ),
    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""_ustr, u"" );
        bRet = true;
 
        //  text after currency symbol
        if ( nCont < nLength )
        {
            AddToTextElement_Impl( rString.subView( nCont, nLength-nCont ) );
        }
    }
    else
    {
        AddToTextElement_Impl( rString );       // simple text
    }
 
    return bRet;        // true: currency element written
}
 
static OUString lcl_GetDefaultCalendar( SvNumberFormatter const * pFormatter, LanguageType nLang )
{
    //  get name of first non-gregorian calendar for the language
 
    OUString aCalendar;
    CalendarWrapper* pCalendar = pFormatter->GetCalendar();
    if (pCalendar)
    {
        lang::Locale aLocale( LanguageTag::convertToLocale( nLang ) );
 
        const uno::Sequence<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 = SvNumberFormatter::GetIndexTableOffset( nRealKey );
 
    SvNumFormatType nFmtType = SvNumFormatType::ALL;
    bool bThousand = false;
    sal_uInt16 nPrecision = 0;
    sal_uInt16 nLeading = 0;
    rFormat.GetNumForInfo( nPart, nFmtType, bThousand, nPrecision, nLeading);
    nFmtType &= ~SvNumFormatType::DEFINED;
 
    //  special treatment of builtin formats that aren't detected by normal parsing
    //  (the same formats that get the type set in SvNumberFormatter::ImpGenerateFormats)
    if ( eBuiltIn == NF_NUMBER_STANDARD )
        nFmtType = SvNumFormatType::NUMBER;
    else if ( eBuiltIn == NF_BOOLEAN )
        nFmtType = SvNumFormatType::LOGICAL;
    else if ( eBuiltIn == NF_TEXT )
        nFmtType = SvNumFormatType::TEXT;
 
    // #101606# An empty subformat is a valid number-style resulting in an
    // empty display string for the condition of the subformat.
 
    XMLTokenEnum eType = XML_TOKEN_INVALID;
    switch ( nFmtType )
    {
        // Type UNDEFINED likely is a crappy format string for that we could
        // not decide on any format type (and maybe could try harder?), but the
        // resulting XMLTokenEnum should be something valid, so make that
        // number-style.
        case SvNumFormatType::UNDEFINED:
            SAL_WARN("xmloff.style","UNDEFINED number format: '" << rFormat.GetFormatstring() << "'");
            [[fallthrough]];
        // Type is 0 if a format contains no recognized elements
        // (like text only) - this is handled as a number-style.
        case SvNumFormatType::ALL:
        case SvNumFormatType::EMPTY:
        case SvNumFormatType::NUMBER:
        case SvNumFormatType::SCIENTIFIC:
        case SvNumFormatType::FRACTION:
            eType = XML_NUMBER_STYLE;
            break;
        case SvNumFormatType::PERCENT:
            eType = XML_PERCENTAGE_STYLE;
            break;
        case SvNumFormatType::CURRENCY:
            eType = XML_CURRENCY_STYLE;
            break;
        case SvNumFormatType::DATE:
        case SvNumFormatType::DATETIME:
            eType = XML_DATE_STYLE;
            break;
        case SvNumFormatType::TIME:
            eType = XML_TIME_STYLE;
            break;
        case SvNumFormatType::TEXT:
            eType = XML_TEXT_STYLE;
            break;
        case SvNumFormatType::LOGICAL:
            eType = XML_BOOLEAN_STYLE;
            break;
        default: break;
    }
    SAL_WARN_IF( eType == XML_TOKEN_INVALID, "xmloff.style", "unknown format type" );
 
    OUString sAttrValue;
    bool bUserDef( rFormat.GetType() & SvNumFormatType::DEFINED );
 
    //  common attributes for format
 
    //  format name (generated from key) - style namespace
    m_rExport.AddAttribute( XML_NAMESPACE_STYLE, XML_NAME,
                        lcl_CreateStyleName( nKey, nPart, bDefPart, m_sPrefix ) );
 
    //  "volatile" attribute for styles used only in maps
    if ( !bDefPart )
        m_rExport.AddAttribute( XML_NAMESPACE_STYLE, XML_VOLATILE, XML_TRUE );
 
    //  language / country
    LanguageType nLang = rFormat.GetLanguage();
    AddLanguageAttr_Impl( nLang );                  // adds to pAttrList
 
    //  title (comment)
    //  titles for builtin formats are not written
    sAttrValue = rFormat.GetComment();
    if ( !sAttrValue.isEmpty() && bUserDef && bDefPart )
    {
        m_rExport.AddAttribute( XML_NAMESPACE_NUMBER, XML_TITLE, sAttrValue );
    }
 
    //  automatic ordering for currency and date formats
    //  only used for some built-in formats
    bool bAutoOrder = ( eBuiltIn == NF_CURRENCY_1000INT     || eBuiltIn == NF_CURRENCY_1000DEC2 ||
                        eBuiltIn == NF_CURRENCY_1000INT_RED || eBuiltIn == NF_CURRENCY_1000DEC2_RED ||
                        eBuiltIn == NF_CURRENCY_1000DEC2_DASHED ||
                        eBuiltIn == NF_DATE_SYSTEM_SHORT    || eBuiltIn == NF_DATE_SYSTEM_LONG ||
                        eBuiltIn == NF_DATE_SYS_MMYY        || eBuiltIn == NF_DATE_SYS_DDMMM ||
                        eBuiltIn == NF_DATE_SYS_DDMMYYYY    || eBuiltIn == NF_DATE_SYS_DDMMYY ||
                        eBuiltIn == NF_DATE_SYS_DMMMYY      || eBuiltIn == NF_DATE_SYS_DMMMYYYY ||
                        eBuiltIn == NF_DATE_SYS_DMMMMYYYY   || eBuiltIn == NF_DATE_SYS_NNDMMMYY ||
                        eBuiltIn == NF_DATE_SYS_NNDMMMMYYYY || eBuiltIn == NF_DATE_SYS_NNNNDMMMMYYYY ||
                        eBuiltIn == NF_DATETIME_SYSTEM_SHORT_HHMM || eBuiltIn == NF_DATETIME_SYS_DDMMYYYY_HHMM ||
                        eBuiltIn == NF_DATETIME_SYS_DDMMYYYY_HHMMSS );
 
    //  format source (for date and time formats)
    //  only used for some built-in formats
    bool bSystemDate = ( eBuiltIn == NF_DATE_SYSTEM_SHORT ||
                         eBuiltIn == NF_DATE_SYSTEM_LONG  ||
                         eBuiltIn == NF_DATETIME_SYSTEM_SHORT_HHMM );
    bool bLongSysDate = ( eBuiltIn == NF_DATE_SYSTEM_LONG );
 
    // check if the format definition matches the key
    if ( bAutoOrder && ( nFmtType == SvNumFormatType::DATE || nFmtType == SvNumFormatType::DATETIME ) &&
            !lcl_IsDefaultDateFormat( rFormat, bSystemDate, eBuiltIn ) )
    {
        bAutoOrder = bSystemDate = bLongSysDate = false;        // don't write automatic-order attribute then
    }
 
    if ( bAutoOrder &&
        ( nFmtType == SvNumFormatType::CURRENCY || nFmtType == SvNumFormatType::DATE || nFmtType == SvNumFormatType::DATETIME ) )
    {
        //  #85109# format type must be checked to avoid dtd errors if
        //  locale data contains other format types at the built-in positions
 
        m_rExport.AddAttribute( XML_NAMESPACE_NUMBER, XML_AUTOMATIC_ORDER,
                              XML_TRUE );
    }
 
    if ( bSystemDate && bAutoOrder &&
        ( nFmtType == SvNumFormatType::DATE || nFmtType == SvNumFormatType::DATETIME ) )
    {
        //  #85109# format type must be checked to avoid dtd errors if
        //  locale data contains other format types at the built-in positions
 
        m_rExport.AddAttribute( XML_NAMESPACE_NUMBER, XML_FORMAT_SOURCE,
                              XML_LANGUAGE );
    }
 
    //  overflow for time formats as in [hh]:mm
    //  controlled by bThousand from number format info
    //  default for truncate-on-overflow is true
    if ( nFmtType == SvNumFormatType::TIME && bThousand )
    {
        m_rExport.AddAttribute( XML_NAMESPACE_NUMBER, XML_TRUNCATE_ON_OVERFLOW,
                              XML_FALSE );
    }
 
    // Native number transliteration
    css::i18n::NativeNumberXmlAttributes2 aAttr;
    rFormat.GetNatNumXml( aAttr, nPart, m_pFormatter->GetNatNum() );
    if ( !aAttr.Format.isEmpty() )
    {
        assert(aAttr.Spellout.isEmpty());   // mutually exclusive
 
        /* FIXME-BCP47: ODF defines no transliteration-script or
         * transliteration-rfc-language-tag */
        LanguageTag aLanguageTag( aAttr.Locale);
        OUString aLanguage, aScript, aCountry;
        aLanguageTag.getIsoLanguageScriptCountry( aLanguage, aScript, aCountry);
        m_rExport.AddAttribute( XML_NAMESPACE_NUMBER, XML_TRANSLITERATION_FORMAT,
                              aAttr.Format );
        m_rExport.AddAttribute( XML_NAMESPACE_NUMBER, XML_TRANSLITERATION_LANGUAGE,
                              aLanguage );
        m_rExport.AddAttribute( XML_NAMESPACE_NUMBER, XML_TRANSLITERATION_COUNTRY,
                              aCountry );
        m_rExport.AddAttribute( XML_NAMESPACE_NUMBER, XML_TRANSLITERATION_STYLE,
                              aAttr.Style );
    }
 
    SvtSaveOptions::ODFSaneDefaultVersion eVersion = m_rExport.getSaneDefaultVersion();
    if ( !aAttr.Spellout.isEmpty() )
    {
        const bool bWriteSpellout = aAttr.Format.isEmpty();
        assert(bWriteSpellout);     // mutually exclusive
 
        // Export only for 1.2 and later with extensions
        // Also ensure that duplicated transliteration-language and
        // transliteration-country attributes never escape into the wild with
        // releases.
        if ( (eVersion & SvtSaveOptions::ODFSVER_EXTENDED) && bWriteSpellout )
        {
            /* FIXME-BCP47: ODF defines no transliteration-script or
             * transliteration-rfc-language-tag */
            LanguageTag aLanguageTag( aAttr.Locale);
            OUString aLanguage, aScript, aCountry;
            aLanguageTag.getIsoLanguageScriptCountry( aLanguage, aScript, aCountry);
            // For 1.2/1.3+ use loext namespace.
            m_rExport.AddAttribute( /*((eVersion < SvtSaveOptions::ODFSVER_)
                        ? */ XML_NAMESPACE_LO_EXT /*: XML_NAMESPACE_NUMBER)*/,
                    XML_TRANSLITERATION_SPELLOUT, aAttr.Spellout );
            m_rExport.AddAttribute( XML_NAMESPACE_NUMBER, XML_TRANSLITERATION_LANGUAGE,
                                  aLanguage );
            m_rExport.AddAttribute( XML_NAMESPACE_NUMBER, XML_TRANSLITERATION_COUNTRY,
                                  aCountry );
        }
    }
 
    // The element
    SvXMLElementExport aElem( m_rExport, XML_NAMESPACE_NUMBER, eType,
                              true, true );
 
    //  color (properties element)
 
    const Color* pCol = rFormat.GetColor( nPart );
    if (pCol)
        WriteColorElement_Impl(*pCol);
 
    //  detect if there is "real" content, excluding color and maps
    //! move to implementation of Write... methods?
    bool bAnyContent = false;
 
    //  format elements
 
    SvXMLEmbeddedTextEntryArr aEmbeddedEntries;
    if ( eBuiltIn == NF_NUMBER_STANDARD )
    {
        //  default number format contains just one number element
        WriteNumberElement_Impl( -1, -1, 1, -1, OUString(), false, 0, aEmbeddedEntries );
        bAnyContent = true;
    }
    else if ( eBuiltIn == NF_BOOLEAN )
    {
        //  boolean format contains just one boolean element
        WriteBooleanElement_Impl();
        bAnyContent = true;
    }
    else if (eType == XML_BOOLEAN_STYLE)
    {
        // <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)) ? u"gregorian"_ustr : aCalendar),
                                (bSystemDate ? bLongSysDate : bLong));
                        bAnyContent = true;
                    }
                    break;
                case NF_KEY_G:
                case NF_KEY_GG:
                case NF_KEY_GGG:
                case NF_KEY_RR:     //! RR acts as GGGEE, no attribute available
                    {
                        //! distinguish GG and GGG and RR
                        bool bLong = ( nElemType == NF_KEY_GGG || nElemType == NF_KEY_RR );
                        WriteEraElement_Impl( aCalendar, ( bSystemDate ? bLongSysDate : bLong ) );
                        bAnyContent = true;
                        if ( nElemType == NF_KEY_RR )
                        {
                            //  calendar attribute for RR is set in first loop
                            WriteYearElement_Impl( aCalendar, ( bSystemDate || bLongSysDate ) );
                        }
                    }
                    break;
                case NF_KEY_Q:
                case NF_KEY_QQ:
                    {
                        bool bLong = ( nElemType == NF_KEY_QQ );
                        WriteQuarterElement_Impl( aCalendar, ( bSystemDate ? bLongSysDate : bLong ) );
                        bAnyContent = true;
                    }
                    break;
                case NF_KEY_WW:
                    WriteWeekElement_Impl( aCalendar );
                    bAnyContent = true;
                    break;
 
                // time elements (bSystemDate is not used):
 
                case NF_KEY_H:
                case NF_KEY_HH:
                    WriteHoursElement_Impl( nElemType == NF_KEY_HH );
                    bAnyContent = true;
                    break;
                case NF_KEY_MI:
                case NF_KEY_MMI:
                    WriteMinutesElement_Impl( nElemType == NF_KEY_MMI );
                    bAnyContent = true;
                    break;
                case NF_KEY_S:
                case NF_KEY_SS:
                    WriteSecondsElement_Impl( ( nElemType == NF_KEY_SS ), nPrecision );
                    bAnyContent = true;
                    break;
                case NF_KEY_AMPM:
                case NF_KEY_AP:
                    WriteAMPMElement_Impl();        // short/long?
                    bAnyContent = true;
                    break;
                case NF_SYMBOLTYPE_STAR :
                    // export only if ODF 1.2 extensions are enabled
                    if (m_rExport.getSaneDefaultVersion() > SvtSaveOptions::ODFSVER_012)
                    {
                        if ( pElemStr && pElemStr->getLength() > 1 )
                            WriteRepeatedElement_Impl( (*pElemStr)[1] );
                    }
                    break;
            }
            nPrevType = nElemType;
            ++nPos;
        }
    }
 
    if ( !m_sTextContent.isEmpty() )
        bAnyContent = true;     // element written in FinishTextElement_Impl
 
    FinishTextElement_Impl();       // final text element - before maps
 
    if ( !bAnyContent )
    {
        //  for an empty format, write an empty text element
        SvXMLElementExport aTElem( m_rExport, XML_NAMESPACE_NUMBER, XML_TEXT,
                                   true, false );
    }
 
    //  mapping (conditions) must be last elements
 
    if (!bDefPart)
        return;
 
    SvNumberformatLimitOps eOp1, eOp2;
    double fLimit1, fLimit2;
    rFormat.GetConditions( eOp1, fLimit1, eOp2, fLimit2 );
 
    WriteMapElement_Impl( eOp1, fLimit1, nKey, 0 );
    WriteMapElement_Impl( eOp2, fLimit2, nKey, 1 );
 
    if ( !rFormat.HasTextFormat() )
        return;
 
    //  4th part is for text -> make an "all other numbers" condition for the 3rd part
    //  by reversing the 2nd condition.
    //  For a trailing text format like  0;@  that has no conditions
    //  use a "less or equal than biggest" condition for the number
    //  part, ODF can't store subformats (style maps) without
    //  conditions.
 
    SvNumberformatLimitOps eOp3 = NUMBERFORMAT_OP_NO;
    double fLimit3 = fLimit2;
    sal_uInt16 nLastPart = 2;
    SvNumberformatLimitOps eOpLast = eOp2;
    if (eOp2 == NUMBERFORMAT_OP_NO)
    {
        eOpLast = eOp1;
        fLimit3 = fLimit1;
        nLastPart = (eOp1 == NUMBERFORMAT_OP_NO) ? 0 : 1;
    }
    switch ( eOpLast )
    {
        case NUMBERFORMAT_OP_EQ: eOp3 = NUMBERFORMAT_OP_NE; break;
        case NUMBERFORMAT_OP_NE: eOp3 = NUMBERFORMAT_OP_EQ; break;
        case NUMBERFORMAT_OP_LT: eOp3 = NUMBERFORMAT_OP_GE; break;
        case NUMBERFORMAT_OP_LE: eOp3 = NUMBERFORMAT_OP_GT; break;
        case NUMBERFORMAT_OP_GT: eOp3 = NUMBERFORMAT_OP_LE; break;
        case NUMBERFORMAT_OP_GE: eOp3 = NUMBERFORMAT_OP_LT; break;
        case NUMBERFORMAT_OP_NO: eOp3 = NUMBERFORMAT_OP_LE; fLimit3 = DBL_MAX; break;
    }
 
    if ( fLimit1 == fLimit2 &&
            ( ( eOp1 == NUMBERFORMAT_OP_LT && eOp2 == NUMBERFORMAT_OP_GT ) ||
              ( eOp1 == NUMBERFORMAT_OP_GT && eOp2 == NUMBERFORMAT_OP_LT ) ) )
    {
        //  For <x 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: */

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'padToLength' is required to be utilized.