/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the Collabora Office 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 <memory>
#include <optional>
#include <string_view>
#include <com/sun/star/container/XNameContainer.hpp>
#include <com/sun/star/xml/AttributeData.hpp>
#include <com/sun/star/beans/XPropertySet.hpp>
#include <com/sun/star/beans/XPropertyState.hpp>
#include <com/sun/star/beans/XMultiPropertySet.hpp>
#include <com/sun/star/beans/XTolerantMultiPropertySet.hpp>
#include <com/sun/star/beans/TolerantPropertySetResultType.hpp>
#include <comphelper/anycompare.hxx>
#include <comphelper/diagnose_ex.hxx>
#include <cppuhelper/weakref.hxx>
#include <osl/diagnose.h>
#include <list>
#include <map>
#include <o3tl/sorted_vector.hxx>

#include <utility>
#include <xmloff/xmlexppr.hxx>
#include <xmloff/xmltoken.hxx>
#include <xmloff/namespacemap.hxx>
#include <xmloff/xmlnamespace.hxx>
#include <xmloff/xmlexp.hxx>
#include <xmloff/xmlprmap.hxx>
#include <xmloff/maptype.hxx>
#include <xmloff/xmltypes.hxx>
#include <xmloff/xmlprhdl.hxx>

using namespace ::com::sun::star;
using namespace ::com::sun::star::beans;
using namespace ::com::sun::star::uno;
using namespace cpo::uno;
using namespace ::xmloff::token;

#define GET_PROP_TYPE( f ) static_cast<sal_uInt16>((f & XML_TYPE_PROP_MASK) >> XML_TYPE_PROP_SHIFT)

namespace {

struct XMLPropTokens_Impl
{
    sal_uInt16 nType;
    XMLTokenEnum eToken;
};

const sal_uInt16 MAX_PROP_TYPES =
    (XML_TYPE_PROP_END >> XML_TYPE_PROP_SHIFT) -
    (XML_TYPE_PROP_START >> XML_TYPE_PROP_SHIFT);

XMLPropTokens_Impl const aPropTokens[MAX_PROP_TYPES] =
{
    { GET_PROP_TYPE(XML_TYPE_PROP_CHART), XML_CHART_PROPERTIES },
    { GET_PROP_TYPE(XML_TYPE_PROP_GRAPHIC), XML_GRAPHIC_PROPERTIES },
    { GET_PROP_TYPE(XML_TYPE_PROP_TABLE), XML_TABLE_PROPERTIES },
    { GET_PROP_TYPE(XML_TYPE_PROP_TABLE_COLUMN), XML_TABLE_COLUMN_PROPERTIES },
    { GET_PROP_TYPE(XML_TYPE_PROP_TABLE_ROW), XML_TABLE_ROW_PROPERTIES },
    { GET_PROP_TYPE(XML_TYPE_PROP_TABLE_CELL), XML_TABLE_CELL_PROPERTIES },
    { GET_PROP_TYPE(XML_TYPE_PROP_LIST_LEVEL), XML_LIST_LEVEL_PROPERTIES },
    { GET_PROP_TYPE(XML_TYPE_PROP_PARAGRAPH), XML_PARAGRAPH_PROPERTIES },
    { GET_PROP_TYPE(XML_TYPE_PROP_TEXT), XML_TEXT_PROPERTIES },
    { GET_PROP_TYPE(XML_TYPE_PROP_DRAWING_PAGE), XML_DRAWING_PAGE_PROPERTIES },
    { GET_PROP_TYPE(XML_TYPE_PROP_PAGE_LAYOUT), XML_PAGE_LAYOUT_PROPERTIES },
    { GET_PROP_TYPE(XML_TYPE_PROP_HEADER_FOOTER), XML_HEADER_FOOTER_PROPERTIES },
    { GET_PROP_TYPE(XML_TYPE_PROP_RUBY), XML_RUBY_PROPERTIES },
    { GET_PROP_TYPE(XML_TYPE_PROP_SECTION), XML_SECTION_PROPERTIES }
};

// public methods

// Take all properties of the XPropertySet which are also found in the
// XMLPropertyMapEntry-array and which are not set to their default-value,
// if a state is available.
// After that I call the method 'ContextFilter'.

struct ComparePropertyState
{
    bool operator()(XMLPropertyState const& lhs, XMLPropertyState const& rhs)
    {
        return lhs.mnIndex < rhs.mnIndex;
    }
};
class XMLPropertyStates_Impl
{
    o3tl::sorted_vector<XMLPropertyState, ComparePropertyState> aPropStates;
public:
    XMLPropertyStates_Impl();
    void AddPropertyState(const XMLPropertyState& rPropState);
    void FillPropertyStateVector(std::vector<XMLPropertyState>& rVector);
};

XMLPropertyStates_Impl::XMLPropertyStates_Impl()
{
}

void XMLPropertyStates_Impl::AddPropertyState(
        const XMLPropertyState& rPropState)
{
    aPropStates.insert(rPropState);
}

void XMLPropertyStates_Impl::FillPropertyStateVector(
        std::vector<XMLPropertyState>& rVector)
{
    rVector.insert( rVector.begin(), aPropStates.begin(), aPropStates.end() );
}

class FilterPropertyInfo_Impl
{
    OUString                msApiName;
    std::vector<sal_uInt32> maIndexes;

public:

    FilterPropertyInfo_Impl( OUString aApiName,
                             const sal_uInt32 nIndex);

    const OUString& GetApiName() const { return msApiName; }
    std::vector<sal_uInt32>& GetIndexes() { return maIndexes; }

    // for sort
    bool operator< ( const FilterPropertyInfo_Impl& rArg ) const
    {
        return (GetApiName() < rArg.GetApiName());
    }
};

FilterPropertyInfo_Impl::FilterPropertyInfo_Impl(
        OUString aApiName,
        const sal_uInt32 nIndex ) :
    msApiName(std::move( aApiName ))
{
    maIndexes.push_back(nIndex);
}

typedef std::list<FilterPropertyInfo_Impl> FilterPropertyInfoList_Impl;

class FilterPropertiesInfo_Impl
{
    FilterPropertyInfoList_Impl             aPropInfos;

    std::optional<Sequence<OUString>>     mxApiNames;

public:
    FilterPropertiesInfo_Impl();

    void AddProperty(const OUString& rApiName, const sal_uInt32 nIndex);
    const cpo::uno::Sequence<OUString>& GetApiNames();
    void FillPropertyStateArray(
            std::vector< XMLPropertyState >& rPropStates,
            const Reference< XPropertySet >& xPropSet,
            const rtl::Reference< XMLPropertySetMapper >& maPropMapper,
            const bool bDefault);
    sal_uInt32 GetPropertyCount() const { return aPropInfos.size(); }
};

FilterPropertiesInfo_Impl::FilterPropertiesInfo_Impl()
{
}

void FilterPropertiesInfo_Impl::AddProperty(
        const OUString& rApiName, const sal_uInt32 nIndex)
{
    aPropInfos.emplace_back(rApiName, nIndex);

    OSL_ENSURE( !mxApiNames, "performance warning: API names already retrieved" );
    mxApiNames.reset();
}

const cpo::uno::Sequence<OUString>& FilterPropertiesInfo_Impl::GetApiNames()
{
    if( !mxApiNames )
    {
        // we have to do three things:
        // 1) sort API names,
        // 2) merge duplicates,
        // 3) construct sequence

        // sort names
        aPropInfos.sort();

        // merge duplicates
        if ( aPropInfos.size() > 1 )
        {
            FilterPropertyInfoList_Impl::iterator aOld = aPropInfos.begin();
            FilterPropertyInfoList_Impl::iterator aEnd = aPropInfos.end();
            FilterPropertyInfoList_Impl::iterator aCurrent = aOld;
            ++aCurrent;

            while ( aCurrent != aEnd )
            {
                // equal to next element?
                if ( aOld->GetApiName() == aCurrent->GetApiName() )
                {
                    // if equal: merge index lists
                    std::vector<sal_uInt32> aMerged;
                    std::merge(aOld->GetIndexes().begin(), aOld->GetIndexes().end(),
                               aCurrent->GetIndexes().begin(), aCurrent->GetIndexes().end(),
                               std::back_inserter(aMerged));
                    aOld->GetIndexes() = std::move(aMerged);
                    aCurrent->GetIndexes().clear();
                    // erase element, and continue with next
                    aCurrent = aPropInfos.erase( aCurrent );
                }
                else
                {
                    // remember old element and continue with next
                    aOld = aCurrent;
                    ++aCurrent;
                }
            }
        }

        // construct sequence
        mxApiNames.emplace( aPropInfos.size() );
        OUString *pNames = mxApiNames->getArray();

        for (auto const& propInfo : aPropInfos)
        {
            *pNames = propInfo.GetApiName();
            ++pNames;
        }
    }

    return *mxApiNames;
}

void FilterPropertiesInfo_Impl::FillPropertyStateArray(
        std::vector< XMLPropertyState >& rPropStates,
        const Reference< XPropertySet >& rPropSet,
        const rtl::Reference< XMLPropertySetMapper >& rPropMapper,
        const bool bDefault )
{
    XMLPropertyStates_Impl aPropStates;

    const cpo::uno::Sequence<OUString>& rApiNames = GetApiNames();

    Reference < XTolerantMultiPropertySet > xTolPropSet( rPropSet, UNO_QUERY );
    if (xTolPropSet.is())
    {
        if (!bDefault)
        {
            Sequence < beans::GetDirectPropertyTolerantResult > aResults(xTolPropSet->getDirectPropertyValuesTolerant(rApiNames));
            sal_Int32 nResultCount(aResults.getLength());
            if (nResultCount > 0)
            {
                const beans::GetDirectPropertyTolerantResult *pResults = aResults.getConstArray();
                FilterPropertyInfoList_Impl::iterator aPropIter(aPropInfos.begin());
                XMLPropertyState aNewProperty( -1 );
                while (nResultCount > 0 && aPropIter != aPropInfos.end())
                {
                    if (pResults->Name == aPropIter->GetApiName())
                    {
                        aNewProperty.mnIndex = -1;
                        aNewProperty.maValue = pResults->Value;

                        for (auto const& index : aPropIter->GetIndexes())
                        {
                            aNewProperty.mnIndex = index;
                            aPropStates.AddPropertyState( aNewProperty );
                        }
                        ++pResults;
                        --nResultCount;
                    }
                    ++aPropIter;
                }
            }
        }
        else
        {
            const Sequence < beans::GetPropertyTolerantResult > aResults(xTolPropSet->getPropertyValuesTolerant(rApiNames));
            OSL_ENSURE( rApiNames.getLength() == aResults.getLength(), "wrong implemented XTolerantMultiPropertySet" );
            FilterPropertyInfoList_Impl::iterator aPropIter(aPropInfos.begin());
            XMLPropertyState aNewProperty( -1 );
            OSL_ENSURE( aPropInfos.size() == static_cast<sal_uInt32>(aResults.getLength()), "wrong implemented XTolerantMultiPropertySet??" );
            for( const auto& rResult : aResults )
            {
                if ((rResult.Result == beans::TolerantPropertySetResultType::SUCCESS) &&
                    ((rResult.State == PropertyState_DIRECT_VALUE) || (rResult.State == PropertyState_DEFAULT_VALUE)))
                {
                    aNewProperty.mnIndex = -1;
                    aNewProperty.maValue = rResult.Value;

                    for (auto const& index : aPropIter->GetIndexes())
                    {
                        aNewProperty.mnIndex = index;
                        aPropStates.AddPropertyState( aNewProperty );
                    }
                }
                ++aPropIter;
            }
        }
    }
    else
    {
        Sequence < PropertyState > aStates;
        const PropertyState *pStates = nullptr;
        Reference< XPropertyState > xPropState( rPropSet, UNO_QUERY );
        if( xPropState.is() )
        {
            aStates = xPropState->getPropertyStates( rApiNames );
            pStates = aStates.getConstArray();
        }

        Reference < XMultiPropertySet > xMultiPropSet( rPropSet, UNO_QUERY );
        if( xMultiPropSet.is() && !bDefault )
        {
            Sequence < Any > aValues;
            if( pStates )
            {
                // step 1: get value count
                sal_uInt32 nValueCount = 0;

                for (size_t i = 0; i < aPropInfos.size(); ++i, ++pStates)
                {
                    if( *pStates == PropertyState_DIRECT_VALUE )
                        nValueCount++;
                }

                if( nValueCount )
                {
                    // step 2: collect property names
                    Sequence < OUString > aAPINames( nValueCount );
                    OUString *pAPINames = aAPINames.getArray();

                    ::std::vector< FilterPropertyInfoList_Impl::iterator > aPropIters;
                    aPropIters.reserve( nValueCount );

                    FilterPropertyInfoList_Impl::iterator aItr = aPropInfos.begin();
                    OSL_ENSURE(aItr != aPropInfos.end(),"Invalid iterator!");

                    pStates = aStates.getConstArray();
                    sal_uInt32 i = 0;
                    while( i < nValueCount )
                    {
                        if( *pStates == PropertyState_DIRECT_VALUE )
                        {
                            *pAPINames++ = aItr->GetApiName();
                            aPropIters.push_back( aItr );
                            ++i;
                        }
                        ++aItr;
                        ++pStates;
                    }

                    aValues = xMultiPropSet->getPropertyValues( aAPINames );
                    const Any *pValues = aValues.getConstArray();

                    ::std::vector< FilterPropertyInfoList_Impl::iterator >::const_iterator
                        pPropIter = aPropIters.begin();

                    XMLPropertyState aNewProperty( -1 );
                    for( i = 0; i < nValueCount; ++i )
                    {
                        aNewProperty.mnIndex = -1;
                        aNewProperty.maValue = *pValues;

                        for (auto const& index : (*pPropIter)->GetIndexes())
                        {
                            aNewProperty.mnIndex = index;
                            aPropStates.AddPropertyState( aNewProperty );
                        }

                        ++pPropIter;
                        ++pValues;
                    }
                }
            }
            else
            {
                aValues = xMultiPropSet->getPropertyValues( rApiNames );
                const Any *pValues = aValues.getConstArray();

                FilterPropertyInfoList_Impl::iterator aItr = aPropInfos.begin();
                for (size_t i = 0; i < aPropInfos.size(); ++i)
                {
                    // The value is stored in the PropertySet itself, add to list.
                    XMLPropertyState aNewProperty( -1 );
                    aNewProperty.maValue = *pValues;
                    ++pValues;
                    for (auto const& index : aItr->GetIndexes())
                    {
                        aNewProperty.mnIndex = index;
                        aPropStates.AddPropertyState( aNewProperty );
                    }
                    ++aItr;
                }
            }
        }
        else
        {
            FilterPropertyInfoList_Impl::iterator aItr = aPropInfos.begin();
            for (size_t i = 0; i < aPropInfos.size(); ++i)
            {
                bool bDirectValue =
                    !pStates || *pStates == PropertyState_DIRECT_VALUE;
                if( bDirectValue || bDefault )
                {
                    // The value is stored in the PropertySet itself, add to list.
                    bool bGotValue = false;
                    XMLPropertyState aNewProperty( -1 );
                    for (auto const& index : aItr->GetIndexes())
                    {
                        if( bDirectValue ||
                            (rPropMapper->GetEntryFlags(index) &
                                            MID_FLAG_DEFAULT_ITEM_EXPORT) != 0 )
                        {
                            try
                            {
                                if( !bGotValue )
                                {
                                    aNewProperty.maValue =
                                        rPropSet->getPropertyValue( aItr->GetApiName() );
                                    bGotValue = true;
                                }
                                aNewProperty.mnIndex = index;
                                aPropStates.AddPropertyState( aNewProperty );
                            }
                            catch( UnknownPropertyException& )
                            {
                                // might be a problem of getImplementationId
                                TOOLS_WARN_EXCEPTION("xmloff.style", "unknown property in getPropertyValue" );
                            }

                        }
                    }
                }

                ++aItr;
                if( pStates )
                    ++pStates;
            }
        }
    }
    aPropStates.FillPropertyStateVector(rPropStates);
}

}

struct SvXMLExportPropertyMapper::Impl
{
    typedef std::map<css::uno::Reference<css::beans::XPropertySetInfo>, std::unique_ptr<FilterPropertiesInfo_Impl>> CacheType;
    CacheType maCache;

    rtl::Reference<SvXMLExportPropertyMapper> mxNextMapper;
    rtl::Reference<XMLPropertySetMapper> mxPropMapper;

    OUString maStyleName;
};

// ctor/dtor , class SvXMLExportPropertyMapper

SvXMLExportPropertyMapper::SvXMLExportPropertyMapper(
        const rtl::Reference< XMLPropertySetMapper >& rMapper ) :
    mpImpl(new Impl)
{
    mpImpl->mxPropMapper = rMapper;
}

SvXMLExportPropertyMapper::~SvXMLExportPropertyMapper()
{
}

void SvXMLExportPropertyMapper::ChainExportMapper(
        const rtl::Reference< SvXMLExportPropertyMapper>& rMapper )
{
    // add map entries from rMapper to current map
    mpImpl->mxPropMapper->AddMapperEntry( rMapper->getPropertySetMapper() );
    // rMapper uses the same map as 'this'
    rMapper->mpImpl->mxPropMapper = mpImpl->mxPropMapper;

    // set rMapper as last mapper in current chain
    rtl::Reference< SvXMLExportPropertyMapper > xNext = mpImpl->mxNextMapper;
    if( xNext.is())
    {
        while (xNext->mpImpl->mxNextMapper.is())
            xNext = xNext->mpImpl->mxNextMapper;
        xNext->mpImpl->mxNextMapper = rMapper;
    }
    else
        mpImpl->mxNextMapper = rMapper;

    // if rMapper was already chained, correct
    // map pointer of successors
    xNext = rMapper;

    while (xNext->mpImpl->mxNextMapper.is())
    {
        xNext = xNext->mpImpl->mxNextMapper;
        xNext->mpImpl->mxPropMapper = mpImpl->mxPropMapper;
    }
}

std::vector<XMLPropertyState> SvXMLExportPropertyMapper::Filter(
    SvXMLExport const& rExport,
    const uno::Reference<beans::XPropertySet>& rPropSet, bool bEnableFoFontFamily ) const
{
    return Filter_(rExport, rPropSet, false, bEnableFoFontFamily);
}

std::vector<XMLPropertyState> SvXMLExportPropertyMapper::FilterDefaults(
    SvXMLExport const& rExport,
    const uno::Reference<beans::XPropertySet>& rPropSet ) const
{
    return Filter_(rExport, rPropSet, true, false/*bEnableFoFontFamily*/);
}

std::vector<XMLPropertyState> SvXMLExportPropertyMapper::Filter_(
    SvXMLExport const& rExport,
    const Reference<XPropertySet>& xPropSet, bool bDefault, bool bEnableFoFontFamily ) const
{
    std::vector< XMLPropertyState > aPropStateArray;

    // Retrieve XPropertySetInfo and XPropertyState
    Reference< XPropertySetInfo > xInfo( xPropSet->getPropertySetInfo() );
    if( !xInfo.is() )
        return aPropStateArray;

    sal_Int32 nProps = mpImpl->mxPropMapper->GetEntryCount();

    FilterPropertiesInfo_Impl *pFilterInfo = nullptr;

    Impl::CacheType::iterator aIter = mpImpl->maCache.find(xInfo);
    if (aIter != mpImpl->maCache.end())
        pFilterInfo = (*aIter).second.get();

    bool bDelInfo = false;
    if( !pFilterInfo )
    {
        assert(GetODFDefaultVersion() != SvtSaveOptions::ODFVER_UNKNOWN);
        const SvtSaveOptions::ODFSaneDefaultVersion nCurrentVersion(rExport.getSaneDefaultVersion());
        pFilterInfo = new FilterPropertiesInfo_Impl;
        for( sal_Int32 i=0; i < nProps; i++ )
        {
            // Are we allowed to ask for the property? (MID_FLAG_NO_PROP..)
            // Does the PropertySet contain name of mpEntries-array ?
            const OUString& rAPIName = mpImpl->mxPropMapper->GetEntryAPIName( i );
            const sal_Int32 nFlags = mpImpl->mxPropMapper->GetEntryFlags( i );
            if( (0 == (nFlags & MID_FLAG_NO_PROPERTY_EXPORT)) &&
                ( (0 != (nFlags & MID_FLAG_MUST_EXIST)) ||
                  xInfo->hasPropertyByName( rAPIName ) ) )
            {
                const SvtSaveOptions::ODFSaneDefaultVersion nEarliestODFVersionForExport(
                        mpImpl->mxPropMapper->GetEarliestODFVersionForExport(i));
                // note: only standard ODF versions are allowed here,
                // only exception is the unknown future
                assert((nEarliestODFVersionForExport & SvtSaveOptions::ODFSVER_EXTENDED) == 0
                    || nEarliestODFVersionForExport == SvtSaveOptions::ODFSVER_FUTURE_EXTENDED);
                static_assert(SvtSaveOptions::ODFSVER_LATEST_EXTENDED < SvtSaveOptions::ODFSVER_FUTURE_EXTENDED);
                /// standard ODF namespaces for elements and attributes
                static const sal_uInt16 s_OdfNs[] = {
                    XML_NAMESPACE_OFFICE,
                    XML_NAMESPACE_STYLE,
                    XML_NAMESPACE_TEXT,
                    XML_NAMESPACE_TABLE,
                    XML_NAMESPACE_DRAW,
                    XML_NAMESPACE_FO,
                    XML_NAMESPACE_XLINK,
                    XML_NAMESPACE_DC,
                    XML_NAMESPACE_META,
                    XML_NAMESPACE_NUMBER,
                    XML_NAMESPACE_PRESENTATION,
                    XML_NAMESPACE_SVG,
                    XML_NAMESPACE_CHART,
                    XML_NAMESPACE_DR3D,
                    XML_NAMESPACE_MATH,
                    XML_NAMESPACE_FORM,
                    XML_NAMESPACE_SCRIPT,
                    XML_NAMESPACE_CONFIG,
                    XML_NAMESPACE_DB,
                    XML_NAMESPACE_XFORMS,
                    XML_NAMESPACE_SMIL,
                    XML_NAMESPACE_ANIMATION,
                    XML_NAMESPACE_XML,
                    XML_NAMESPACE_XHTML,
                    XML_NAMESPACE_GRDDL,
                };
                static bool s_Assert(false);
                if (!s_Assert)
                {
                    assert(std::is_sorted(std::begin(s_OdfNs), std::end(s_OdfNs)));
                    s_Assert = true;
                }
                //static_assert(std::is_sorted(std::begin(s_OdfNs), std::end(s_OdfNs)));
                auto const ns(mpImpl->mxPropMapper->GetEntryNameSpace(i));
                auto const iter(std::lower_bound(std::begin(s_OdfNs), std::end(s_OdfNs),
                            ns));
                bool const isExtension(iter == std::end(s_OdfNs) || *iter != ns
                        // FIXME: very special hack to suppress style:hyperlink
                        || (ns == XML_NAMESPACE_STYLE
                            && mpImpl->mxPropMapper->GetEntryXMLName(i) == GetXMLToken(XML_HYPERLINK)));
                if (isExtension
                    ? ((nCurrentVersion & SvtSaveOptions::ODFSVER_EXTENDED)
                        // if it's in standard ODF, don't export extension
                        && (nCurrentVersion < nEarliestODFVersionForExport))
                    : (nEarliestODFVersionForExport <= nCurrentVersion))
                {
                    pFilterInfo->AddProperty(rAPIName, i);
                }
            }
        }

        // Check whether the property set info is destroyed if it is assigned to
        // a weak reference only; If it is destroyed, then every instance of
        // getPropertySetInfo returns a new object; such property set infos must
        // not be cached:
        WeakReference < XPropertySetInfo > xWeakInfo( xInfo );
        xInfo.clear();
        xInfo = xWeakInfo;
        if( xInfo.is() )
        {
            mpImpl->maCache.emplace(xInfo, std::unique_ptr<FilterPropertiesInfo_Impl>(pFilterInfo));
        }
        else
            bDelInfo = true;
    }

    if( pFilterInfo->GetPropertyCount() )
    {
        try
        {
            pFilterInfo->FillPropertyStateArray(
                aPropStateArray, xPropSet, mpImpl->mxPropMapper, bDefault);
        }
        catch( UnknownPropertyException& )
        {
            // might be a problem of getImplementationId
            TOOLS_WARN_EXCEPTION("xmloff.style", "unknown property in getPropertyStates" );
        }
    }

    // Call context-filter
    if( !aPropStateArray.empty() )
        ContextFilter(bEnableFoFontFamily, aPropStateArray, xPropSet);

    // Have to do if we change from a vector to a list or something like that

    if( bDelInfo )
        delete pFilterInfo;

    return aPropStateArray;
}

void SvXMLExportPropertyMapper::ContextFilter(
        bool bEnableFoFontFamily,
        std::vector< XMLPropertyState >& rProperties,
        const Reference< XPropertySet >& rPropSet ) const
{
    // Derived class could implement this.
    if (mpImpl->mxNextMapper.is())
        mpImpl->mxNextMapper->ContextFilter(bEnableFoFontFamily, rProperties, rPropSet);
}

// Compares two Sequences of XMLPropertyState:
//  1.Number of elements equal ?
//  2.Index of each element equal ? (So I know whether the propertynames are the same)
//  3.Value of each element equal ?
bool SvXMLExportPropertyMapper::Equals(
        const std::vector< XMLPropertyState >& aProperties1,
        const std::vector< XMLPropertyState >& aProperties2 ) const
{
    if (aProperties1.size() < aProperties2.size())
        return true;
    if (aProperties1.size() > aProperties2.size())
        return false;

    sal_uInt32 nCount = aProperties1.size();

    for (sal_uInt32 nIndex = 0; nIndex < nCount; ++nIndex)
    {
        const XMLPropertyState& rProp1 = aProperties1[ nIndex ];
        const XMLPropertyState& rProp2 = aProperties2[ nIndex ];

        // Compare index. If equal, compare value
        if( rProp1.mnIndex < rProp2.mnIndex )
            return true;
        if( rProp1.mnIndex > rProp2.mnIndex )
            return false;

        if( rProp1.mnIndex != -1 )
        {
            // Now compare values
            if ( (mpImpl->mxPropMapper->GetEntryType( rProp1.mnIndex ) &
                  XML_TYPE_BUILDIN_CMP ) != 0 )
            {
                // simple type ( binary compare )
                if ( rProp1.maValue != rProp2.maValue)
                    return false;
            }
            else
            {
                // complex type ( ask for compare-function )
                if (!mpImpl->mxPropMapper->GetPropertyHandler(
                            rProp1.mnIndex )->equals( rProp1.maValue,
                                                      rProp2.maValue ))
                    return false;
            }
        }
    }

    return true;
}

// Compares two Sequences of XMLPropertyState:
//  1.Number of elements equal ?
//  2.Index of each element equal ? (So I know whether the propertynames are the same)
//  3.Value of each element equal ?
bool SvXMLExportPropertyMapper::LessPartial(
        const std::vector< XMLPropertyState >& aProperties1,
        const std::vector< XMLPropertyState >& aProperties2 ) const
{
    if (aProperties1.size() < aProperties2.size())
        return true;
    if (aProperties1.size() > aProperties2.size())
        return false;

    sal_uInt32 nCount = aProperties1.size();

    for (sal_uInt32 nIndex = 0; nIndex < nCount; ++nIndex)
    {
        const XMLPropertyState& rProp1 = aProperties1[ nIndex ];
        const XMLPropertyState& rProp2 = aProperties2[ nIndex ];

        // Compare index. If equal, compare value
        if( rProp1.mnIndex < rProp2.mnIndex )
            return true;
        if( rProp1.mnIndex > rProp2.mnIndex )
            return false;

        if( rProp1.mnIndex != -1 )
        {
            // Now compare values
            if ( (mpImpl->mxPropMapper->GetEntryType( rProp1.mnIndex ) &
                  XML_TYPE_BUILDIN_CMP ) != 0 )
            {
                // simple type ( binary compare )
                if ( comphelper::anyLess(rProp1.maValue, rProp2.maValue) )
                    return true;
                if ( comphelper::anyLess(rProp2.maValue, rProp1.maValue ) )
                    return false;
            }
        }
    }

    return false;
}

/** fills the given attribute list with the items in the given set
void SvXMLExportPropertyMapper::exportXML( SvXMLAttributeList& rAttrList,
        const ::std::vector< XMLPropertyState >& rProperties,
        const SvXMLUnitConverter& rUnitConverter,
        const SvXMLNamespaceMap& rNamespaceMap,
        sal_uInt16 nFlags ) const
{
    _exportXML( rAttrList, rProperties, rUnitConverter, rNamespaceMap,
                nFlags, 0, -1, -1 );
}

void SvXMLExportPropertyMapper::exportXML( SvXMLAttributeList& rAttrList,
        const ::std::vector< XMLPropertyState >& rProperties,
        const SvXMLUnitConverter& rUnitConverter,
        const SvXMLNamespaceMap& rNamespaceMap,
        sal_Int32 nPropMapStartIdx, sal_Int32 nPropMapEndIdx,
        sal_uInt16 nFlags ) const
{
    _exportXML( rAttrList, rProperties, rUnitConverter, rNamespaceMap,
                nFlags, 0, nPropMapStartIdx, nPropMapEndIdx );
}
*/

void SvXMLExportPropertyMapper::exportXML(
        SvXMLExport& rExport,
        const ::std::vector< XMLPropertyState >& rProperties,
        SvXmlExportFlags nFlags,
        bool bUseExtensionNamespaceForGraphicProperties) const
{
    exportXML(rExport, rProperties, -1, -1,  nFlags, bUseExtensionNamespaceForGraphicProperties);
}


void SvXMLExportPropertyMapper::exportXML(
        SvXMLExport& rExport,
        const ::std::vector< XMLPropertyState >& rProperties,
        sal_Int32 nPropMapStartIdx, sal_Int32 nPropMapEndIdx,
        SvXmlExportFlags nFlags, bool bUseExtensionNamespaceForGraphicProperties) const
{
    sal_uInt16 nPropTypeFlags = 0;
    for( sal_uInt16 i=0; i<MAX_PROP_TYPES; ++i )
    {
        sal_uInt16 nPropType = aPropTokens[i].nType;
        if( 0==i || (nPropTypeFlags & (1 << nPropType)) != 0 )
        {
            sal_uInt16 nNamespace = XML_NAMESPACE_STYLE;
            if (bUseExtensionNamespaceForGraphicProperties &&
                aPropTokens[i].eToken == xmloff::token::XML_GRAPHIC_PROPERTIES)
            {
                nNamespace = XML_NAMESPACE_LO_EXT;
                if ((rExport.getSaneDefaultVersion() & SvtSaveOptions::ODFSVER_EXTENDED) == 0)
                {
                    continue; // don't write for ODF <= 1.2
                }
            }

            std::vector<sal_uInt16> aIndexArray;

            _exportXML( nPropType, nPropTypeFlags,
                        rExport.GetAttrList(), rProperties,
                        rExport.GetMM100UnitConverter(),
                        rExport.GetNamespaceMap(),
                        &aIndexArray,
                        nPropMapStartIdx, nPropMapEndIdx );

            if( rExport.GetAttrList().getLength() > 0 ||
                !aIndexArray.empty() )
            {
                SvXMLElementExport aElem( rExport, nNamespace,
                                  aPropTokens[i].eToken,
                                  bool(nFlags & SvXmlExportFlags::IGN_WS),
                                  false );

                exportElementItems( rExport, rProperties, nFlags, aIndexArray );
            }
        }
    }
}

/** this method is called for every item that has the
    MID_FLAG_SPECIAL_ITEM_EXPORT flag set */
void SvXMLExportPropertyMapper::handleSpecialItem(
        comphelper::AttributeList& rAttrList,
        const XMLPropertyState& rProperty,
        const SvXMLUnitConverter& rUnitConverter,
        const SvXMLNamespaceMap& rNamespaceMap,
        const ::std::vector< XMLPropertyState > *pProperties,
        sal_uInt32 nIdx ) const
{
    if (mpImpl->mxNextMapper.is())
    {
        mpImpl->mxNextMapper->handleSpecialItem(
            rAttrList, rProperty, rUnitConverter, rNamespaceMap, pProperties, nIdx);
        return;
    }
    // An empty Any (e.g. unset UserDefinedAttributes / ChartUserDefinedAttributes)
    // or a null XNameContainer reference means there is nothing to export; this
    // is a normal state, not a missing-handler bug.
    if (!rProperty.maValue.hasValue()
        || rProperty.maValue.getValueTypeClass() == cpo::uno::TypeClass_INTERFACE)
        return;
    SAL_WARN("xmloff.style",
             "special item not handled in xml export: index=" << rProperty.mnIndex
             << " ns=" << mpImpl->mxPropMapper->GetEntryNameSpace(rProperty.mnIndex)
             << " xmlName=" << mpImpl->mxPropMapper->GetEntryXMLName(rProperty.mnIndex)
             << " apiName=" << mpImpl->mxPropMapper->GetEntryAPIName(rProperty.mnIndex));
}

/** this method is called for every item that has the
    MID_FLAG_ELEMENT_EXPORT flag set */
void SvXMLExportPropertyMapper::handleElementItem(
        SvXMLExport& rExport,
        const XMLPropertyState& rProperty,
        SvXmlExportFlags nFlags,
        const ::std::vector< XMLPropertyState > *pProperties,
        sal_uInt32 nIdx ) const
{
    if (mpImpl->mxNextMapper.is())
    {
        mpImpl->mxNextMapper->handleElementItem(rExport, rProperty, nFlags, pProperties, nIdx);
        return;
    }
    // An empty Any or a null interface (e.g. an unset complex color or a
    // missing background graphic) means there is no child element to write.
    // Treat that as a normal "nothing to export" state, not a missing-handler bug.
    if (!rProperty.maValue.hasValue()
        || rProperty.maValue.getValueTypeClass() == cpo::uno::TypeClass_INTERFACE)
        return;
    SAL_WARN("xmloff.style",
             "element item not handled in xml export: index=" << rProperty.mnIndex
             << " ns=" << mpImpl->mxPropMapper->GetEntryNameSpace(rProperty.mnIndex)
             << " xmlName=" << mpImpl->mxPropMapper->GetEntryXMLName(rProperty.mnIndex)
             << " apiName=" << mpImpl->mxPropMapper->GetEntryAPIName(rProperty.mnIndex));
}

// protected methods

/** fills the given attribute list with the items in the given set */
void SvXMLExportPropertyMapper::_exportXML(
        sal_uInt16 nPropType, sal_uInt16& rPropTypeFlags,
        comphelper::AttributeList& rAttrList,
        const ::std::vector< XMLPropertyState >& rProperties,
        const SvXMLUnitConverter& rUnitConverter,
        const SvXMLNamespaceMap& rNamespaceMap,
        std::vector<sal_uInt16>* pIndexArray,
        sal_Int32 nPropMapStartIdx, sal_Int32 nPropMapEndIdx ) const
{
    const sal_uInt32 nCount = rProperties.size();
    sal_uInt32 nIndex = 0;

    if( -1 == nPropMapStartIdx )
        nPropMapStartIdx = 0;
    if( -1 == nPropMapEndIdx )
        nPropMapEndIdx = mpImpl->mxPropMapper->GetEntryCount();

    while( nIndex < nCount )
    {
        sal_Int32 nPropMapIdx = rProperties[nIndex].mnIndex;
        if( nPropMapIdx >= nPropMapStartIdx &&
            nPropMapIdx < nPropMapEndIdx  )// valid entry?
        {
            sal_uInt32 nEFlags = mpImpl->mxPropMapper->GetEntryFlags(nPropMapIdx);
            sal_uInt16 nEPType = GET_PROP_TYPE(nEFlags);
            OSL_ENSURE(nEPType >= (XML_TYPE_PROP_START >> XML_TYPE_PROP_SHIFT),
                       "no prop type specified");
            rPropTypeFlags |= (1 << nEPType);
            if( nEPType == nPropType )
            {
                // we have a valid map entry here, so let's use it...
                if( ( nEFlags & MID_FLAG_ELEMENT_ITEM_EXPORT ) != 0 )
                {
                    // element items do not add any properties,
                    // we export it later
                    if( pIndexArray )
                    {
                        pIndexArray->push_back( static_cast<sal_uInt16>(nIndex) );
                    }
                }
                else
                {
                    _exportXML( rAttrList, rProperties[nIndex], rUnitConverter,
                                rNamespaceMap, &rProperties, nIndex );
                }
            }
        }

        nIndex++;
    }
}

namespace
{
// -1 = Attribute needs extended namespace, but current ODF version is strict.
// 1 = Attribute needs extended namespace and current ODF version allows it.
// 0 = Attribute does not need extended namespace
sal_Int8 CheckExtendedNamespace(std::u16string_view sXMLAttributeName, std::u16string_view sValue,
                                const SvtSaveOptions::ODFSaneDefaultVersion nODFVersion)
{
    if (IsXMLToken(sXMLAttributeName, XML_WRITING_MODE)
        && (IsXMLToken(sValue, XML_BT_LR) || IsXMLToken(sValue, XML_TB_RL90)))
        return nODFVersion & SvtSaveOptions::ODFSVER_EXTENDED ? 1 : -1;
    else if (IsXMLToken(sXMLAttributeName, XML_VERTICAL_REL)
             && (IsXMLToken(sValue, XML_PAGE_CONTENT_BOTTOM)
                 || IsXMLToken(sValue, XML_PAGE_CONTENT_TOP)))
        return nODFVersion & SvtSaveOptions::ODFSVER_EXTENDED ? 1 : -1;
    // don't export end zones when they have the default no-limit value
    else if (IsXMLToken(sXMLAttributeName, XML_HYPHENATION_ZONE_ALWAYS)
        || IsXMLToken(sXMLAttributeName, XML_HYPHENATION_ZONE_COLUMN)
        || IsXMLToken(sXMLAttributeName, XML_HYPHENATION_ZONE_PAGE)
        || IsXMLToken(sXMLAttributeName, XML_HYPHENATION_ZONE_SPREAD))
        return IsXMLToken(sValue, XML_NO_LIMIT) ? -1 : 1;
    // don't export word spacing and glyph scaling when they have the default 100% value
    else if (IsXMLToken(sXMLAttributeName, XML_WORD_SPACING)
        || IsXMLToken(sXMLAttributeName, XML_WORD_SPACING_MINIMUM)
        || IsXMLToken(sXMLAttributeName, XML_WORD_SPACING_MAXIMUM)
        || IsXMLToken(sXMLAttributeName, XML_TEXT_SCALE_MINIMUM)
        || IsXMLToken(sXMLAttributeName, XML_TEXT_SCALE_MAXIMUM))
    {
        static constexpr OUString s100PercentCompare( u"100%"_ustr );
        size_t nBegin = sValue.find( s100PercentCompare );
        return nBegin != std::u16string_view::npos ? -1 : 1;
    }
    // don't export letter spacing when they have the default 0% value
    else if (IsXMLToken(sXMLAttributeName, XML_LETTER_SPACING_MINIMUM)
        || IsXMLToken(sXMLAttributeName, XML_LETTER_SPACING_MAXIMUM))
    {
        static constexpr OUString sDefaultPercentCompare( u"0%"_ustr );
        size_t nBegin = sValue.find( sDefaultPercentCompare );
        return nBegin == static_cast<size_t>(0) ? -1 : 1;
    }
    return 0;
}
}

void SvXMLExportPropertyMapper::_exportXML(
        comphelper::AttributeList& rAttrList,
        const XMLPropertyState& rProperty,
        const SvXMLUnitConverter& rUnitConverter,
        const SvXMLNamespaceMap& rNamespaceMap,
        const ::std::vector< XMLPropertyState > *pProperties,
        sal_uInt32 nIdx ) const
{
    if ((mpImpl->mxPropMapper->GetEntryFlags(rProperty.mnIndex) & MID_FLAG_SPECIAL_ITEM_EXPORT) != 0)
    {
        uno::Reference< container::XNameContainer > xAttrContainer;
        if( (rProperty.maValue >>= xAttrContainer) && xAttrContainer.is() )
        {
            std::unique_ptr<SvXMLNamespaceMap> pNewNamespaceMap;
            const SvXMLNamespaceMap *pNamespaceMap = &rNamespaceMap;

            const cpo::uno::Sequence< OUString > aAttribNames( xAttrContainer->getElementNames() );

            xml::AttributeData aData;
            for( const auto& rAttribName : aAttribNames )
            {
                xAttrContainer->getByName( rAttribName ) >>= aData;
                OUString sAttribName( rAttribName );

                // extract namespace prefix from attribute name if it exists
                OUString sPrefix;
                const sal_Int32 nColonPos =
                    rAttribName.indexOf( ':' );
                if( nColonPos != -1 )
                    sPrefix = rAttribName.copy( 0, nColonPos );

                if( !sPrefix.isEmpty() )
                {
                    OUString sNamespace( aData.Namespace );

                    // if the prefix isn't defined yet or has another meaning,
                    // we have to redefine it now.
                    sal_uInt16 nKey = pNamespaceMap->GetKeyByPrefix( sPrefix );
                    if( USHRT_MAX == nKey || pNamespaceMap->GetNameByKey( nKey ) != sNamespace )
                    {
                        bool bAddNamespace = false;
                        if( USHRT_MAX == nKey )
                        {
                            // The prefix is unused, so it is sufficient
                            // to add it to the namespace map.
                            bAddNamespace = true;
                        }
                        else
                        {
                            // check if there is a prefix registered for the
                            // namespace URI
                            nKey = pNamespaceMap->GetKeyByName( sNamespace );
                            if( XML_NAMESPACE_UNKNOWN == nKey )
                            {
                                // There is no prefix for the namespace, so
                                // we have to generate one and have to add it.
                                sal_Int32 n=0;
                                OUString sOrigPrefix( sPrefix );
                                do
                                {
                                    sPrefix = sOrigPrefix + OUString::number( ++n );
                                    nKey = pNamespaceMap->GetKeyByPrefix( sPrefix );
                                }
                                while( nKey != USHRT_MAX );

                                bAddNamespace = true;
                            }
                            else
                            {
                                // If there is a prefix for the namespace,
                                // we reuse that.
                                sPrefix = pNamespaceMap->GetPrefixByKey( nKey );
                            }
                            // In any case, the attribute name has to be adapted.
                            sAttribName = sPrefix + ":" + rAttribName.subView(nColonPos+1);
                        }

                        if( bAddNamespace )
                        {
                            if( !pNewNamespaceMap )
                            {
                                pNewNamespaceMap.reset(new SvXMLNamespaceMap( rNamespaceMap ));
                                pNamespaceMap = pNewNamespaceMap.get();
                            }
                            pNewNamespaceMap->Add( sPrefix, sNamespace );
                            OUString sAttr = GetXMLToken(XML_XMLNS) + ":" + sPrefix;
                            rAttrList.AddAttribute( sAttr, sNamespace );
                        }
                    }
                }
                OUString sOldValue( rAttrList.getValueByName( sAttribName ) );
                OSL_ENSURE( sOldValue.isEmpty(), "alien attribute exists already" );
                OSL_ENSURE(aData.Type == GetXMLToken(XML_CDATA), "different type to our default type which should be written out");
                if( sOldValue.isEmpty() )
                    rAttrList.AddAttribute( sAttribName, aData.Value );
            }
        }
        else
        {
            handleSpecialItem( rAttrList, rProperty, rUnitConverter,
                               rNamespaceMap, pProperties, nIdx );
        }
    }
    else if ((mpImpl->mxPropMapper->GetEntryFlags(rProperty.mnIndex) & MID_FLAG_ELEMENT_ITEM_EXPORT ) == 0)
    {
        OUString aValue;
        OUString sName = rNamespaceMap.GetQNameByKey(
            mpImpl->mxPropMapper->GetEntryNameSpace(rProperty.mnIndex),
            mpImpl->mxPropMapper->GetEntryXMLName(rProperty.mnIndex));

        bool bRemove = false;
        if ((mpImpl->mxPropMapper->GetEntryFlags( rProperty.mnIndex ) & MID_FLAG_MERGE_ATTRIBUTE) != 0)
        {
            aValue = rAttrList.getValueByName( sName );
            bRemove = true;
        }

        if (mpImpl->mxPropMapper->exportXML(aValue, rProperty, rUnitConverter))
        {
            if( bRemove )
                rAttrList.RemoveAttribute( sName );

            // We don't seem to have a generic mechanism to write an attribute in the extension
            // namespace in case of certain attribute values only, so do this manually.
            sal_Int8 nExtendedStatus
                = CheckExtendedNamespace(mpImpl->mxPropMapper->GetEntryXMLName(rProperty.mnIndex),
                                         aValue, rUnitConverter.getSaneDefaultVersion());
            if (nExtendedStatus == -1)
                return;
            if (nExtendedStatus == 1)
                sName = rNamespaceMap.GetQNameByKey(
                    XML_NAMESPACE_LO_EXT, mpImpl->mxPropMapper->GetEntryXMLName(rProperty.mnIndex));
            rAttrList.AddAttribute( sName, aValue );
        }
    }
}

void SvXMLExportPropertyMapper::exportElementItems(
        SvXMLExport& rExport,
        const ::std::vector< XMLPropertyState >& rProperties,
        SvXmlExportFlags nFlags,
        const std::vector<sal_uInt16>& rIndexArray ) const
{
    bool bItemsExported = false;
    for (const sal_uInt16 nElement : rIndexArray)
    {
        OSL_ENSURE( 0 != (mpImpl->mxPropMapper->GetEntryFlags(
                rProperties[nElement].mnIndex ) & MID_FLAG_ELEMENT_ITEM_EXPORT),
                "wrong mid flag!" );

        rExport.IgnorableWhitespace();
        handleElementItem( rExport, rProperties[nElement],
                           nFlags, &rProperties, nElement );
        bItemsExported = true;
    }

    if( bItemsExported )
        rExport.IgnorableWhitespace();
}

const rtl::Reference<XMLPropertySetMapper>& SvXMLExportPropertyMapper::getPropertySetMapper() const
{
    return mpImpl->mxPropMapper;
}

void SvXMLExportPropertyMapper::SetStyleName( const OUString& rStyleName )
{
    mpImpl->maStyleName = rStyleName;
}

const OUString& SvXMLExportPropertyMapper::GetStyleName() const
{
    return mpImpl->maStyleName;
}

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