/* -*- 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 <sal/config.h>

#include <string_view>

#include <utility>
#include <xmloff/xmluconv.hxx>

#include <com/sun/star/util/DateTime.hpp>
#include <com/sun/star/util/Date.hpp>
#include <rtl/ustrbuf.hxx>
#include <osl/diagnose.h>
#include <sal/log.hxx>
#include <xmloff/xmlement.hxx>
#include <xmloff/xmltoken.hxx>
#include <rtl/math.hxx>

#include <tools/date.hxx>
#include <tools/time.hxx>
#include <tools/fldunit.hxx>

#include <com/sun/star/drawing/Position3D.hpp>
#include <com/sun/star/frame/XModel.hpp>
#include <com/sun/star/util/XNumberFormatsSupplier.hpp>
#include <com/sun/star/style/NumberingType.hpp>
#include <com/sun/star/text/DefaultNumberingProvider.hpp>
#include <com/sun/star/text/XDefaultNumberingProvider.hpp>
#include <com/sun/star/text/XNumberingTypeInfo.hpp>
#include <com/sun/star/i18n/CharacterClassification.hpp>
#include <com/sun/star/i18n/UnicodeType.hpp>
#include <basegfx/vector/b3dvector.hxx>

#include <sax/tools/converter.hxx>


using namespace com::sun::star;
using namespace com::sun::star::uno;
using namespace com::sun::star::lang;
using namespace com::sun::star::text;
using namespace com::sun::star::style;
using namespace ::com::sun::star::i18n;
using namespace ::xmloff::token;


const sal_Int8 XML_MAXDIGITSCOUNT_TIME = 11;
constexpr OUStringLiteral XML_NULLDATE = u"NullDate";

struct SvXMLUnitConverter::Impl
{
    sal_Int16 m_eCoreMeasureUnit; /*css::util::MeasureUnit*/
    sal_Int16 m_eXMLMeasureUnit; /*css::util::MeasureUnit*/
    SvtSaveOptions::ODFSaneDefaultVersion m_eODFVersion;
    util::Date m_aNullDate;
    mutable uno::Reference< text::XNumberingTypeInfo > m_xNumTypeInfo;
    mutable uno::Reference< i18n::XCharacterClassification > m_xCharClass;
    uno::Reference< uno::XComponentContext > m_xContext;

    Impl(uno::Reference<uno::XComponentContext> xContext,
            sal_Int16 const eCoreMeasureUnit,
            sal_Int16 const eXMLMeasureUnit,
            SvtSaveOptions::ODFSaneDefaultVersion const nODFVersion)
        : m_eCoreMeasureUnit(eCoreMeasureUnit)
        , m_eXMLMeasureUnit(eXMLMeasureUnit)
        , m_eODFVersion(nODFVersion)
        , m_aNullDate(30, 12, 1899)
        , m_xContext(std::move(xContext))
    {
        OSL_ENSURE( m_xContext.is(), "got no service manager" );
    }

    void createNumTypeInfo() const;
};


void SvXMLUnitConverter::Impl::createNumTypeInfo() const
{
    Reference<XDefaultNumberingProvider> xDefNum = DefaultNumberingProvider::create(m_xContext);
    m_xNumTypeInfo.set(xDefNum, uno::UNO_QUERY);
}

const uno::Reference< text::XNumberingTypeInfo >&
SvXMLUnitConverter::getNumTypeInfo() const
{
    if (!m_pImpl->m_xNumTypeInfo.is())
    {
        m_pImpl->createNumTypeInfo();
    }
    return m_pImpl->m_xNumTypeInfo;
}

void SvXMLUnitConverter::SetCoreMeasureUnit(sal_Int16 const eCoreMeasureUnit/*css::util::MeasureUnit*/)
{
    m_pImpl->m_eCoreMeasureUnit = eCoreMeasureUnit;
}

void SvXMLUnitConverter::SetXMLMeasureUnit(sal_Int16 const eXMLMeasureUnit/*css::util::MeasureUnit*/)
{
    m_pImpl->m_eXMLMeasureUnit = eXMLMeasureUnit;
}

sal_Int16 SvXMLUnitConverter::GetXMLMeasureUnit() const
{
    return m_pImpl->m_eXMLMeasureUnit;
}

SvtSaveOptions::ODFSaneDefaultVersion SvXMLUnitConverter::getSaneDefaultVersion() const
{
    return m_pImpl->m_eODFVersion;
}

void SvXMLUnitConverter::overrideSaneDefaultVersion(
        SvtSaveOptions::ODFSaneDefaultVersion const nODFVersion)
{
    m_pImpl->m_eODFVersion = nODFVersion;
}

/** constructs a SvXMLUnitConverter. The core measure unit is the
    default unit for numerical measures, the XML measure unit is
    the default unit for textual measures
*/

SvXMLUnitConverter::SvXMLUnitConverter(
    const uno::Reference<uno::XComponentContext>& xContext,
    sal_Int16 const eCoreMeasureUnit,
    sal_Int16 const eXMLMeasureUnit,
    SvtSaveOptions::ODFSaneDefaultVersion const nODFVersion)
: m_pImpl(new Impl(xContext, eCoreMeasureUnit, eXMLMeasureUnit, nODFVersion))
{
}

SvXMLUnitConverter::~SvXMLUnitConverter()
{
}

sal_Int16 SvXMLUnitConverter::GetMeasureUnit(FieldUnit const nFieldUnit)
{
    sal_Int16 eUnit = util::MeasureUnit::INCH;
    switch( nFieldUnit )
    {
    case FieldUnit::MM:
        eUnit = util::MeasureUnit::MM;
        break;
    case FieldUnit::CM:
    case FieldUnit::M:
    case FieldUnit::KM:
        eUnit = util::MeasureUnit::CM;
        break;
    case FieldUnit::TWIP:
        eUnit = util::MeasureUnit::TWIP;
        break;
    case FieldUnit::POINT:
    case FieldUnit::PICA:
        eUnit = util::MeasureUnit::POINT;
        break;
    case FieldUnit::MM_100TH:
        eUnit = util::MeasureUnit::MM_100TH;
        break;
    case FieldUnit::INCH:
        eUnit = util::MeasureUnit::INCH;
        break;
    default:
        assert(false);
        break;
    }
    return eUnit;
}

/** convert string to measure using optional min and max values*/
bool SvXMLUnitConverter::convertMeasureToCore( sal_Int32& nValue,
                                         std::u16string_view rString,
                                         sal_Int32 nMin, sal_Int32 nMax ) const
{
    return ::sax::Converter::convertMeasure( nValue, rString,
                                               m_pImpl->m_eCoreMeasureUnit,
                                               nMin, nMax );
}

/** convert string to measure using optional min and max values*/
bool SvXMLUnitConverter::convertMeasureToCore( sal_Int32& nValue,
                                         std::string_view rString,
                                         sal_Int32 nMin, sal_Int32 nMax ) const
{
    return ::sax::Converter::convertMeasure( nValue, rString,
                                               m_pImpl->m_eCoreMeasureUnit,
                                               nMin, nMax );
}

/** convert measure to string */
void SvXMLUnitConverter::convertMeasureToXML( OUStringBuffer& rString,
                                         sal_Int32 nMeasure ) const
{
    ::sax::Converter::convertMeasure( rString, nMeasure,
                                        m_pImpl->m_eCoreMeasureUnit,
                                        m_pImpl->m_eXMLMeasureUnit );
}

/** convert measure to string */
OUString SvXMLUnitConverter::convertMeasureToXML( sal_Int32 nMeasure ) const
{
    OUStringBuffer s;
    ::sax::Converter::convertMeasure( s, nMeasure,
                                        m_pImpl->m_eCoreMeasureUnit,
                                        m_pImpl->m_eXMLMeasureUnit );
    return s.makeStringAndClear();
}

/** convert string to enum using given enum map, if the enum is
    not found in the map, this method will return false
*/
bool SvXMLUnitConverter::convertEnumImpl( sal_uInt16& rEnum,
                                      std::u16string_view rValue,
                                      const SvXMLEnumStringMapEntry<sal_uInt16> *pMap )
{
    while( pMap->GetName() )
    {
        auto nameLength = pMap->GetNameLength();
        if( static_cast<sal_Int32>(rValue.size()) == nameLength &&
            rtl_ustr_asciil_reverseEquals_WithLength(
                    rValue.data(), pMap->GetName(), nameLength ) )
        {
            rEnum = pMap->GetValue();
            return true;
        }
        ++pMap;
    }

    return false;
}

/** convert string to enum using given token map, if the enum is
    not found in the map, this method will return false */
bool SvXMLUnitConverter::convertEnumImpl(
    sal_uInt16& rEnum,
    std::u16string_view rValue,
    const SvXMLEnumMapEntry<sal_uInt16> *pMap )
{
    while( pMap->GetToken() != XML_TOKEN_INVALID )
    {
        if( IsXMLToken( rValue, pMap->GetToken() ) )
        {
            rEnum = pMap->GetValue();
            return true;
        }
        ++pMap;
    }
    return false;
}

/** convert string to enum using given token map, if the enum is
    not found in the map, this method will return false */
bool SvXMLUnitConverter::convertEnumImpl(
    sal_uInt16& rEnum,
    std::string_view rValue,
    const SvXMLEnumMapEntry<sal_uInt16> *pMap )
{
    while( pMap->GetToken() != XML_TOKEN_INVALID )
    {
        if( IsXMLToken( rValue, pMap->GetToken() ) )
        {
            rEnum = pMap->GetValue();
            return true;
        }
        ++pMap;
    }
    return false;
}

/** convert enum to string using given token map with an optional
    default token. If the enum is not found in the map,
    this method will either use the given default or return
    false if no default is set */
bool SvXMLUnitConverter::convertEnumImpl(
    OUStringBuffer& rBuffer,
    sal_uInt16 nValue,
    const SvXMLEnumMapEntry<sal_uInt16> *pMap,
    enum XMLTokenEnum eDefault)
{
    enum XMLTokenEnum eTok = eDefault;

    while( pMap->GetToken() != XML_TOKEN_INVALID )
    {
        if( pMap->GetValue() == nValue )
        {
            eTok = pMap->GetToken();
            break;
        }
        ++pMap;
    }

    // the map may have contained XML_TOKEN_INVALID
    if( eTok == XML_TOKEN_INVALID )
        eTok = eDefault;

    if( eTok != XML_TOKEN_INVALID )
        rBuffer.append( GetXMLToken(eTok) );

    return (eTok != XML_TOKEN_INVALID);
}

static int lcl_gethex( int nChar )
{
    if( nChar >= '0' && nChar <= '9' )
        return nChar - '0';
    else if( nChar >= 'a' && nChar <= 'f' )
        return nChar - 'a' + 10;
    else if( nChar >= 'A' && nChar <= 'F' )
        return nChar - 'A' + 10;
    else
        return 0;
}

const char aHexTab[] = "0123456789abcdef";


/** convert double number to string (using ::rtl::math) */
void SvXMLUnitConverter::convertDouble(OUStringBuffer& rBuffer,
    double fNumber) const
{
    ::sax::Converter::convertDouble(rBuffer, fNumber,
        true/*bWriteUnits*/, m_pImpl->m_eCoreMeasureUnit, m_pImpl->m_eXMLMeasureUnit);
}

/** convert string to double number (using ::rtl::math) */
bool SvXMLUnitConverter::convertDouble(double& rValue,
    std::u16string_view rString) const
{
    sal_Int16 const eSrcUnit = ::sax::Converter::GetUnitFromString(
            rString, m_pImpl->m_eCoreMeasureUnit);

    return ::sax::Converter::convertDouble(rValue, rString,
        eSrcUnit, m_pImpl->m_eCoreMeasureUnit);
}

/** convert string to double number (using ::rtl::math) */
bool SvXMLUnitConverter::convertDouble(double& rValue,
    std::string_view rString) const
{
    sal_Int16 const eSrcUnit = ::sax::Converter::GetUnitFromString(
            rString, m_pImpl->m_eCoreMeasureUnit);

    return ::sax::Converter::convertDouble(rValue, rString,
        eSrcUnit, m_pImpl->m_eCoreMeasureUnit);
}

/** get the Null Date of the XModel and set it to the UnitConverter */
bool SvXMLUnitConverter::setNullDate(const css::uno::Reference <css::frame::XModel>& xModel)
{
    css::uno::Reference <css::util::XNumberFormatsSupplier> xNumberFormatsSupplier (xModel, css::uno::UNO_QUERY);
    if (xNumberFormatsSupplier.is())
    {
        const css::uno::Reference <css::beans::XPropertySet> xPropertySet = xNumberFormatsSupplier->getNumberFormatSettings();
        return xPropertySet.is() && (xPropertySet->getPropertyValue(XML_NULLDATE) >>= m_pImpl->m_aNullDate);
    }
    return false;
}

/** convert double to ISO Date Time String */
void SvXMLUnitConverter::convertDateTime(OUStringBuffer& rBuffer,
                     const double& fDateTime, bool const bAddTimeIf0AM)
{
    convertDateTime(rBuffer, fDateTime, m_pImpl->m_aNullDate, bAddTimeIf0AM);
}

/** convert ISO Date Time String to double */
bool SvXMLUnitConverter::convertDateTime(double& fDateTime,
                     std::u16string_view rString) const
{
    return convertDateTime(fDateTime, rString, m_pImpl->m_aNullDate);
}

/** convert ISO Date Time String to double */
bool SvXMLUnitConverter::convertDateTime(double& fDateTime,
                     std::string_view rString) const
{
    return convertDateTime(fDateTime, rString, m_pImpl->m_aNullDate);
}

/** convert double to ISO Date Time String */
void SvXMLUnitConverter::convertDateTime( OUStringBuffer& rBuffer,
        const double& fDateTime,
        const css::util::Date& aTempNullDate,
        bool bAddTimeIf0AM )
{
    double fValue = fDateTime;
    const sal_Int32 nDays = static_cast <sal_Int32> (::rtl::math::approxFloor (fValue));
    Date aDate (aTempNullDate.Day, aTempNullDate.Month, aTempNullDate.Year);
    aDate.AddDays( nDays);
    fValue -= nDays;
    const bool bHasTime = (fValue > 0.0);

    sal_Int16 nTempYear = aDate.GetYear();
    assert(nTempYear != 0);
    if (nTempYear < 0)
    {
        rBuffer.append( '-');
        nTempYear = -nTempYear;
    }
    if (nTempYear < 1000)
        rBuffer.append( '0');
    if (nTempYear < 100)
        rBuffer.append( '0');
    if (nTempYear < 10)
        rBuffer.append( '0');
    rBuffer.append( sal_Int32( nTempYear));
    rBuffer.append( '-');
    sal_uInt16 nTemp = aDate.GetMonth();
    assert(1 <= nTemp && nTemp <= 12);
    if (nTemp < 10)
        rBuffer.append( '0');
    rBuffer.append( sal_Int32( nTemp));
    rBuffer.append( '-');
    nTemp = aDate.GetDay();
    assert(1 <= nTemp && nTemp <= 31);
    if (nTemp < 10)
        rBuffer.append( '0');
    rBuffer.append( sal_Int32( nTemp));
    if (!(bHasTime || bAddTimeIf0AM))
        return;

    double fCount;
    if (nDays > 0)
        fCount = ::rtl::math::approxFloor (log10(static_cast<double>(nDays))) + 1;
    else if (nDays < 0)
        fCount = ::rtl::math::approxFloor (log10(static_cast<double>(nDays * -1))) + 1;
    else
        fCount = 0.0;
    const int nDigits = sal_Int16(fCount) + 4;  // +4 for *86400 in seconds
    const int nFractionDecimals = std::max( XML_MAXDIGITSCOUNT_TIME - nDigits, 0);

    sal_uInt16 nHour, nMinute, nSecond;
    double fFractionOfSecond;
    // Pass the original date+time value for proper scaling and rounding.
    tools::Time::GetClock( fDateTime, nHour, nMinute, nSecond, fFractionOfSecond, nFractionDecimals);

    rBuffer.append( 'T');
    if (nHour < 10)
        rBuffer.append( '0');
    rBuffer.append( sal_Int32( nHour));
    rBuffer.append( ':');
    if (nMinute < 10)
        rBuffer.append( '0');
    rBuffer.append( sal_Int32( nMinute));
    rBuffer.append( ':');
    if (nSecond < 10)
        rBuffer.append( '0');
    rBuffer.append( sal_Int32( nSecond));
    if (!nFractionDecimals)
        return;

    // nFractionDecimals+1 to not round up what GetClock() carefully
    // truncated.
    OUString aFraction( ::rtl::math::doubleToUString( fFractionOfSecond,
                rtl_math_StringFormat_F,
                nFractionDecimals + 1, '.', true));
    const sal_Int32 nLen = aFraction.getLength();
    if ( nLen > 2 )
    {
        // Truncate nFractionDecimals+1 digit if it was not rounded to zero.
        const sal_Int32 nCount = nLen - 2 - static_cast<int>(nLen > nFractionDecimals + 2);
        rBuffer.append( '.');
        rBuffer.append( aFraction.subView(2, nCount));     // strip 0.
    }
}

/** convert ISO Date Time String to double */
template<typename V>
static bool lcl_convertDateTime( double& fDateTime,
                            V rString, const css::util::Date& aTempNullDate)
{
    css::util::DateTime aDateTime;
    bool bSuccess = ::sax::Converter::parseDateTime(aDateTime, rString);

    if (bSuccess)
    {
        const Date aTmpNullDate(aTempNullDate.Day, aTempNullDate.Month, aTempNullDate.Year);
        const Date aTempDate(aDateTime.Day, aDateTime.Month, aDateTime.Year);
        const sal_Int32 nTage = aTempDate - aTmpNullDate;
        double fTempDateTime = nTage;
        double Hour = aDateTime.Hours;
        double Min = aDateTime.Minutes;
        double Sec = aDateTime.Seconds;
        double NanoSec = aDateTime.NanoSeconds;
        fTempDateTime += Hour    / ::tools::Time::hourPerDay;
        fTempDateTime += Min     / ::tools::Time::minutePerDay;
        fTempDateTime += Sec     / ::tools::Time::secondPerDay;
        fTempDateTime += NanoSec / ::tools::Time::nanoSecPerDay;
        fDateTime = fTempDateTime;
    }
    return bSuccess;
}

bool SvXMLUnitConverter::convertDateTime( double& fDateTime,
                            std::u16string_view rString, const css::util::Date& aTempNullDate)
{
    return lcl_convertDateTime(fDateTime, rString, aTempNullDate);
}
/** convert ISO Date Time String to double */
bool SvXMLUnitConverter::convertDateTime( double& fDateTime,
                            std::string_view rString, const css::util::Date& aTempNullDate)
{
    return lcl_convertDateTime(fDateTime, rString, aTempNullDate);
}


SvXMLTokenEnumerator::SvXMLTokenEnumerator( std::u16string_view rString, sal_Unicode cSeparator /* = ' ' */ )
: maTokenString( rString ), mnNextTokenPos(0), mcSeparator( cSeparator )
{
}

bool SvXMLTokenEnumerator::getNextToken( std::u16string_view& rToken )
{
    if( std::u16string_view::npos == mnNextTokenPos )
        return false;

    size_t nTokenEndPos = maTokenString.find( mcSeparator, mnNextTokenPos );
    if( nTokenEndPos != std::u16string_view::npos )
    {
        rToken = maTokenString.substr( mnNextTokenPos,
                                     nTokenEndPos - mnNextTokenPos );
        mnNextTokenPos = nTokenEndPos + 1;

        // if the mnNextTokenPos is at the end of the string, we have
        // to deliver an empty token
        if( mnNextTokenPos > maTokenString.size() )
            mnNextTokenPos = std::u16string_view::npos;
    }
    else
    {
        rToken = maTokenString.substr( mnNextTokenPos );
        mnNextTokenPos = std::u16string_view::npos;
    }

    return true;
}

static bool lcl_getPositions(std::string_view _sValue, std::string_view& _rContentX, std::string_view& _rContentY, std::string_view& _rContentZ)
{
    if(_sValue.empty() || _sValue[0] != '(')
        return false;

    size_t nPos(1);
    size_t nFound = _sValue.find(' ', nPos);

    if(nFound == std::string_view::npos || nFound <= nPos)
        return false;

    _rContentX = _sValue.substr(nPos, nFound - nPos);

    nPos = nFound + 1;
    nFound = _sValue.find(' ', nPos);

    if(nFound == std::string_view::npos || nFound <= nPos)
        return false;

    _rContentY = _sValue.substr(nPos, nFound - nPos);

    nPos = nFound + 1;
    nFound = _sValue.find(')', nPos);

    if(nFound == std::string_view::npos || nFound <= nPos)
        return false;

    _rContentZ = _sValue.substr(nPos, nFound - nPos);
    return true;

}

/** convert string to ::basegfx::B3DVector */
bool SvXMLUnitConverter::convertB3DVector( ::basegfx::B3DVector& rVector, std::string_view rValue )
{
    std::string_view aContentX,aContentY,aContentZ;
    if ( !lcl_getPositions(rValue,aContentX,aContentY,aContentZ) )
        return false;

    rtl_math_ConversionStatus eStatus;

    rVector.setX(::rtl::math::stringToDouble(aContentX, '.',
            ',', &eStatus));

    if( eStatus != rtl_math_ConversionStatus_Ok )
        return false;

    rVector.setY(::rtl::math::stringToDouble(aContentY, '.',
            ',', &eStatus));

    if( eStatus != rtl_math_ConversionStatus_Ok )
        return false;

    rVector.setZ(::rtl::math::stringToDouble(aContentZ, '.',
            ',', &eStatus));


    return ( eStatus == rtl_math_ConversionStatus_Ok );
}

/** convert ::basegfx::B3DVector to string */
void SvXMLUnitConverter::convertB3DVector( OUStringBuffer &rBuffer, const ::basegfx::B3DVector& rVector )
{
    rBuffer.append('(');
    ::sax::Converter::convertDouble(rBuffer, rVector.getX());
    rBuffer.append(' ');
    ::sax::Converter::convertDouble(rBuffer, rVector.getY());
    rBuffer.append(' ');
    ::sax::Converter::convertDouble(rBuffer, rVector.getZ());
    rBuffer.append(')');
}

/** convert string to Position3D */
bool SvXMLUnitConverter::convertPosition3D( drawing::Position3D& rPosition,
    std::string_view rValue ) const
{
    std::string_view aContentX,aContentY,aContentZ;
    if ( !lcl_getPositions(rValue,aContentX,aContentY,aContentZ) )
        return false;

    if ( !convertDouble( rPosition.PositionX, aContentX ) )
        return false;
    if ( !convertDouble( rPosition.PositionY, aContentY ) )
        return false;
    return convertDouble( rPosition.PositionZ, aContentZ );
}

/** convert Position3D to string */
void SvXMLUnitConverter::convertPosition3D( OUStringBuffer &rBuffer,
                                           const drawing::Position3D& rPosition )
{
    rBuffer.append( '(' );
    convertDouble( rBuffer, rPosition.PositionX );
    rBuffer.append( ' ' );
    convertDouble( rBuffer, rPosition.PositionY );
    rBuffer.append( ' ' );
    convertDouble( rBuffer, rPosition.PositionZ );
    rBuffer.append( ')' );
}

bool SvXMLUnitConverter::convertNumFormat(
        sal_Int16& rType,
        const OUString& rNumFmt,
        std::u16string_view rNumLetterSync,
        bool bNumberNone ) const
{
    bool bRet = true;
    bool bExt = false;

    sal_Int32 nLen = rNumFmt.getLength();
    if( 0 == nLen )
    {
        if( bNumberNone )
            rType = NumberingType::NUMBER_NONE;
        else
            bRet = false;
    }
    else if( 1 == nLen )
    {
        switch( rNumFmt[0] )
        {
        case '1':  rType = NumberingType::ARABIC;          break;
        case 'a':  rType = NumberingType::CHARS_LOWER_LETTER;  break;
        case 'A':  rType = NumberingType::CHARS_UPPER_LETTER;  break;
        case 'i':  rType = NumberingType::ROMAN_LOWER; break;
        case 'I':  rType = NumberingType::ROMAN_UPPER; break;
        default:                bExt = true; break;
        }
        if( !bExt && IsXMLToken( rNumLetterSync, XML_TRUE ) )
        {
            switch( rType )
            {
            case NumberingType::CHARS_LOWER_LETTER:
                rType = NumberingType::CHARS_LOWER_LETTER_N;
                break;
            case NumberingType::CHARS_UPPER_LETTER:
                rType = NumberingType::CHARS_UPPER_LETTER_N;
                break;
            }
        }
    }
    else
    {
        bExt = true;
    }
    if( bExt )
    {
        Reference < XNumberingTypeInfo > xInfo = getNumTypeInfo();
        if( xInfo.is() && xInfo->hasNumberingType( rNumFmt ) )
        {
            rType = xInfo->getNumberingType( rNumFmt );
        }
        else
        {
            rType = NumberingType::ARABIC;
        }
    }

    return bRet;
}

void SvXMLUnitConverter::convertNumFormat( OUStringBuffer& rBuffer,
                           sal_Int16 nType ) const
{
    enum XMLTokenEnum eFormat = XML_TOKEN_INVALID;
    switch( nType )
    {
    case NumberingType::CHARS_UPPER_LETTER:     eFormat = XML_A_UPCASE; break;
    case NumberingType::CHARS_LOWER_LETTER:     eFormat = XML_A; break;
    case NumberingType::ROMAN_UPPER:            eFormat = XML_I_UPCASE; break;
    case NumberingType::ROMAN_LOWER:            eFormat = XML_I; break;
    case NumberingType::ARABIC:                 eFormat = XML_1; break;
    case NumberingType::CHARS_UPPER_LETTER_N:   eFormat = XML_A_UPCASE; break;
    case NumberingType::CHARS_LOWER_LETTER_N:   eFormat = XML_A; break;
    case NumberingType::NUMBER_NONE:            eFormat = XML__EMPTY; break;

    case NumberingType::CHAR_SPECIAL:
    case NumberingType::PAGE_DESCRIPTOR:
    case NumberingType::BITMAP:
        SAL_WARN_IF( eFormat == XML_TOKEN_INVALID, "xmloff", "invalid number format" );
        break;
    default:
        break;
    }

    if( eFormat != XML_TOKEN_INVALID )
    {
        rBuffer.append( GetXMLToken(eFormat) );
    }
    else
    {
        Reference < XNumberingTypeInfo > xInfo = getNumTypeInfo();
        if( xInfo.is() )
            rBuffer.append( xInfo->getNumberingIdentifier( nType ) );
    }
}

void SvXMLUnitConverter::convertNumLetterSync( OUStringBuffer& rBuffer,
                               sal_Int16 nType )
{
    enum XMLTokenEnum eSync = XML_TOKEN_INVALID;
    switch( nType )
    {
    case NumberingType::CHARS_UPPER_LETTER:
    case NumberingType::CHARS_LOWER_LETTER:
    case NumberingType::ROMAN_UPPER:
    case NumberingType::ROMAN_LOWER:
    case NumberingType::ARABIC:
    case NumberingType::NUMBER_NONE:
        break;

    case NumberingType::CHARS_UPPER_LETTER_N:
    case NumberingType::CHARS_LOWER_LETTER_N:
        eSync = XML_TRUE;
        break;

    case NumberingType::CHAR_SPECIAL:
    case NumberingType::PAGE_DESCRIPTOR:
    case NumberingType::BITMAP:
        SAL_WARN_IF( eSync == XML_TOKEN_INVALID, "xmloff", "invalid number format" );
        break;
    }
    if( eSync != XML_TOKEN_INVALID )
        rBuffer.append( GetXMLToken(eSync) );
}

void SvXMLUnitConverter::convertPropertySet(uno::Sequence<beans::PropertyValue>& rProps,
                    const uno::Reference<beans::XPropertySet>& aProperties)
{
    uno::Reference< beans::XPropertySetInfo > xPropertySetInfo = aProperties->getPropertySetInfo();
    if (!xPropertySetInfo.is())
        return;

    const uno::Sequence< beans::Property > aProps = xPropertySetInfo->getProperties();
    if (aProps.hasElements())
    {
        rProps.realloc(aProps.getLength());
        beans::PropertyValue* pProps = rProps.getArray();
        for (const auto& rProp : aProps)
        {
            pProps->Name = rProp.Name;
            pProps->Value = aProperties->getPropertyValue(rProp.Name);
            ++pProps;
        }
    }
}

void SvXMLUnitConverter::convertPropertySet(uno::Reference<beans::XPropertySet> const & rProperties,
                    const uno::Sequence<beans::PropertyValue>& aProps)
{
    if (aProps.hasElements())
    {
        uno::Reference< beans::XPropertySetInfo > xPropertySetInfo = rProperties->getPropertySetInfo();
        if (xPropertySetInfo.is())
        {
            for (const auto& rProp : aProps)
            {
                if (xPropertySetInfo->hasPropertyByName(rProp.Name))
                    rProperties->setPropertyValue(rProp.Name, rProp.Value);
            }
        }
    }
}


OUString SvXMLUnitConverter::encodeStyleName(
        const OUString& rName,
        bool *pEncoded ) const
{
    if( pEncoded )
        *pEncoded = false;

    sal_Int32 nLen = rName.getLength();
    OUStringBuffer aBuffer( nLen*2 );

    for( sal_Int32 i = 0; i < nLen; i++ )
    {
        sal_Unicode c = rName[i];
        bool bValidChar = false;
        if( c < 0x00ffU )
        {
            bValidChar =
                (c >= 0x0041 && c <= 0x005a) ||
                (c >= 0x0061 && c <= 0x007a) ||
                (c >= 0x00c0 && c <= 0x00d6) ||
                (c >= 0x00d8 && c <= 0x00f6) ||
                (c >= 0x00f8 && c <= 0x00ff) ||
                ( i > 0 && ( (c >= 0x0030 && c <= 0x0039) ||
                             c == 0x00b7 || c == '-' || c == '.') );
        }
        else
        {
            if( (c >= 0xf900U && c <= 0xfffeU) ||
                 (c >= 0x20ddU && c <= 0x20e0U))
            {
                bValidChar = false;
            }
            else if( (c >= 0x02bbU && c <= 0x02c1U) || c == 0x0559 ||
                     c == 0x06e5 || c == 0x06e6 )
            {
                bValidChar = true;
            }
            else if( c == 0x0387 )
            {
                bValidChar = i > 0;
            }
            else
            {
                if (!m_pImpl->m_xCharClass.is())
                {
                    m_pImpl->m_xCharClass = CharacterClassification::create( m_pImpl->m_xContext );
                }
                sal_Int16 nType = m_pImpl->m_xCharClass->getType(rName, i);

                switch( nType )
                {
                case UnicodeType::UPPERCASE_LETTER:     // Lu
                case UnicodeType::LOWERCASE_LETTER:     // Ll
                case UnicodeType::TITLECASE_LETTER:     // Lt
                case UnicodeType::OTHER_LETTER:         // Lo
                case UnicodeType::LETTER_NUMBER:        // Nl
                    bValidChar = true;
                    break;
                case UnicodeType::NON_SPACING_MARK:     // Ms
                case UnicodeType::ENCLOSING_MARK:       // Me
                case UnicodeType::COMBINING_SPACING_MARK:   //Mc
                case UnicodeType::MODIFIER_LETTER:      // Lm
                case UnicodeType::DECIMAL_DIGIT_NUMBER: // Nd
                    bValidChar = i > 0;
                    break;
                }
            }
        }
        if( bValidChar )
        {
            aBuffer.append( c );
        }
        else
        {
            aBuffer.append( '_' );
            if( c > 0x0fff )
                aBuffer.append( static_cast< sal_Unicode >(
                            aHexTab[ (c >> 12) & 0x0f ]  ) );
            if( c > 0x00ff )
                aBuffer.append( static_cast< sal_Unicode >(
                        aHexTab[ (c >> 8) & 0x0f ] ) );
            if( c > 0x000f )
                aBuffer.append( static_cast< sal_Unicode >(
                        aHexTab[ (c >> 4) & 0x0f ] ) );
            aBuffer.append( static_cast< sal_Unicode >(
                        aHexTab[ c & 0x0f ] ) );
            aBuffer.append( '_' );
            if( pEncoded )
                *pEncoded = true;
        }
    }

    // check for length
    if( aBuffer.getLength() > ((1<<15)-1) )
    {
        aBuffer = rName;
        if( pEncoded )
            *pEncoded = false;
    }


    return aBuffer.makeStringAndClear();
}

/** convert string (hex) to number (sal_uInt32) */
bool SvXMLUnitConverter::convertHex( sal_uInt32& nVal, std::u16string_view rValue )
{
    if( rValue.size() != 8 )
        return false;

    nVal = 0;
    for ( int i = 0; i < 8; i++ )
    {
        nVal = ( nVal << 4 )
            | sal::static_int_cast< sal_uInt32 >( lcl_gethex( rValue[i] ) );
    }

    return true;
}

/** convert number (sal_uInt32) to string (hex) */
void SvXMLUnitConverter::convertHex( OUStringBuffer& rBuffer,
                                        sal_uInt32 nVal )
{
    for ( int i = 0; i < 8; i++ )
    {
        rBuffer.append( sal_Unicode( aHexTab[ nVal >> 28 ] ) );
        nVal <<= 4;
    }
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
