/* -*- 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 <memory>
#include <xmloff/unointerfacetouniqueidentifiermapper.hxx>
#include <com/sun/star/lang/XInitialization.hpp>
#include <com/sun/star/animations/AnimationTransformType.hpp>
#include <com/sun/star/animations/XAnimationNodeSupplier.hpp>
#include <com/sun/star/animations/AnimationNodeType.hpp>
#include <com/sun/star/animations/SequenceTimeContainer.hpp>
#include <com/sun/star/animations/XIterateContainer.hpp>
#include <com/sun/star/animations/XAnimateMotion.hpp>
#include <com/sun/star/animations/XAnimatePhysics.hpp>
#include <com/sun/star/animations/XAnimateColor.hpp>
#include <com/sun/star/animations/XAnimateTransform.hpp>
#include <com/sun/star/animations/XTransitionFilter.hpp>
#include <com/sun/star/animations/XCommand.hpp>
#include <com/sun/star/animations/XAudio.hpp>
#include <com/sun/star/animations/ValuePair.hpp>
#include <com/sun/star/animations/AnimationColorSpace.hpp>
#include <com/sun/star/presentation/EffectPresetClass.hpp>
#include <com/sun/star/animations/Timing.hpp>
#include <com/sun/star/animations/Event.hpp>
#include <com/sun/star/beans/XPropertySet.hpp>
#include <com/sun/star/io/WrongFormatException.hpp>
#include <com/sun/star/xml/sax/XFastAttributeList.hpp>
#include <com/sun/star/text/XTextCursor.hpp>
#include <com/sun/star/text/XTextRangeCompare.hpp>
#include <com/sun/star/presentation/ParagraphTarget.hpp>
#include <com/sun/star/container/XEnumerationAccess.hpp>
#include <com/sun/star/animations/EventTrigger.hpp>
#include <com/sun/star/presentation/EffectCommands.hpp>
#include <com/sun/star/util/Duration.hpp>
#include <comphelper/processfactory.hxx>
#include <comphelper/string.hxx>

#include <rtl/math.h>
#include <sal/log.hxx>
#include <comphelper/diagnose_ex.hxx>
#include <o3tl/string_view.hxx>
#include <sax/tools/converter.hxx>

#include <vector>

#include <xmloff/xmltypes.hxx>
#include "sdpropls.hxx"
#include <xmloff/xmltoken.hxx>
#include <xmloff/xmlimp.hxx>
#include <xmloff/xmlnamespace.hxx>
#include <xmloff/xmluconv.hxx>
#include <xmloff/xmlprhdl.hxx>
#include <xmlsdtypes.hxx>

#include <animations.hxx>
#include <animationimport.hxx>

using namespace ::cppu;
using namespace ::com::sun::star;
using namespace ::com::sun::star::beans;
using namespace ::com::sun::star::animations;
using namespace ::com::sun::star::presentation;
using namespace ::com::sun::star::drawing;
using namespace ::com::sun::star::uno;
using namespace ::xmloff::token;

using ::com::sun::star::xml::sax::XFastAttributeList;
using ::com::sun::star::beans::NamedValue;
using ::com::sun::star::text::XTextRange;
using ::com::sun::star::text::XTextCursor;
using ::com::sun::star::text::XTextRangeCompare;
using ::com::sun::star::container::XEnumerationAccess;
using ::com::sun::star::container::XEnumeration;
using ::com::sun::star::lang::XInitialization;

static OUString
lcl_GetMediaReference(SvXMLImport const& rImport, OUString const& rURL)
{
    if (rImport.IsPackageURL(rURL))
        return "vnd.sun.star.Package:" + rURL;

    return rImport.GetAbsoluteReference(rURL);
}

namespace xmloff
{

class AnimationsImportHelperImpl
{
private:
    SvXMLImport& mrImport;

public:
    explicit AnimationsImportHelperImpl( SvXMLImport& rImport );

    Any convertValue( XMLTokenEnum eAttributeName, const OUString& rValue );
    Sequence< Any > convertValueSequence( XMLTokenEnum eAttributeName, std::u16string_view rValue );

    Any convertTarget( const OUString& rValue );
    static Any convertPath( const OUString& rValue );
    Any convertTiming( const OUString& rValue );
    static Sequence< double > convertKeyTimes( std::string_view rValue );
    static Sequence< TimeFilterPair > convertTimeFilter( std::string_view rValue );
};

AnimationsImportHelperImpl::AnimationsImportHelperImpl( SvXMLImport& rImport )
:   mrImport( rImport )
{
}

static bool isDouble( std::string_view rValue )
{
    sal_Int32 nLength = rValue.size();
    const char * pStr = rValue.data();
    while( nLength )
    {
        if( (*pStr >= '0' && *pStr <= '9') || *pStr == '-' || *pStr == '.' || *pStr == '+' || *pStr == 'e' || *pStr == 'E' )
        {
            pStr++;
            nLength--;
        }
        else
        {
            return false;
        }
    }

    return true;
}

static bool isTime( const OUString& rValue )
{
    sal_Int32 nLength = rValue.getLength();
    const sal_Unicode * pStr;
    for( pStr = rValue.getStr(); nLength; pStr++, nLength-- )
    {
        if( !( (*pStr >= '0' && *pStr <= '9') || *pStr == '-' || *pStr == '.' || *pStr == '+' || *pStr == 'e' || *pStr == 'E' ) )
            break;
    }

    // return true if this is a double (if someone forgot the 's' we silently ignore it)
    // or if it's a double that ends with a 's' or 'S'
    return (nLength == 0) || ((*pStr == 's' || *pStr == 'S') && (nLength == 1));
}

Any AnimationsImportHelperImpl::convertTarget( const OUString& rValue )
{
    try
    {
        Reference< XInterface > xRef( mrImport.getInterfaceToIdentifierMapper().getReference( rValue ) );

        Reference< XShape > _xShape( xRef, UNO_QUERY );
        if( _xShape.is() )
            return Any( _xShape );

        Reference< XTextCursor > xTextCursor( xRef, UNO_QUERY );
        if( xTextCursor.is() )
        {
            Reference< XTextRange > xStart( xTextCursor->getStart() ), xRange;
            Reference< XShape > xShape( xTextCursor->getText(), UNO_QUERY_THROW );
            Reference< XTextRangeCompare > xTextRangeCompare( xShape, UNO_QUERY_THROW );

            Reference< XEnumerationAccess > xParaEnumAccess( xShape, UNO_QUERY_THROW );
            Reference< XEnumeration > xEnumeration( xParaEnumAccess->createEnumeration(), UNO_SET_THROW );
            sal_Int16 nParagraph = 0;

            while( xEnumeration->hasMoreElements() )
            {
                xEnumeration->nextElement() >>= xRange;

                // break if start of selection is prior to end of current paragraph
                if( xRange.is() && (xTextRangeCompare->compareRegionEnds( xStart, xRange ) >= 0 ) )
                {
                    return Any( ParagraphTarget( xShape, nParagraph ) );
                }

                nParagraph++;
            }
        }
    }
    catch (const RuntimeException&)
    {
        TOOLS_WARN_EXCEPTION("xmloff.draw", "");
    }

    Any aAny;
    return aAny;
}

Any AnimationsImportHelperImpl::convertValue( XMLTokenEnum eAttributeName, const OUString& rValue )
{
    sal_Int32 nCommaPos = -1, nPos;
    sal_Int32 nOpenBrackets = 0;
    for( nPos = 0; (nPos < rValue.getLength()) && (nCommaPos == -1); nPos++ )
    {
        switch( rValue[nPos] )
        {
        case ',':
            if( nOpenBrackets == 0 )
                nCommaPos = nPos;
            break;
        case '(':
        case '[':
        case '{':
            nOpenBrackets++;
            break;
        case ')':
        case ']':
        case '}':
            nOpenBrackets--;
            break;
        }
    }

    if( nCommaPos >= 0 )
    {
        ValuePair aPair;
        aPair.First = convertValue( eAttributeName, rValue.copy( 0, nCommaPos ) );
        aPair.Second = convertValue( eAttributeName, rValue.copy( nCommaPos+1 ) );
        return Any( aPair );
    }
    else
    {
        Any aAny;
        sal_Int32 nType = XML_TYPE_STRING;

        if( rValue.getLength() ) switch( eAttributeName )
        {
        case XML_X:
        case XML_Y:
        case XML_WIDTH:
        case XML_HEIGHT:
        case XML_TRANSLATE:
        {
            return Any( rValue );
        }

        case XML_SCALE:
        case XML_SKEWY:
        case XML_SKEWX:
        case XML_OPACITY:
        case XML_ROTATE:            nType = XML_TYPE_DOUBLE;                    break;
        case XML_TEXT_ROTATION_ANGLE:nType = XML_TYPE_TEXT_ROTATION_ANGLE;      break;
        case XML_FILL_COLOR:
        case XML_STROKE_COLOR:
        case XML_DIM:
        case XML_COLOR:             nType = XML_TYPE_COLOR;                     break;
        case XML_FILL:              nType = XML_SD_TYPE_FILLSTYLE;              break;
        case XML_STROKE:            nType = XML_SD_TYPE_STROKE;                 break;
        case XML_FONT_WEIGHT:       nType = XML_TYPE_TEXT_WEIGHT;               break;
        case XML_FONT_STYLE:        nType = XML_TYPE_TEXT_POSTURE;              break;
        case XML_TEXT_UNDERLINE:    nType = XML_TYPE_TEXT_UNDERLINE_STYLE;      break;
        case XML_FONT_SIZE:         nType = XML_TYPE_DOUBLE_PERCENT;            break;
        case XML_VISIBILITY:        nType = XML_SD_TYPE_PRESPAGE_VISIBILITY;    break;

        default:
            if( !rValue.isEmpty() )
                aAny <<= rValue;
            return aAny;
        }

        const XMLPropertyHandler* pHandler = mrImport.GetShapeImport()->GetSdPropHdlFactory()->GetPropertyHandler( nType );
        if( pHandler )
            pHandler->importXML( rValue, aAny, mrImport.GetMM100UnitConverter() );

        return aAny;
    }
}

Sequence< Any > AnimationsImportHelperImpl::convertValueSequence( XMLTokenEnum eAttributeName, std::u16string_view rValue )
{
    Sequence< Any > aValues;

    const sal_Int32 nElements { comphelper::string::getTokenCount(rValue, ';') };
    if ( nElements>0 )
    {
        // prepare the sequence
        aValues.realloc( nElements );

        // fill the sequence
        Any* pValues = aValues.getArray();
        for (sal_Int32 nIndex = 0; nIndex >= 0; )
            *pValues++ = convertValue( eAttributeName, OUString(o3tl::getToken(rValue, 0, ';', nIndex )) );
    }

    return aValues;
}

Any AnimationsImportHelperImpl::convertTiming( const OUString& rValue )
{
    Any aAny;

    const sal_Int32 nElements { comphelper::string::getTokenCount(rValue, ';') };
    if ( nElements>0 )
    {
        if( nElements == 1 )
        {
            if( IsXMLToken( rValue, XML_MEDIA ) )
            {
                aAny <<= Timing_MEDIA;
            }
            else if( IsXMLToken( rValue, XML_INDEFINITE ) )
            {
                aAny <<= Timing_INDEFINITE;
            }
            else if( isTime( rValue ) )
            {
                aAny <<= rValue.toDouble();
            }
            else
            {
                Event aEvent;
                aEvent.Repeat = 0;
                aEvent.Trigger = 0;

                OUString aEventTrigger;

                sal_Int32 nPos = rValue.indexOf( '+' );
                if( nPos == -1 )
                {
                    aEventTrigger = rValue;
                }
                else
                {
                    aEventTrigger = rValue.copy( 0, nPos );

                    // convert offset
                    aEvent.Offset = convertTiming( rValue.copy( nPos + 1 ) );
                }

                nPos = aEventTrigger.indexOf( '.' );
                if( nPos != -1 )
                {
                    aEvent.Source <<= mrImport.getInterfaceToIdentifierMapper().getReference( aEventTrigger.copy( 0, nPos ) );
                    aEventTrigger = aEventTrigger.copy( nPos + 1 );
                }

                sal_Int16 nEnum;
                if( SvXMLUnitConverter::convertEnum( nEnum, aEventTrigger, aAnimations_EnumMap_EventTrigger ) )
                {
                    aEvent.Trigger = nEnum;
                }
                else
                {
                    OSL_FAIL("AnimationsImportHelperImpl::convertTiming(), unknown event trigger!");
                }

                aAny <<= aEvent;
            }
        }
        else
        {
            // fill the sequence
            Sequence< Any > aValues( nElements );
            Any* pValues = aValues.getArray();
            for (sal_Int32 nIndex = 0; nIndex >= 0; )
                *pValues++ = convertTiming( rValue.getToken( 0, ';', nIndex ) );

            aAny <<= aValues;
        }
    }
    return aAny;
}

Sequence< double > AnimationsImportHelperImpl::convertKeyTimes( std::string_view rValue )
{
    const sal_Int32 nElements { comphelper::string::getTokenCount(rValue, ';') };

    Sequence< double > aKeyTimes( nElements );

    if( nElements )
    {
        double* pValues = aKeyTimes.getArray();
        for (sal_Int32 nIndex = 0; nIndex >= 0; )
            *pValues++ = o3tl::toDouble(o3tl::getToken(rValue, 0, ';', nIndex ));
    }

    return aKeyTimes;
}

Sequence< TimeFilterPair > AnimationsImportHelperImpl::convertTimeFilter( std::string_view rValue )
{
    const sal_Int32 nElements { comphelper::string::getTokenCount(rValue, ';') };

    Sequence< TimeFilterPair > aTimeFilter( nElements );

    if( nElements )
    {
        TimeFilterPair* pValues = aTimeFilter.getArray();
        for (sal_Int32 nIndex = 0; nIndex >= 0; )
        {
            const std::string_view aToken( o3tl::getToken(rValue, 0, ';', nIndex ) );

            size_t nPos = aToken.find( ',' );
            if( nPos != std::string_view::npos )
            {
                pValues->Time = rtl_math_stringToDouble(
                    aToken.data(), aToken.data() + nPos, '.', 0, nullptr, nullptr);
                pValues->Progress = rtl_math_stringToDouble(
                    aToken.data() + nPos + 1, aToken.data() + aToken.size(), '.', 0,
                    nullptr, nullptr);
            }
            pValues++;
        }
    }

    return aTimeFilter;
}

Any AnimationsImportHelperImpl::convertPath( const OUString& rValue )
{
    return Any( rValue );
}


AnimationNodeContext::AnimationNodeContext(
        const Reference< XAnimationNode >& xParentNode,
        SvXMLImport& rImport, sal_Int32 nElement,
        const css::uno::Reference< css::xml::sax::XFastAttributeList>& xAttrList,
        const std::shared_ptr<AnimationsImportHelperImpl>& pHelper )
:   SvXMLImportContext(rImport),
    mpHelper( pHelper )
{
    bool bRootContext = !pHelper;
    try
    {
        if( bRootContext )
        {
            mpHelper = std::make_shared<AnimationsImportHelperImpl>( rImport );
            mxNode = xParentNode;
        }
        else
        {
            sal_Int16 nPresetClass = EffectPresetClass::CUSTOM;

            const char* pServiceName = nullptr;

            // we see namespace ANIMATION and ANIMATION_OOO and PRESENTATION_OASIS and PRESENTATION_SO52 and PRESENTATION_OOO
            switch( nElement & TOKEN_MASK )
            {
            case XML_SEQ:
                pServiceName = "com.sun.star.animations.SequenceTimeContainer"; break;
            case XML_ITERATE:
                pServiceName = "com.sun.star.animations.IterateContainer"; break;
            case XML_ANIMATE:
                pServiceName = "com.sun.star.animations.Animate"; break;
            case XML_SET:
                pServiceName = "com.sun.star.animations.AnimateSet"; break;
            case XML_ANIMATEMOTION:
                pServiceName = "com.sun.star.animations.AnimateMotion"; break;
            case XML_ANIMATEPHYSICS:
                pServiceName = "com.sun.star.animations.AnimatePhysics"; break;
            case XML_ANIMATECOLOR:
                pServiceName = "com.sun.star.animations.AnimateColor"; break;
            case XML_ANIMATETRANSFORM:
                pServiceName = "com.sun.star.animations.AnimateTransform"; break;
            case XML_TRANSITIONFILTER:
                pServiceName = "com.sun.star.animations.TransitionFilter"; break;
            case XML_AUDIO:
                pServiceName = "com.sun.star.animations.Audio"; break;
            case XML_COMMAND:
                pServiceName = "com.sun.star.animations.Command"; break;
            case XML_PAR:
                {
                    for (auto &aIter : sax_fastparser::castToFastAttributeList( xAttrList ))
                    {
                        if( (aIter.getToken() & TOKEN_MASK) == XML_PRESET_ID)
                        {
                            const OUString aValue = aIter.toString();
                            if ( aValue == "ooo-entrance-random" )
                            {
                                nPresetClass = EffectPresetClass::ENTRANCE;
                            }
                            else if ( aValue == "ooo-exit-random" )
                            {
                                nPresetClass = EffectPresetClass::EXIT;
                            }

                            if( nPresetClass != EffectPresetClass::CUSTOM )
                            {
                                pServiceName = "com.sun.star.comp.sd.RandomAnimationNode";
                                break;
                            }
                        }
                    }
                    if( !pServiceName )
                        pServiceName = "com.sun.star.animations.ParallelTimeContainer";
                }
                break;
            default:
                SAL_WARN("xmloff", "unexpected token '" + SvXMLImport::getNameFromToken(nElement)
                            << "' 0x" << std::hex << nElement);
                break;
            }

            if( pServiceName )
            {
                const Reference< XComponentContext >& xContext( ::comphelper::getProcessComponentContext() );

                mxNode.set(
                    xContext->getServiceManager()->createInstanceWithContext(OUString::createFromAscii(pServiceName), xContext),
                    UNO_QUERY_THROW );

                if( nPresetClass != EffectPresetClass::CUSTOM )
                {
                    Reference< XInitialization > xInit( mxNode, UNO_QUERY_THROW );
                    const Any aAny( nPresetClass );
                    Sequence< Any > aArgs( &aAny, 1 ) ;
                    xInit->initialize( aArgs );
                }

                init_node( xAttrList );

                Reference< XTimeContainer > xParentContainer( xParentNode, UNO_QUERY_THROW );
                xParentContainer->appendChild( mxNode );
            }
        }
    }
    catch (const RuntimeException&)
    {
        TOOLS_WARN_EXCEPTION("xmloff.draw", "");
    }
}

void AnimationNodeContext::init_node(  const css::uno::Reference< css::xml::sax::XFastAttributeList >& xAttrList )
{
    if( !mxNode.is() )
        return;

    try
    {
        const sal_Int16 nNodeType = mxNode->getType();

        // query for optional interfaces that are often used later
        Reference< XAnimate > xAnimate( mxNode, UNO_QUERY );
        Reference< XCommand > xCommand( mxNode, UNO_QUERY );
        Reference< XTransitionFilter > xTransitionFilter( mxNode, UNO_QUERY );
        Reference< XIterateContainer > xIter( mxNode, UNO_QUERY );

        std::vector< NamedValue > aUserData;
        XMLTokenEnum meAttributeName = XML_TOKEN_INVALID;
        OUString aFrom, aBy, aTo, aValues;
        bool bHaveXmlId( false );
        OUString sXmlId;

        sal_Int16 nEnum;
        for (auto &aIter : sax_fastparser::castToFastAttributeList( xAttrList ))
        {
            auto nToken = aIter.getToken();
            switch( nToken )
            {
            case XML_ELEMENT(SMIL, XML_BEGIN):
            case XML_ELEMENT(SMIL_COMPAT, XML_BEGIN):
            case XML_ELEMENT(SMIL_SO52, XML_BEGIN):
            {
                mxNode->setBegin( mpHelper->convertTiming( aIter.toString() ) );
            }
            break;
            case XML_ELEMENT(SMIL, XML_DUR):
            case XML_ELEMENT(SMIL_COMPAT, XML_DUR):
            case XML_ELEMENT(SMIL_SO52, XML_DUR):
            {
                mxNode->setDuration( mpHelper->convertTiming( aIter.toString() ) );
            }
            break;
            case XML_ELEMENT(SMIL, XML_END):
            case XML_ELEMENT(SMIL_COMPAT, XML_END):
            case XML_ELEMENT(SMIL_SO52, XML_END):
            {
                mxNode->setEnd( mpHelper->convertTiming( aIter.toString() ) );
            }
            break;
            case XML_ELEMENT(SMIL, XML_FILL):
            case XML_ELEMENT(SMIL_COMPAT, XML_FILL):
            case XML_ELEMENT(SMIL_SO52, XML_FILL):
            {
                if( SvXMLUnitConverter::convertEnum( nEnum, aIter.toView(), aAnimations_EnumMap_Fill ) )
                    mxNode->setFill( nEnum );
            }
            break;
            case XML_ELEMENT(SMIL, XML_FILLDEFAULT):
            case XML_ELEMENT(SMIL_COMPAT, XML_FILLDEFAULT):
            case XML_ELEMENT(SMIL_SO52, XML_FILLDEFAULT):
            {
                if( SvXMLUnitConverter::convertEnum( nEnum, aIter.toView(), aAnimations_EnumMap_FillDefault ) )
                    mxNode->setFillDefault( nEnum );
            }
            break;
            case XML_ELEMENT(SMIL, XML_RESTART):
            case XML_ELEMENT(SMIL_COMPAT, XML_RESTART):
            case XML_ELEMENT(SMIL_SO52, XML_RESTART):
            {
                if( SvXMLUnitConverter::convertEnum( nEnum, aIter.toView(), aAnimations_EnumMap_Restart ) )
                    mxNode->setRestart( nEnum );
            }
            break;
            case XML_ELEMENT(SMIL, XML_RESTARTDEFAULT):
            case XML_ELEMENT(SMIL_COMPAT, XML_RESTARTDEFAULT):
            case XML_ELEMENT(SMIL_SO52, XML_RESTARTDEFAULT):
            {
                if( SvXMLUnitConverter::convertEnum( nEnum, aIter.toView(), aAnimations_EnumMap_RestartDefault ) )
                    mxNode->setRestartDefault( nEnum );
            }
            break;
            case XML_ELEMENT(SMIL, XML_ACCELERATE):
            case XML_ELEMENT(SMIL_COMPAT, XML_ACCELERATE):
            case XML_ELEMENT(SMIL_SO52, XML_ACCELERATE):
            {
                if( isDouble( aIter.toView() ) )
                    mxNode->setAcceleration( aIter.toDouble() );
            }
            break;
            case XML_ELEMENT(SMIL, XML_DECELERATE):
            case XML_ELEMENT(SMIL_COMPAT, XML_DECELERATE):
            case XML_ELEMENT(SMIL_SO52, XML_DECELERATE):
            {
                if( isDouble( aIter.toView() ) )
                    mxNode->setDecelerate( aIter.toDouble() );
            }
            break;
            case XML_ELEMENT(SMIL, XML_AUTOREVERSE):
            case XML_ELEMENT(SMIL_COMPAT, XML_AUTOREVERSE):
            case XML_ELEMENT(SMIL_SO52, XML_AUTOREVERSE):
            {
                bool bTemp;
                if (::sax::Converter::convertBool( bTemp, aIter.toView() ))
                    mxNode->setAutoReverse( bTemp  );
            }
            break;
            case XML_ELEMENT(SMIL, XML_REPEATCOUNT):
            case XML_ELEMENT(SMIL_COMPAT, XML_REPEATCOUNT):
            case XML_ELEMENT(SMIL_SO52, XML_REPEATCOUNT):
            {
                mxNode->setRepeatCount( mpHelper->convertTiming( aIter.toString() ) );
            }
            break;
            case XML_ELEMENT(SMIL, XML_REPEATDUR):
            case XML_ELEMENT(SMIL_COMPAT, XML_REPEATDUR):
            case XML_ELEMENT(SMIL_SO52, XML_REPEATDUR):
            {
                mxNode->setRepeatDuration( mpHelper->convertTiming( aIter.toString() ) );
            }
            break;
            case XML_ELEMENT(SMIL, XML_ENDSYNC):
            case XML_ELEMENT(SMIL_COMPAT, XML_ENDSYNC):
            case XML_ELEMENT(SMIL_SO52, XML_ENDSYNC):
            {
                if( SvXMLUnitConverter::convertEnum( nEnum, aIter.toView(), aAnimations_EnumMap_Endsync ) )
                    mxNode->setEndSync( Any( nEnum ) );
            }
            break;
            case XML_ELEMENT(PRESENTATION, XML_NODE_TYPE):
            case XML_ELEMENT(PRESENTATION_SO52, XML_NODE_TYPE):
            case XML_ELEMENT(PRESENTATION_OOO, XML_NODE_TYPE):
            case XML_ELEMENT(PRESENTATION_OASIS, XML_NODE_TYPE):
            {
                if( SvXMLUnitConverter::convertEnum( nEnum, aIter.toView(), aAnimations_EnumMap_EffectNodeType ) )
                    aUserData.emplace_back( GetXMLToken( XML_NODE_TYPE ), Any( nEnum ) );
            }
            break;
            case XML_ELEMENT(PRESENTATION, XML_PRESET_ID):
            case XML_ELEMENT(PRESENTATION_SO52, XML_PRESET_ID):
            case XML_ELEMENT(PRESENTATION_OOO, XML_PRESET_ID):
            case XML_ELEMENT(PRESENTATION_OASIS, XML_PRESET_ID):
            {
                aUserData.emplace_back( GetXMLToken( XML_PRESET_ID ), Any( aIter.toString() ) );
            }
            break;
            case XML_ELEMENT(PRESENTATION, XML_PRESET_SUB_TYPE):
            case XML_ELEMENT(PRESENTATION_SO52, XML_PRESET_SUB_TYPE):
            case XML_ELEMENT(PRESENTATION_OOO, XML_PRESET_SUB_TYPE):
            case XML_ELEMENT(PRESENTATION_OASIS, XML_PRESET_SUB_TYPE):
            {
                aUserData.emplace_back( GetXMLToken( XML_PRESET_SUB_TYPE ), Any( aIter.toString() ) );
            }
            break;
            case XML_ELEMENT(PRESENTATION, XML_PRESET_CLASS):
            case XML_ELEMENT(PRESENTATION_SO52, XML_PRESET_CLASS):
            case XML_ELEMENT(PRESENTATION_OOO, XML_PRESET_CLASS):
            case XML_ELEMENT(PRESENTATION_OASIS, XML_PRESET_CLASS):
            {
                if( SvXMLUnitConverter::convertEnum( nEnum, aIter.toView(), aAnimations_EnumMap_EffectPresetClass ) )
                    aUserData.emplace_back( GetXMLToken( XML_PRESET_CLASS ), Any( nEnum ) );
            }
            break;
            case XML_ELEMENT(PRESENTATION, XML_AFTER_EFFECT):
            case XML_ELEMENT(PRESENTATION_SO52, XML_AFTER_EFFECT):
            case XML_ELEMENT(PRESENTATION_OOO, XML_AFTER_EFFECT):
            {
                bool bTemp;
                if (::sax::Converter::convertBool( bTemp, aIter.toView() ))
                    aUserData.emplace_back( GetXMLToken( XML_AFTER_EFFECT ), Any( bTemp ) );
            }
            break;
            case XML_ELEMENT(XLINK, XML_HREF):
            {
                if( nNodeType == AnimationNodeType::AUDIO )
                {
                    Reference< XAudio > xAudio( mxNode, UNO_QUERY_THROW );
                    xAudio->setSource( Any(lcl_GetMediaReference(GetImport(), aIter.toString())) );
                    break;
                }
                [[fallthrough]];
            }
            case XML_ELEMENT(SMIL, XML_TARGETELEMENT):
            case XML_ELEMENT(SMIL_COMPAT, XML_TARGETELEMENT):
            case XML_ELEMENT(SMIL_SO52, XML_TARGETELEMENT):
            {
                Any aTarget( mpHelper->convertTarget( aIter.toString() ) );

                if( xAnimate.is() )
                {
                    xAnimate->setTarget( aTarget );
                }
                else if( xIter.is() )
                {
                    xIter->setTarget( aTarget );
                }
                else if( xCommand.is() )
                {
                    xCommand->setTarget( aTarget );
                }
            }
            break;

            case XML_ELEMENT(ANIMATION, XML_AUDIO_LEVEL):
            case XML_ELEMENT(ANIMATION_OOO, XML_AUDIO_LEVEL):
            {
                if( nNodeType == AnimationNodeType::AUDIO )
                {
                    if( isDouble( aIter.toView() ) )
                    {
                        Reference< XAudio > xAudio( mxNode, UNO_QUERY_THROW );
                        xAudio->setVolume( aIter.toDouble() );
                    }
                }
            }
            break;

            case XML_ELEMENT(PRESENTATION, XML_MASTER_ELEMENT):
            case XML_ELEMENT(PRESENTATION_SO52, XML_MASTER_ELEMENT):
            case XML_ELEMENT(PRESENTATION_OOO, XML_MASTER_ELEMENT):
            {
                Reference< XAnimationNode > xMaster( GetImport().getInterfaceToIdentifierMapper().getReference( aIter.toString() ), UNO_QUERY );
                aUserData.emplace_back( GetXMLToken( XML_MASTER_ELEMENT ), Any( xMaster ) );
            }
            break;

            case XML_ELEMENT(ANIMATION, XML_SUB_ITEM):
            case XML_ELEMENT(ANIMATION_OOO, XML_SUB_ITEM):
            {
                if( SvXMLUnitConverter::convertEnum( nEnum, aIter.toView(), aAnimations_EnumMap_SubItem ) )
                {
                    if( xAnimate.is() )
                    {
                        xAnimate->setSubItem( nEnum );
                    }
                    else if( xIter.is() )
                    {
                        xIter->setSubItem( nEnum );
                    }
                }
            }
            break;

            case XML_ELEMENT(SMIL, XML_ATTRIBUTENAME):
            case XML_ELEMENT(SMIL_COMPAT, XML_ATTRIBUTENAME):
            case XML_ELEMENT(SMIL_SO52, XML_ATTRIBUTENAME):
            {
                if( xAnimate.is() )
                {
                    OUString aName( aIter.toString() );

                    const struct ImplAttributeNameConversion* p = getAnimationAttributeNamesConversionList();
                    while( !p->maAPIName.isEmpty() )
                    {
                        if( IsXMLToken( aIter, p->meXMLToken ) )
                        {
                            aName = p->maAPIName;
                            meAttributeName = p->meXMLToken;
                            break;
                        }

                        p++;
                    }

                    xAnimate->setAttributeName( aName );
                }
            }
            break;

            case XML_ELEMENT(SMIL, XML_VALUES):
            case XML_ELEMENT(SMIL_COMPAT, XML_VALUES):
            case XML_ELEMENT(SMIL_SO52, XML_VALUES):
            {
                aValues = aIter.toString();
            }
            break;

            case XML_ELEMENT(SMIL, XML_FROM):
            case XML_ELEMENT(SMIL_COMPAT, XML_FROM):
            case XML_ELEMENT(SMIL_SO52, XML_FROM):
            {
                aFrom = aIter.toString();
            }
            break;

            case XML_ELEMENT(SMIL, XML_BY):
            case XML_ELEMENT(SMIL_COMPAT, XML_BY):
            case XML_ELEMENT(SMIL_SO52, XML_BY):
            {
                aBy = aIter.toString();
            }
            break;

            case XML_ELEMENT(SMIL, XML_TO):
            case XML_ELEMENT(SMIL_COMPAT, XML_TO):
            case XML_ELEMENT(SMIL_SO52, XML_TO):
            {
                aTo = aIter.toString();
            }
            break;

            case XML_ELEMENT(SMIL, XML_KEYTIMES):
            case XML_ELEMENT(SMIL_COMPAT, XML_KEYTIMES):
            case XML_ELEMENT(SMIL_SO52, XML_KEYTIMES):
            {
                if( xAnimate.is() )
                    xAnimate->setKeyTimes( AnimationsImportHelperImpl::convertKeyTimes( aIter.toView() ) );
            }
            break;

            case XML_ELEMENT(ANIMATION, XML_FORMULA):
            case XML_ELEMENT(ANIMATION_OOO, XML_FORMULA):
            {
                if( xAnimate.is() )
                    xAnimate->setFormula( aIter.toString() );
            }
            break;

            case XML_ELEMENT(ANIMATION, XML_ID):
            case XML_ELEMENT(ANIMATION_OOO, XML_ID):
            {
                if (!bHaveXmlId) { sXmlId = aIter.toString(); }
            }
            break;
            case XML_ELEMENT(XML, XML_ID):
            {
                sXmlId = aIter.toString();
                bHaveXmlId = true;
            }
            break;

            case XML_ELEMENT(SMIL, XML_CALCMODE):
            case XML_ELEMENT(SMIL_COMPAT, XML_CALCMODE):
            case XML_ELEMENT(SMIL_SO52, XML_CALCMODE):
            {
                if( xAnimate.is() )
                {
                    if( SvXMLUnitConverter::convertEnum( nEnum, aIter.toView(), aAnimations_EnumMap_CalcMode ) )
                        xAnimate->setCalcMode( nEnum );
                }
            }
            break;

            case XML_ELEMENT(SMIL, XML_ACCUMULATE):
            case XML_ELEMENT(SMIL_COMPAT, XML_ACCUMULATE):
            case XML_ELEMENT(SMIL_SO52, XML_ACCUMULATE):
            {
                if( xAnimate.is() )
                    xAnimate->setAccumulate( IsXMLToken( aIter, XML_SUM ) );
            }
            break;

            case XML_ELEMENT(PRESENTATION, XML_ADDITIVE):
            case XML_ELEMENT(PRESENTATION_SO52, XML_ADDITIVE):
            case XML_ELEMENT(PRESENTATION_OOO, XML_ADDITIVE):
            case XML_ELEMENT(SMIL, XML_ADDITIVE):
            case XML_ELEMENT(SMIL_COMPAT, XML_ADDITIVE):
            case XML_ELEMENT(SMIL_SO52, XML_ADDITIVE):
            {
                if( xAnimate.is() )
                {
                    if( SvXMLUnitConverter::convertEnum( nEnum, aIter.toView(), aAnimations_EnumMap_AdditiveMode ) )
                        xAnimate->setAdditive( nEnum );
                }
            }
            break;

            case XML_ELEMENT(SMIL, XML_KEYSPLINES):
            case XML_ELEMENT(SMIL_COMPAT, XML_KEYSPLINES):
            case XML_ELEMENT(SMIL_SO52, XML_KEYSPLINES):
            {
                if( xAnimate.is() )
                    xAnimate->setTimeFilter( AnimationsImportHelperImpl::convertTimeFilter( aIter.toView() ) );
            }
            break;

            case XML_ELEMENT(SVG, XML_PATH):
            case XML_ELEMENT(SVG_COMPAT, XML_PATH):
            {
                Reference< XAnimateMotion > xAnimateMotion( mxNode, UNO_QUERY );
                if( xAnimateMotion.is() )
                    xAnimateMotion->setPath( AnimationsImportHelperImpl::convertPath( aIter.toString() ) );
            }
            break;

            case XML_ELEMENT(ANIMATION, XML_PHYSICS_ANIMATION_START_VELOCITY_X):
            case XML_ELEMENT(ANIMATION_OOO, XML_PHYSICS_ANIMATION_START_VELOCITY_X):
            case XML_ELEMENT(LO_EXT, XML_PHYSICS_ANIMATION_START_VELOCITY_X):
            {
                Reference< XAnimatePhysics > xAnimatePhysics( mxNode, UNO_QUERY );
                if( xAnimatePhysics.is() )
                    xAnimatePhysics->setStartVelocityX( Any(aIter.toDouble()) );
            }
            break;

            case XML_ELEMENT(ANIMATION, XML_PHYSICS_ANIMATION_START_VELOCITY_Y):
            case XML_ELEMENT(ANIMATION_OOO, XML_PHYSICS_ANIMATION_START_VELOCITY_Y):
            case XML_ELEMENT(LO_EXT, XML_PHYSICS_ANIMATION_START_VELOCITY_Y):
            {
                Reference< XAnimatePhysics > xAnimatePhysics( mxNode, UNO_QUERY );
                if( xAnimatePhysics.is() )
                    xAnimatePhysics->setStartVelocityY( Any(aIter.toDouble()) );
            }
            break;

            case XML_ELEMENT(ANIMATION, XML_PHYSICS_ANIMATION_DENSITY):
            case XML_ELEMENT(ANIMATION_OOO, XML_PHYSICS_ANIMATION_DENSITY):
            case XML_ELEMENT(LO_EXT, XML_PHYSICS_ANIMATION_DENSITY):
            {
                Reference< XAnimatePhysics > xAnimatePhysics( mxNode, UNO_QUERY );
                if( xAnimatePhysics.is() )
                    xAnimatePhysics->setDensity( Any(aIter.toDouble()) );
            }
            break;

            case XML_ELEMENT(ANIMATION, XML_PHYSICS_ANIMATION_BOUNCINESS):
            case XML_ELEMENT(ANIMATION_OOO, XML_PHYSICS_ANIMATION_BOUNCINESS):
            case XML_ELEMENT(LO_EXT, XML_PHYSICS_ANIMATION_BOUNCINESS):
            {
                Reference< XAnimatePhysics > xAnimatePhysics( mxNode, UNO_QUERY );
                if( xAnimatePhysics.is() )
                    xAnimatePhysics->setBounciness( Any(aIter.toDouble()) );
            }
            break;

            case XML_ELEMENT(ANIMATION, XML_COLOR_INTERPOLATION):
            case XML_ELEMENT(ANIMATION_OOO, XML_COLOR_INTERPOLATION):
            {
                Reference< XAnimateColor > xAnimateColor( mxNode, UNO_QUERY );
                if( xAnimateColor.is() )
                    xAnimateColor->setColorInterpolation( IsXMLToken( aIter, XML_HSL ) ? AnimationColorSpace::HSL : AnimationColorSpace::RGB );
            }
            break;

            case XML_ELEMENT(ANIMATION, XML_COLOR_INTERPOLATION_DIRECTION):
            case XML_ELEMENT(ANIMATION_OOO, XML_COLOR_INTERPOLATION_DIRECTION):
            {
                Reference< XAnimateColor > xAnimateColor( mxNode, UNO_QUERY );
                if( xAnimateColor.is() )
                    xAnimateColor->setDirection( IsXMLToken( aIter, XML_CLOCKWISE ) );
            }
            break;

            case XML_ELEMENT(SVG, XML_TYPE):
            case XML_ELEMENT(SVG_COMPAT, XML_TYPE):
            {
                Reference< XAnimateTransform > xTransform( mxNode, UNO_QUERY );
                if( xTransform.is() )
                {
                    if( SvXMLUnitConverter::convertEnum( nEnum, aIter.toView(), aAnimations_EnumMap_TransformType ) )
                    {
                        xTransform->setTransformType( nEnum );
                        switch( nEnum )
                        {
                        case AnimationTransformType::SCALE: meAttributeName = XML_SCALE; break;
                        case AnimationTransformType::ROTATE: meAttributeName = XML_ROTATE; break;
                        case AnimationTransformType::SKEWX: meAttributeName = XML_SKEWX; break;
                        case AnimationTransformType::SKEWY: meAttributeName = XML_SKEWY; break;
                        //case AnimationTransformType::TRANSLATE:
                        default:
                            meAttributeName = XML_TRANSLATE; break;
                        }
                    }
                }
            }
            break;

            case XML_ELEMENT(SMIL, XML_TYPE):
            case XML_ELEMENT(SMIL_COMPAT, XML_TYPE):
            case XML_ELEMENT(SMIL_SO52, XML_TYPE):
            {
                if( xTransitionFilter.is() )
                {
                    if( SvXMLUnitConverter::convertEnum( nEnum, aIter.toView(), aAnimations_EnumMap_TransitionType ) )
                        xTransitionFilter->setTransition( nEnum );
                }
            }
            break;

            case XML_ELEMENT(SMIL, XML_SUBTYPE):
            case XML_ELEMENT(SMIL_COMPAT, XML_SUBTYPE):
            case XML_ELEMENT(SMIL_SO52, XML_SUBTYPE):
            {
                if( xTransitionFilter.is() )
                {
                    if( SvXMLUnitConverter::convertEnum( nEnum, aIter.toView(), aAnimations_EnumMap_TransitionSubType ) )
                        xTransitionFilter->setSubtype( nEnum );
                }
            }
            break;

            case XML_ELEMENT(SMIL, XML_MODE):
            case XML_ELEMENT(SMIL_COMPAT, XML_MODE):
            case XML_ELEMENT(SMIL_SO52, XML_MODE):
            {
                if( xTransitionFilter.is() )
                    xTransitionFilter->setMode( IsXMLToken( aIter, XML_IN ) );
            }
            break;

            case XML_ELEMENT(SMIL, XML_DIRECTION):
            case XML_ELEMENT(SMIL_COMPAT, XML_DIRECTION):
            case XML_ELEMENT(SMIL_SO52, XML_DIRECTION):
            {
                if( xTransitionFilter.is() )
                    xTransitionFilter->setDirection( IsXMLToken( aIter, XML_FORWARD ) );
            }
            break;

            case XML_ELEMENT(SMIL, XML_FADECOLOR):
            case XML_ELEMENT(SMIL_COMPAT, XML_FADECOLOR):
            case XML_ELEMENT(SMIL_SO52, XML_FADECOLOR):
            {
                if( xTransitionFilter.is() )
                {
                    sal_Int32 nColor(0);
                    ::sax::Converter::convertColor(nColor, aIter.toView());
                    xTransitionFilter->setFadeColor(nColor);
                }
            }
            break;

            case XML_ELEMENT(ANIMATION, XML_ITERATE_TYPE):
            case XML_ELEMENT(ANIMATION_OOO, XML_ITERATE_TYPE):
            {
                if( SvXMLUnitConverter::convertEnum( nEnum, aIter.toView(), aAnimations_EnumMap_IterateType ) )
                {
                    if( xIter.is() )
                        xIter->setIterateType( nEnum );
                }
            }
            break;

            case XML_ELEMENT(ANIMATION, XML_ITERATE_INTERVAL):
            case XML_ELEMENT(ANIMATION_OOO, XML_ITERATE_INTERVAL):
            {
                if( xIter.is() )
                {
                    OUString rValue = aIter.toString();
                    double fInterval = 0.0;
                    if( rValue.match("P") )
                    {
                        css::util::Duration aDuration;
                        if (::sax::Converter::convertDuration(aDuration, rValue))
                        {
                            fInterval = ((((aDuration.Hours * 60)
                                + aDuration.Minutes) * 60) + aDuration.Seconds)
                              + (aDuration.NanoSeconds / 1000000000.0);
                        }
                    }
                    else
                    {
                        fInterval = aIter.toDouble();
                    }

                    xIter->setIterateInterval( fInterval );
                }
            }
            break;

            case XML_ELEMENT(PRESENTATION, XML_GROUP_ID):
            case XML_ELEMENT(PRESENTATION_SO52, XML_GROUP_ID):
            case XML_ELEMENT(PRESENTATION_OOO, XML_GROUP_ID):
            {
                aUserData.emplace_back( "group-id", Any( aIter.toInt32() ) );
            }
            break;

            case XML_ELEMENT(ANIMATION, XML_COMMAND):
            case XML_ELEMENT(ANIMATION_OOO, XML_COMMAND):
            {
                if( xCommand.is() && nNodeType == AnimationNodeType::COMMAND )
                {
                    if( SvXMLUnitConverter::convertEnum( nEnum, aIter.toView(), aAnimations_EnumMap_Command ) )
                    {
                        xCommand->setCommand( nEnum );
                    }
                }
            }
            break;

            default:
            {
                // push all unknown attributes within the presentation namespace as user data
                if (IsTokenInNamespace(nToken, XML_NAMESPACE_PRESENTATION)
                    || IsTokenInNamespace(nToken, XML_NAMESPACE_PRESENTATION_SO52)
                    || IsTokenInNamespace(nToken, XML_NAMESPACE_PRESENTATION_OASIS)
                    || IsTokenInNamespace(nToken, XML_NAMESPACE_PRESENTATION_OOO))
                {
                    aUserData.emplace_back( SvXMLImport::getNameFromToken(aIter.getToken()), Any( aIter.toString() ) );
                }
                else
                    XMLOFF_WARN_UNKNOWN("xmloff", aIter);
            }
            }
        }

        if (!sXmlId.isEmpty())
        {
            Reference< XInterface > const xRef( mxNode, UNO_QUERY );
            GetImport().getInterfaceToIdentifierMapper().registerReference(
                sXmlId, xRef );
        }

        sal_Int32 nUserDataCount = aUserData.size();
        if( nUserDataCount )
        {
            Sequence< NamedValue > aUnoUserData( nUserDataCount );
            NamedValue* pData = aUnoUserData.getArray();
            for (auto const& item : aUserData)
                *pData++ = item;

            mxNode->setUserData( aUnoUserData );
        }

        // convert values
        if( xAnimate.is() )
        {
            if( !aFrom.isEmpty() )
                xAnimate->setFrom( mpHelper->convertValue( meAttributeName, aFrom ) );

            if( !aBy.isEmpty() )
                xAnimate->setBy( mpHelper->convertValue( meAttributeName, aBy ) );

            if( !aTo.isEmpty() )
                xAnimate->setTo( mpHelper->convertValue( meAttributeName, aTo ) );

            if( !aValues.isEmpty() )
                xAnimate->setValues( mpHelper->convertValueSequence( meAttributeName, aValues ) );

            if (xAnimate->getValues().getLength() != xAnimate->getKeyTimes().getLength())
                throw css::io::WrongFormatException();
        }
    }
    catch (const css::io::WrongFormatException&)
    {
        throw;
    }
    catch (const RuntimeException&)
    {
        TOOLS_WARN_EXCEPTION("xmloff.draw", "");
    }
}

css::uno::Reference< css::xml::sax::XFastContextHandler >  AnimationNodeContext::createFastChildContext(sal_Int32 nElement,
        const css::uno::Reference<css::xml::sax::XFastAttributeList>& xAttrList)
{
    if( mxNode.is())
        return new AnimationNodeContext( mxNode, GetImport(), nElement, xAttrList, mpHelper );
    return nullptr;
}

namespace {

class AnimationsImport: public SvXMLImport, public XAnimationNodeSupplier
{
public:
    explicit AnimationsImport( const Reference< XComponentContext > & rxContext );

    SvXMLImportContext* CreateFastContext(sal_Int32 nElement,
                    const Reference<XFastAttributeList>& xAttrList) override;

    // XInterface
    virtual Any SAL_CALL queryInterface( const Type& aType ) override;
    virtual void SAL_CALL acquire() noexcept override;
    virtual void SAL_CALL release() noexcept override;

    // XAnimationNodeSupplier
    Reference< XAnimationNode > SAL_CALL getAnimationNode() override;

private:
    Reference< XAnimationNode > mxRootNode;
};

}

AnimationsImport::AnimationsImport( const Reference< XComponentContext > & rxContext )
: SvXMLImport( rxContext, u"xmloff::AnimationsImport"_ustr, SvXMLImportFlags::META )
    //FIXME: the above "IMPORT_META" used to be a nonsensical "true", question
    // remains whether this should be IMPORT_META (same numerical value as
    // true) or default IMPORT_ALL
{
    mxRootNode.set( SequenceTimeContainer::create(rxContext), UNO_QUERY_THROW );
}

// XInterface
Any SAL_CALL AnimationsImport::queryInterface( const Type& aType )
{
    if ( aType == cppu::UnoType<XAnimationNodeSupplier>::get())
    {
        return Any( Reference<XAnimationNodeSupplier>( this ) );
    }
    else
    {
        return SvXMLImport::queryInterface( aType );
    }
}

void SAL_CALL AnimationsImport::acquire() noexcept
{
    SvXMLImport::acquire();
}

void SAL_CALL AnimationsImport::release() noexcept
{
    SvXMLImport::release();
}

SvXMLImportContext *AnimationsImport::CreateFastContext(
        sal_Int32 nElement,
        const Reference<XFastAttributeList>& xAttrList)
{
    SvXMLImportContext* pContext = nullptr;

    if( nElement == XML_ELEMENT(ANIMATION, XML_SEQ) || nElement == XML_ELEMENT(ANIMATION_OOO, XML_SEQ) )
    {
         pContext = new AnimationNodeContext( mxRootNode, *this, nElement, xAttrList );
    }

    return pContext;
}

// XAnimationNodeSupplier
Reference< XAnimationNode > SAL_CALL AnimationsImport::getAnimationNode()
{
    return mxRootNode;
}

void AnimationNodeContext::postProcessRootNode( const Reference< XAnimationNode >& xRootNode, Reference< XPropertySet > const & xPageProps )
{
    if( !(xRootNode.is() && xPageProps.is()) )
        return;

    try
    {
        Reference< XEnumerationAccess > xEnumerationAccess( xRootNode, UNO_QUERY_THROW );
        Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration(), UNO_SET_THROW );
        if( xEnumeration->hasMoreElements() )
        {
            Reference< XAnimationNode > xNode( xEnumeration->nextElement(), UNO_QUERY_THROW );
            if( xNode->getType() == AnimationNodeType::PAR )
            {
                Event aEvent;
                if( (xNode->getBegin() >>= aEvent) && (aEvent.Trigger == EventTrigger::BEGIN_EVENT) )
                {
                    // found transition node
                    Reference< XEnumerationAccess > xChildEnumerationAccess( xNode, UNO_QUERY_THROW );
                    Reference< XEnumeration > xChildEnumeration( xChildEnumerationAccess->createEnumeration(), UNO_SET_THROW );
                    while( xChildEnumeration->hasMoreElements() )
                    {
                        Reference< XAnimationNode > xChildNode( xChildEnumeration->nextElement(), UNO_QUERY_THROW );
                        switch( xChildNode->getType() )
                        {
                        case AnimationNodeType::TRANSITIONFILTER:
                        {
                            Reference< XTransitionFilter > xTransFilter( xChildNode, UNO_QUERY_THROW );

                            xPageProps->setPropertyValue(u"TransitionType"_ustr, Any( xTransFilter->getTransition() ) );
                            xPageProps->setPropertyValue(u"TransitionSubtype"_ustr, Any( xTransFilter->getSubtype() ) );
                            xPageProps->setPropertyValue(u"TransitionDirection"_ustr, Any( xTransFilter->getDirection() ) );
                            xPageProps->setPropertyValue(u"TransitionFadeColor"_ustr, Any( xTransFilter->getFadeColor() ) );

                            double fDuration;
                            if( xTransFilter->getDuration() >>= fDuration )
                                xPageProps->setPropertyValue(u"TransitionDuration"_ustr, Any( fDuration ) );

                        }
                        break;

                        case AnimationNodeType::COMMAND:
                        {
                            Reference< XCommand > xCommand( xChildNode, UNO_QUERY_THROW );
                            if( xCommand->getCommand() == EffectCommands::STOPAUDIO )
                            {
                                xPageProps->setPropertyValue(u"Sound"_ustr, Any(true) );
                            }
                        }
                        break;

                        case AnimationNodeType::AUDIO:
                        {
                            Reference< XAudio > xAudio( xChildNode, UNO_QUERY_THROW );
                            OUString sSoundURL;
                            if( (xAudio->getSource() >>= sSoundURL) && !sSoundURL.isEmpty() )
                            {
                                xPageProps->setPropertyValue(u"Sound"_ustr, Any(sSoundURL) );

                                Timing eTiming;
                                if( (xAudio->getRepeatCount() >>= eTiming) && (eTiming == Timing_INDEFINITE) )
                                    xPageProps->setPropertyValue(u"LoopSound"_ustr, Any( true ) );
                            }
                        }
                        break;

                        }
                    }

                    Reference< XTimeContainer > xRootContainer( xRootNode, UNO_QUERY_THROW );
                    xRootContainer->removeChild( xNode );
                }
            }
        }
    }
    catch (const Exception&)
    {
        TOOLS_WARN_EXCEPTION("xmloff.draw", "");
    }
}

} // namespace xmloff

extern "C" SAL_DLLPUBLIC_EXPORT uno::XInterface*
com_sun_star_comp_Xmloff_AnimationsImport(uno::XComponentContext* pCtx,
                                          uno::Sequence<uno::Any> const& /*rSeq*/)
{
    return cppu::acquire(new xmloff::AnimationsImport(pCtx));
}

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