/* -*- 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/XMultiServiceFactory.hpp>
#include <com/sun/star/lang/XServiceInfo.hpp>
#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/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/xml/sax/XAttributeList.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 <sax/tools/converter.hxx>

#include <vector>

#include <o3tl/make_unique.hxx>

#include <xmloff/xmltypes.hxx>
#include "sdpropls.hxx"
#include <xmloff/xmltoken.hxx>
#include <xmloff/xmlimp.hxx>
#include <xmloff/xmlnmspe.hxx>
#include <xmloff/xmluconv.hxx>
#include <osl/diagnose.h>
#include <xmloff/nmspmap.hxx>
#include <xmloff/xmlprhdl.hxx>
#include <anim.hxx>
#include <facreg.hxx>

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

using namespace ::std;
using namespace ::cppu;
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::XAttributeList;
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::XMultiServiceFactory;
using ::com::sun::star::lang::XInitialization;

Sequence< OUString > AnimationsImport_getSupportedServiceNames() throw()
{
    return Sequence< OUString > { "com.sun.star.comp.Xmloff.AnimationsImport" };
}

OUString AnimationsImport_getImplementationName() throw()
{
    return OUString( "xmloff::AnimationsImport" );
}

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;

    std::unique_ptr<SvXMLTokenMap> mpAnimationNodeTokenMap;
    std::unique_ptr<SvXMLTokenMap> mpAnimationNodeAttributeTokenMap;

public:
    explicit AnimationsImportHelperImpl( SvXMLImport& rImport );

    const SvXMLTokenMap& getAnimationNodeTokenMap();
    const SvXMLTokenMap& getAnimationNodeAttributeTokenMap();

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

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

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

const SvXMLTokenMap& AnimationsImportHelperImpl::getAnimationNodeTokenMap()
{
    if( mpAnimationNodeTokenMap == nullptr )
    {
        static const SvXMLTokenMapEntry aAnimationNodeTokenMap[] =
        {
            { XML_NAMESPACE_ANIMATION,  XML_PAR,                sal_uInt16(AnimationNodeType::PAR) },
            { XML_NAMESPACE_ANIMATION,  XML_SEQ,                sal_uInt16(AnimationNodeType::SEQ) },
            { XML_NAMESPACE_ANIMATION,  XML_ITERATE,            sal_uInt16(AnimationNodeType::ITERATE) },
            { XML_NAMESPACE_ANIMATION,  XML_ANIMATE,            sal_uInt16(AnimationNodeType::ANIMATE) },
            { XML_NAMESPACE_ANIMATION,  XML_SET,                sal_uInt16(AnimationNodeType::SET) },
            { XML_NAMESPACE_ANIMATION,  XML_ANIMATEMOTION,      sal_uInt16(AnimationNodeType::ANIMATEMOTION) },
            { XML_NAMESPACE_ANIMATION,  XML_ANIMATECOLOR,       sal_uInt16(AnimationNodeType::ANIMATECOLOR) },
            { XML_NAMESPACE_ANIMATION,  XML_ANIMATETRANSFORM,   sal_uInt16(AnimationNodeType::ANIMATETRANSFORM) },
            { XML_NAMESPACE_ANIMATION,  XML_TRANSITIONFILTER,   sal_uInt16(AnimationNodeType::TRANSITIONFILTER) },
            { XML_NAMESPACE_ANIMATION,  XML_AUDIO,              sal_uInt16(AnimationNodeType::AUDIO) },
            { XML_NAMESPACE_ANIMATION,  XML_COMMAND,            sal_uInt16(AnimationNodeType::COMMAND) },
            XML_TOKEN_MAP_END
        };

        mpAnimationNodeTokenMap = o3tl::make_unique<SvXMLTokenMap>( aAnimationNodeTokenMap );
    }

    return *mpAnimationNodeTokenMap;
}

enum AnimationNodeAttributes
{
    ANA_Begin,
    ANA_Dur,
    ANA_End,
    ANA_Fill,
    ANA_FillDefault,
    ANA_Restart,
    ANA_RestartDefault,
    ANA_Accelerate,
    ANA_Decelerate,
    ANA_AutoReverse,
    ANA_RepeatCount,
    ANA_RepeatDur,
    ANA_EndSync,
    ANA_Node_Type,
    ANA_Preset_ID,
    ANA_Preset_Sub_Type,
    ANA_Preset_Class,
    ANA_After_Effect,
    ANA_Target,
    ANA_XLink,
    ANA_MasterElement,
    ANA_SubItem,
    ANA_AttributeName,
    ANA_Values,
    ANA_From,
    ANA_By,
    ANA_To,
    ANA_KeyTimes,
    ANA_CalcMode,
    ANA_Accumulate,
    ANA_AdditiveMode,
    ANA_KeySplines,
    ANA_Path,
    ANA_ColorSpace,
    ANA_ColorDirection,
    ANA_TransformType,
    ANA_TransitionType,
    ANA_TransitionSubType,
    ANA_Mode,
    ANA_Direction,
    ANA_FadeColor,
    ANA_IterateType,
    ANA_IterateInterval,
    ANA_Formula,
    ANA_ANIMID,
    ANA_XMLID,
    ANA_Group_Id,
    ANA_Command,
    ANA_Volume
};

const SvXMLTokenMap& AnimationsImportHelperImpl::getAnimationNodeAttributeTokenMap()
{
    if( mpAnimationNodeAttributeTokenMap == nullptr )
    {
        static const SvXMLTokenMapEntry aAnimationNodeAttributeTokenMap[] =
        {
            { XML_NAMESPACE_SMIL, XML_BEGIN,                    sal_uInt16(ANA_Begin) },
            { XML_NAMESPACE_SMIL, XML_DUR,                      sal_uInt16(ANA_Dur) },
            { XML_NAMESPACE_SMIL, XML_END,                      sal_uInt16(ANA_End) },
            { XML_NAMESPACE_SMIL, XML_FILL,                     sal_uInt16(ANA_Fill) },
            { XML_NAMESPACE_SMIL, XML_FILLDEFAULT,              sal_uInt16(ANA_FillDefault) },
            { XML_NAMESPACE_SMIL, XML_RESTART,                  sal_uInt16(ANA_Restart) },
            { XML_NAMESPACE_SMIL, XML_RESTARTDEFAULT,           sal_uInt16(ANA_RestartDefault) },
            { XML_NAMESPACE_SMIL, XML_ACCELERATE,               sal_uInt16(ANA_Accelerate) },
            { XML_NAMESPACE_SMIL, XML_DECELERATE,               sal_uInt16(ANA_Decelerate) },
            { XML_NAMESPACE_SMIL, XML_AUTOREVERSE,              sal_uInt16(ANA_AutoReverse) },
            { XML_NAMESPACE_SMIL, XML_REPEATCOUNT,              sal_uInt16(ANA_RepeatCount) },
            { XML_NAMESPACE_SMIL, XML_REPEATDUR,                sal_uInt16(ANA_RepeatDur) },
            { XML_NAMESPACE_SMIL, XML_ENDSYNC,                  sal_uInt16(ANA_EndSync) },
            { XML_NAMESPACE_PRESENTATION, XML_NODE_TYPE,        sal_uInt16(ANA_Node_Type) },
            { XML_NAMESPACE_PRESENTATION, XML_PRESET_ID,        sal_uInt16(ANA_Preset_ID) },
            { XML_NAMESPACE_PRESENTATION, XML_PRESET_SUB_TYPE,  sal_uInt16(ANA_Preset_Sub_Type) },
            { XML_NAMESPACE_PRESENTATION, XML_PRESET_CLASS,     sal_uInt16(ANA_Preset_Class) },
            { XML_NAMESPACE_PRESENTATION, XML_AFTER_EFFECT,     sal_uInt16(ANA_After_Effect) },
            { XML_NAMESPACE_SMIL, XML_TARGETELEMENT,            sal_uInt16(ANA_Target) },
            { XML_NAMESPACE_XLINK, XML_HREF,                    sal_uInt16(ANA_XLink) },
            { XML_NAMESPACE_PRESENTATION, XML_MASTER_ELEMENT,   sal_uInt16(ANA_MasterElement) },
            { XML_NAMESPACE_ANIMATION, XML_SUB_ITEM,            sal_uInt16(ANA_SubItem) },
            { XML_NAMESPACE_SMIL, XML_ATTRIBUTENAME,            sal_uInt16(ANA_AttributeName) },
            { XML_NAMESPACE_SMIL, XML_VALUES,                   sal_uInt16(ANA_Values) },
            { XML_NAMESPACE_SMIL, XML_FROM,                     sal_uInt16(ANA_From) },
            { XML_NAMESPACE_SMIL, XML_BY,                       sal_uInt16(ANA_By) },
            { XML_NAMESPACE_SMIL, XML_TO,                       sal_uInt16(ANA_To) },
            { XML_NAMESPACE_SMIL, XML_KEYTIMES,                 sal_uInt16(ANA_KeyTimes) },
            { XML_NAMESPACE_SMIL, XML_CALCMODE,                 sal_uInt16(ANA_CalcMode) },
            { XML_NAMESPACE_SMIL, XML_ACCUMULATE,               sal_uInt16(ANA_Accumulate) },
            { XML_NAMESPACE_PRESENTATION, XML_ADDITIVE,         sal_uInt16(ANA_AdditiveMode) },
            { XML_NAMESPACE_SMIL, XML_ADDITIVE,                 sal_uInt16(ANA_AdditiveMode) },
            { XML_NAMESPACE_SMIL, XML_KEYSPLINES,               sal_uInt16(ANA_KeySplines) },
            { XML_NAMESPACE_SVG, XML_PATH,                      sal_uInt16(ANA_Path) },
            { XML_NAMESPACE_ANIMATION, XML_COLOR_INTERPOLATION, sal_uInt16(ANA_ColorSpace) },
            { XML_NAMESPACE_ANIMATION, XML_COLOR_INTERPOLATION_DIRECTION,       sal_uInt16(ANA_ColorDirection) },
            { XML_NAMESPACE_SVG, XML_TYPE,                      sal_uInt16(ANA_TransformType) },
            { XML_NAMESPACE_SMIL, XML_TYPE,                     sal_uInt16(ANA_TransitionType) },
            { XML_NAMESPACE_SMIL, XML_SUBTYPE,                  sal_uInt16(ANA_TransitionSubType) },
            { XML_NAMESPACE_SMIL, XML_MODE,                     sal_uInt16(ANA_Mode) },
            { XML_NAMESPACE_SMIL, XML_DIRECTION,                sal_uInt16(ANA_Direction) },
            { XML_NAMESPACE_SMIL, XML_FADECOLOR,                sal_uInt16(ANA_FadeColor) },
            { XML_NAMESPACE_ANIMATION, XML_ITERATE_TYPE,        sal_uInt16(ANA_IterateType) },
            { XML_NAMESPACE_ANIMATION, XML_ITERATE_INTERVAL,    sal_uInt16(ANA_IterateInterval) },
            { XML_NAMESPACE_ANIMATION, XML_FORMULA,             sal_uInt16(ANA_Formula) },
            { XML_NAMESPACE_ANIMATION, XML_ID,                  sal_uInt16(ANA_ANIMID) },
            { XML_NAMESPACE_XML, XML_ID,                        sal_uInt16(ANA_XMLID) },
            { XML_NAMESPACE_PRESENTATION, XML_GROUP_ID,         sal_uInt16(ANA_Group_Id) },
            { XML_NAMESPACE_ANIMATION, XML_AUDIO_LEVEL,         sal_uInt16(ANA_Volume) },
            { XML_NAMESPACE_ANIMATION, XML_COMMAND,             sal_uInt16(ANA_Command) },

            XML_TOKEN_MAP_END
        };

        mpAnimationNodeAttributeTokenMap = o3tl::make_unique<SvXMLTokenMap>( aAnimationNodeAttributeTokenMap );
    }

    return *mpAnimationNodeAttributeTokenMap;
}

static bool isDouble( const OUString& rValue )
{
    sal_Int32 nLength = rValue.getLength();
    const sal_Unicode * pStr = rValue.getStr();
    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));
}

static sal_Int32 count_codes( const OUString& rString, sal_Unicode nCode )
{
    sal_Int32 nCount = 0;
    sal_Int32 fromIndex = 0;

    while(true)
    {
        fromIndex = rString.indexOf( nCode, fromIndex );
        if( fromIndex == -1 )
            break;

        fromIndex++;
        nCount++;
    }

    return nCount;
}

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

        Reference< XShape > _xShape( xRef, UNO_QUERY );
        if( _xShape.is() )
            return makeAny( _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_QUERY_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 makeAny( ParagraphTarget( xShape, nParagraph ) );
                }

                nParagraph++;
            }
        }
    }
    catch (const RuntimeException&)
    {
        OSL_FAIL( "xmloff::AnimationsImportImpl::convertTarget(), RuntimeException caught!" );
    }

    Any aAny;
    return aAny;
}

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

    if( nCommaPos >= 0 )
    {
        ValuePair aPair;
        aPair.First = convertValue( eAttributeName, rValue.copy( 0, nCommaPos ) );
        aPair.Second = convertValue( eAttributeName, rValue.copy( nCommaPos+1 ) );
        return makeAny( 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 makeAny( 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, const OUString& rValue )
{
    Sequence< Any > aValues;

    // do we have any value at all?
    if( !rValue.isEmpty() )
    {
        sal_Int32 nElements = count_codes( rValue, ';') + 1; // a non empty string has at least one value

        // prepare the sequence
        aValues.realloc( nElements );

        // fill the sequence
        Any* pValues = aValues.getArray();
        sal_Int32 nIndex;
        for( nIndex = 0; nElements && (nIndex >= 0); nElements-- )
        {
            *pValues++ = convertValue( eAttributeName, rValue.getToken( 0, ';', nIndex ) );
        }
    }

    return aValues;
}

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

    // do we have any value at all?
    if( !rValue.isEmpty() )
    {
        // count the values
        sal_Int32 nElements = count_codes( rValue, ';' ) + 1; // a non empty string has at least one value

        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();
            sal_Int32 nIndex = 0;
            while( (nElements--) && (nIndex >= 0) )
                *pValues++ = convertTiming( rValue.getToken( 0, ';', nIndex ) );

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

Sequence< double > AnimationsImportHelperImpl::convertKeyTimes( const OUString& rValue )
{
    sal_Int32 nElements = 0;

    if( !rValue.isEmpty() )
        nElements = count_codes( rValue, ';' ) + 1; // a non empty string has at least one value

    Sequence< double > aKeyTimes( nElements );

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

    return aKeyTimes;
}

Sequence< TimeFilterPair > AnimationsImportHelperImpl::convertTimeFilter( const OUString& rValue )
{
    sal_Int32 nElements = 0;

    if( !rValue.isEmpty() )
        nElements = count_codes( rValue, ';' ) + 1; // a non empty string has at least one value

    Sequence< TimeFilterPair > aTimeFilter( nElements );

    if( nElements )
    {
        TimeFilterPair* pValues = aTimeFilter.getArray();
        sal_Int32 nIndex = 0;
        while( (nElements--) && (nIndex >= 0) )
        {
            const OUString aToken( rValue.getToken( 0, ';', nIndex ) );

            sal_Int32 nPos = aToken.indexOf( ',' );
            if( nPos >= 0 )
            {
                pValues->Time = aToken.copy( 0, nPos ).toDouble();
                pValues->Progress = aToken.copy( nPos+1 ).toDouble();
            }
            pValues++;
        }
    }

    return aTimeFilter;
}

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


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

            const sal_Char* pServiceName = nullptr;

            sal_Int16 nNodeType = static_cast<sal_Int16>(mpHelper->getAnimationNodeTokenMap().Get( nPrfx, rLocalName ));
            switch( nNodeType )
            {
            case AnimationNodeType::SEQ:                pServiceName = "com.sun.star.animations.SequenceTimeContainer"; break;
            case AnimationNodeType::ITERATE:            pServiceName = "com.sun.star.animations.IterateContainer"; break;
            case AnimationNodeType::ANIMATE:            pServiceName = "com.sun.star.animations.Animate"; break;
            case AnimationNodeType::SET:                pServiceName = "com.sun.star.animations.AnimateSet"; break;
            case AnimationNodeType::ANIMATEMOTION:      pServiceName = "com.sun.star.animations.AnimateMotion"; break;
            case AnimationNodeType::ANIMATECOLOR:       pServiceName = "com.sun.star.animations.AnimateColor"; break;
            case AnimationNodeType::ANIMATETRANSFORM:   pServiceName = "com.sun.star.animations.AnimateTransform"; break;
            case AnimationNodeType::TRANSITIONFILTER:   pServiceName = "com.sun.star.animations.TransitionFilter"; break;
            case AnimationNodeType::AUDIO:              pServiceName = "com.sun.star.animations.Audio"; break;
            case AnimationNodeType::COMMAND:            pServiceName = "com.sun.star.animations.Command"; break;
            case AnimationNodeType::PAR:
                {
                    const sal_Int16 nCount = xAttrList.is() ? xAttrList->getLength() : 0;
                    sal_Int16 nAttribute;
                    for( nAttribute = 0; nAttribute < nCount; nAttribute++ )
                    {
                        OUString aLocalName;
                        sal_uInt16 nPrefix = GetImport().GetNamespaceMap().GetKeyByAttrName( xAttrList->getNameByIndex( nAttribute ), &aLocalName );
                        if( (nPrefix == XML_NAMESPACE_PRESENTATION) && IsXMLToken( aLocalName, XML_PRESET_ID ) )
                        {
                            const OUString& rValue = xAttrList->getValueByIndex( nAttribute );
                            if ( rValue == "ooo-entrance-random" )
                            {
                                nPresetClass = EffectPresetClass::ENTRANCE;
                            }
                            else if ( rValue == "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:
                pServiceName = nullptr;
            }

            if( pServiceName )
            {
                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( makeAny( 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&)
    {
        OSL_FAIL( "xmloff::AnimationsImportImpl::AnimationsImportImpl(), RuntimeException caught!" );
    }
}

void AnimationNodeContext::StartElement( const css::uno::Reference< css::xml::sax::XAttributeList >& )
{
    // code of StartElement is moved to init_node that is now called
    // in c'tor before appending this node to its parent.
    // This is needed for random nodes that need the correct target
    // set when child nodes are appended.
}

void AnimationNodeContext::init_node(  const css::uno::Reference< css::xml::sax::XAttributeList >& xAttrList )
{
    if( mxNode.is() ) 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;

        const sal_Int16 nCount = xAttrList.is() ? xAttrList->getLength() : 0;
        sal_Int16 nEnum;
        sal_Int16 nAttribute;
        for( nAttribute = 0; nAttribute < nCount; nAttribute++ )
        {
            const OUString& rAttrName = xAttrList->getNameByIndex( nAttribute );
            const OUString& rValue = xAttrList->getValueByIndex( nAttribute );

            OUString aLocalName;
            sal_uInt16 nPrefix = GetImport().GetNamespaceMap().GetKeyByAttrName( rAttrName, &aLocalName );
            switch( mpHelper->getAnimationNodeAttributeTokenMap().Get( nPrefix, aLocalName ) )
            {
            case ANA_Begin:
            {
                mxNode->setBegin( mpHelper->convertTiming( rValue ) );
            }
            break;
            case ANA_Dur:
            {
                mxNode->setDuration( mpHelper->convertTiming( rValue ) );
            }
            break;
            case ANA_End:
            {
                mxNode->setEnd( mpHelper->convertTiming( rValue ) );
            }
            break;
            case ANA_Fill:
            {
                if( SvXMLUnitConverter::convertEnum( nEnum, rValue, aAnimations_EnumMap_Fill ) )
                    mxNode->setFill( nEnum );
            }
            break;
            case ANA_FillDefault:
            {
                if( SvXMLUnitConverter::convertEnum( nEnum, rValue, aAnimations_EnumMap_FillDefault ) )
                    mxNode->setFillDefault( nEnum );
            }
            break;
            case ANA_Restart:
            {
                if( SvXMLUnitConverter::convertEnum( nEnum, rValue, aAnimations_EnumMap_Restart ) )
                    mxNode->setRestart( nEnum );
            }
            break;
            case ANA_RestartDefault:
            {
                if( SvXMLUnitConverter::convertEnum( nEnum, rValue, aAnimations_EnumMap_RestartDefault ) )
                    mxNode->setRestartDefault( nEnum );
            }
            break;
            case ANA_Accelerate:
            {
                if( isDouble( rValue ) )
                    mxNode->setAcceleration( rValue.toDouble() );
            }
            break;
            case ANA_Decelerate:
            {
                if( isDouble( rValue ) )
                    mxNode->setDecelerate( rValue.toDouble() );
            }
            break;
            case ANA_AutoReverse:
            {
                bool bTemp;
                if (::sax::Converter::convertBool( bTemp, rValue ))
                    mxNode->setAutoReverse( bTemp  );
            }
            break;
            case ANA_RepeatCount:
            {
                mxNode->setRepeatCount( mpHelper->convertTiming( rValue ) );
            }
            break;
            case ANA_RepeatDur:
            {
                mxNode->setRepeatDuration( mpHelper->convertTiming( rValue ) );
            }
            break;
            case ANA_EndSync:
            {
                if( SvXMLUnitConverter::convertEnum( nEnum, rValue, aAnimations_EnumMap_Endsync ) )
                    mxNode->setEndSync( makeAny( nEnum ) );
            }
            break;
            case ANA_Node_Type:
            {
                if( SvXMLUnitConverter::convertEnum( nEnum, rValue, aAnimations_EnumMap_EffectNodeType ) )
                    aUserData.emplace_back( GetXMLToken( XML_NODE_TYPE ), makeAny( nEnum ) );
            }
            break;
            case ANA_Preset_ID:
            {
                aUserData.emplace_back( GetXMLToken( XML_PRESET_ID ), makeAny( rValue ) );
            }
            break;
            case ANA_Preset_Sub_Type:
            {
                aUserData.emplace_back( GetXMLToken( XML_PRESET_SUB_TYPE ), makeAny( rValue ) );
            }
            break;
            case ANA_Preset_Class:
            {
                if( SvXMLUnitConverter::convertEnum( nEnum, rValue, aAnimations_EnumMap_EffectPresetClass ) )
                    aUserData.emplace_back( GetXMLToken( XML_PRESET_CLASS ), makeAny( nEnum ) );
            }
            break;
            case ANA_After_Effect:
            {
                bool bTemp;
                if (::sax::Converter::convertBool( bTemp, rValue ))
                    aUserData.emplace_back( GetXMLToken( XML_AFTER_EFFECT ), makeAny( bTemp ) );
            }
            break;
            case ANA_XLink:
            {
                if( nNodeType == AnimationNodeType::AUDIO )
                {
                    Reference< XAudio > xAudio( mxNode, UNO_QUERY_THROW );
                    xAudio->setSource( makeAny(lcl_GetMediaReference(GetImport(), rValue)) );
                    break;
                }
                SAL_FALLTHROUGH;
            }
            case ANA_Target:
            {
                Any aTarget( mpHelper->convertTarget( rValue ) );

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

            case ANA_Volume:
            {
                if( nNodeType == AnimationNodeType::AUDIO )
                {
                    if( isDouble( rValue ) )
                    {
                        Reference< XAudio > xAudio( mxNode, UNO_QUERY_THROW );
                        xAudio->setVolume( rValue.toDouble() );
                    }
                }
            }
            break;

            case ANA_MasterElement:
            {
                Reference< XAnimationNode > xMaster( GetImport().getInterfaceToIdentifierMapper().getReference( rValue ), UNO_QUERY );
                aUserData.emplace_back( GetXMLToken( XML_MASTER_ELEMENT ), makeAny( xMaster ) );
            }
            break;

            case ANA_SubItem:
            {
                if( SvXMLUnitConverter::convertEnum( nEnum, rValue, aAnimations_EnumMap_SubItem ) )
                {
                    if( xAnimate.is() )
                    {
                        xAnimate->setSubItem( nEnum );
                    }
                    else if( xIter.is() )
                    {
                        xIter->setSubItem( nEnum );
                    }
                }
            }
            break;

            case ANA_AttributeName:
            {
                if( xAnimate.is() )
                {
                    OUString aName( rValue );

                    const struct ImplAttributeNameConversion* p = getAnimationAttributeNamesConversionList();
                    while( p->mpAPIName )
                    {
                        if( IsXMLToken( aName, p->meXMLToken ) )
                        {
                            aName = OUString::createFromAscii( p->mpAPIName );
                            meAttributeName = p->meXMLToken;
                            break;
                        }

                        p++;
                    }

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

            case ANA_Values:
            {
                aValues = rValue;
            }
            break;

            case ANA_From:
            {
                aFrom = rValue;
            }
            break;

            case ANA_By:
            {
                aBy = rValue;
            }
            break;

            case ANA_To:
            {
                aTo = rValue;
            }
            break;

            case ANA_KeyTimes:
            {
                if( xAnimate.is() )
                    xAnimate->setKeyTimes( AnimationsImportHelperImpl::convertKeyTimes( rValue ) );
            }
            break;

            case ANA_Formula:
            {
                if( xAnimate.is() )
                    xAnimate->setFormula( rValue );
            }
            break;

            case ANA_ANIMID:
            {
                if (!bHaveXmlId) { sXmlId = rValue; }
            }
            break;
            case ANA_XMLID:
            {
                sXmlId = rValue;
                bHaveXmlId = true;
            }
            break;

            case ANA_CalcMode:
            {
                if( xAnimate.is() )
                {
                    if( SvXMLUnitConverter::convertEnum( nEnum, rValue, aAnimations_EnumMap_CalcMode ) )
                        xAnimate->setCalcMode( nEnum );
                }
            }
            break;

            case ANA_Accumulate:
            {
                if( xAnimate.is() )
                    xAnimate->setAccumulate( IsXMLToken( rValue, XML_SUM ) );
            }
            break;

            case ANA_AdditiveMode:
            {
                if( xAnimate.is() )
                {
                    if( SvXMLUnitConverter::convertEnum( nEnum, rValue, aAnimations_EnumMap_AdditiveMode ) )
                        xAnimate->setAdditive( nEnum );
                }
            }
            break;

            case ANA_KeySplines:
            {
                if( xAnimate.is() )
                    xAnimate->setTimeFilter( AnimationsImportHelperImpl::convertTimeFilter( rValue ) );
            }
            break;

            case ANA_Path:
            {
                Reference< XAnimateMotion > xAnimateMotion( mxNode, UNO_QUERY );
                if( xAnimateMotion.is() )
                    xAnimateMotion->setPath( AnimationsImportHelperImpl::convertPath( rValue ) );
            }
            break;

            case ANA_ColorSpace:
            {
                Reference< XAnimateColor > xAnimateColor( mxNode, UNO_QUERY );
                if( xAnimateColor.is() )
                    xAnimateColor->setColorInterpolation( IsXMLToken( rValue, XML_HSL ) ? AnimationColorSpace::HSL : AnimationColorSpace::RGB );
            }
            break;

            case ANA_ColorDirection:
            {
                Reference< XAnimateColor > xAnimateColor( mxNode, UNO_QUERY );
                if( xAnimateColor.is() )
                    xAnimateColor->setDirection( IsXMLToken( rValue, XML_CLOCKWISE ) );
            }
            break;

            case ANA_TransformType:
            {
                Reference< XAnimateTransform > xTransform( mxNode, UNO_QUERY );
                if( xTransform.is() )
                {
                    if( SvXMLUnitConverter::convertEnum( nEnum, rValue, 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 ANA_TransitionType:
            {
                if( xTransitionFilter.is() )
                {
                    if( SvXMLUnitConverter::convertEnum( nEnum, rValue, aAnimations_EnumMap_TransitionType ) )
                        xTransitionFilter->setTransition( nEnum );
                }
            }
            break;

            case ANA_TransitionSubType:
            {
                if( xTransitionFilter.is() )
                {
                    if( SvXMLUnitConverter::convertEnum( nEnum, rValue, aAnimations_EnumMap_TransitionSubType ) )
                        xTransitionFilter->setSubtype( nEnum );
                }
            }
            break;

            case ANA_Mode:
            {
                if( xTransitionFilter.is() )
                    xTransitionFilter->setMode( IsXMLToken( rValue, XML_IN ) );
            }
            break;

            case ANA_Direction:
            {
                if( xTransitionFilter.is() )
                    xTransitionFilter->setDirection( IsXMLToken( rValue, XML_FORWARD ) );
            }
            break;

            case ANA_FadeColor:
            {
                if( xTransitionFilter.is() )
                {
                    sal_Int32 nColor(0);
                    ::sax::Converter::convertColor(nColor, rValue);
                    xTransitionFilter->setFadeColor(nColor);
                }
            }
            break;

            case ANA_IterateType:
            {
                if( SvXMLUnitConverter::convertEnum( nEnum, rValue, aAnimations_EnumMap_IterateType ) )
                {
                    if( xIter.is() )
                        xIter->setIterateType( nEnum );
                }
            }
            break;

            case ANA_IterateInterval:
            {
                if( xIter.is() )
                {
                    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 = rValue.toDouble();
                    }

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

            case ANA_Group_Id:
            {
                aUserData.emplace_back( aLocalName, makeAny( rValue.toInt32() ) );
            }
            break;

            case ANA_Command:
            {
                if( xCommand.is() && nNodeType == AnimationNodeType::COMMAND )
                {
                    if( SvXMLUnitConverter::convertEnum( nEnum, rValue, aAnimations_EnumMap_Command ) )
                    {
                        xCommand->setCommand( nEnum );
                    }
                }
            }
            break;

            default:
                // push all unknown attributes within the presentation namespace as user data
                if( nPrefix == XML_NAMESPACE_PRESENTATION )
                {
                    aUserData.emplace_back( aLocalName, makeAny( rValue ) );
                }
            }
        }

        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 ) );
        }
    }
    catch (const RuntimeException&)
    {
        OSL_FAIL( "xmloff::AnimationNodeContext::StartElement(), RuntimeException caught!" );
    }
}

SvXMLImportContextRef AnimationNodeContext::CreateChildContext( sal_uInt16 nPrefix, const OUString& rLocalName,
        const css::uno::Reference< css::xml::sax::XAttributeList>& xAttrList )
{
    if( mxNode.is())
        return new AnimationNodeContext( mxNode, GetImport(), nPrefix, rLocalName, xAttrList, mpHelper );
    else
        return new SvXMLImportContext( GetImport(), nPrefix, rLocalName );
}

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

    SvXMLImportContext* CreateDocumentContext(sal_uInt16 nPrefix, const OUString& rLocalName,   const Reference<XAttributeList>& xAttrList) override;

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

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

private:
    Reference< XAnimationNode > mxRootNode;
};

AnimationsImport::AnimationsImport( const Reference< XComponentContext > & rxContext )
: SvXMLImport( rxContext, AnimationsImport_getImplementationName(), 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
{
    // add namespaces
    GetNamespaceMap().Add(
        GetXMLToken(XML_NP_PRESENTATION),
        GetXMLToken(XML_N_PRESENTATION),
        XML_NAMESPACE_PRESENTATION);

    GetNamespaceMap().Add(
        GetXMLToken(XML_NP_SMIL),
        GetXMLToken(XML_N_SMIL),
        XML_NAMESPACE_SMIL);

    GetNamespaceMap().Add(
        GetXMLToken(XML_NP_ANIMATION),
        GetXMLToken(XML_N_ANIMATION),
        XML_NAMESPACE_ANIMATION);

    mxRootNode.set( SequenceTimeContainer::create(rxContext), UNO_QUERY_THROW );
}

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

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

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

SvXMLImportContext *AnimationsImport::CreateDocumentContext(
        sal_uInt16 const nPrefix, const OUString& rLocalName,
        const Reference<XAttributeList>& xAttrList)
{
    SvXMLImportContext* pContext = nullptr;

    if( (XML_NAMESPACE_ANIMATION == nPrefix) && IsXMLToken( rLocalName, XML_SEQ ) )
    {
         pContext = new AnimationNodeContext( mxRootNode, *this, nPrefix, rLocalName, xAttrList );
    }
    else
    {
        pContext = SvXMLImport::CreateDocumentContext(nPrefix, rLocalName, 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() ) try
    {
        Reference< XEnumerationAccess > xEnumerationAccess( xRootNode, UNO_QUERY_THROW );
        Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration(), UNO_QUERY_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_QUERY_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("TransitionType", Any( xTransFilter->getTransition() ) );
                            xPageProps->setPropertyValue("TransitionSubtype", Any( xTransFilter->getSubtype() ) );
                            xPageProps->setPropertyValue("TransitionDirection", Any( xTransFilter->getDirection() ) );
                            xPageProps->setPropertyValue("TransitionFadeColor", Any( xTransFilter->getFadeColor() ) );

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

                        }
                        break;

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

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

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

                        }
                    }

                    Reference< XTimeContainer > xRootContainer( xRootNode, UNO_QUERY_THROW );
                    xRootContainer->removeChild( xNode );
                }
            }
        }
    }
    catch (const Exception&)
    {
        OSL_FAIL("xmloff::AnimationsImport::postProcessRootNode(), exception caught!");
    }
}

} // namespace xmloff

Reference< XInterface > AnimationsImport_createInstance(const Reference< XMultiServiceFactory > & rSMgr)
{
    return static_cast<cppu::OWeakObject*>(new xmloff::AnimationsImport( comphelper::getComponentContext(rSMgr) ));
}

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