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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

V530 The return value of function 'truncate' 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 'truncate' 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 'insert' 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 'insert' 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 'insert' 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.

V1053 Calling the 'SetAttribute' virtual function in the constructor may lead to unexpected result at runtime.