/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the Collabora Office project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include <memory>
#include "datatypes.hxx"
#include "resourcehelper.hxx"
#include <frm_strings.hxx>
#include <property.hxx>
#include <strings.hrc>
#include "convert.hxx"
#include <comphelper/processfactory.hxx>

#include <com/sun/star/xsd/DataTypeClass.hpp>
#include <com/sun/star/xsd/WhiteSpaceTreatment.hpp>
#include <o3tl/string_view.hxx>
#include <tools/datetime.hxx>
#include <rtl/math.hxx>
#include <sal/log.hxx>
#include <utility>


namespace xforms
{


    using ::com::sun::star::uno::Reference;
    using ::cpo::uno::Any;
    using ::com::sun::star::util::Date;
    using ::com::sun::star::util::Time;
    using ::com::sun::star::util::DateTime;
    using ::com::sun::star::lang::IllegalArgumentException;
    using ::com::sun::star::beans::XPropertyChangeListener;
    using ::com::sun::star::beans::XVetoableChangeListener;

    using ::com::sun::star::beans::PropertyAttribute::BOUND;
    using ::com::sun::star::beans::PropertyAttribute::READONLY;

    using namespace ::com::sun::star::xsd;
    using namespace ::frm;
    U_NAMESPACE_USE

    OXSDDataType::OXSDDataType( OUString _aName, sal_Int16 _nTypeClass )
        :m_bIsBasic( true )
        ,m_nTypeClass( _nTypeClass )
        ,m_sName(std::move( _aName ))
        ,m_nWST( WhiteSpaceTreatment::Preserve )
        ,m_bPatternMatcherDirty( true )
    {
    }


    OXSDDataType::~OXSDDataType()
    {
    }


    void OXSDDataType::registerProperties()
    {
        registerProperty( PROPERTY_NAME,            PROPERTY_ID_NAME,           BOUND, &m_sName,    cppu::UnoType<decltype(m_sName)>::get() );
        registerProperty( PROPERTY_XSD_WHITESPACE,  PROPERTY_ID_XSD_WHITESPACE, BOUND, &m_nWST,     cppu::UnoType<cppu::UnoUnsignedShortType>::get() );
        registerProperty( PROPERTY_XSD_PATTERN,     PROPERTY_ID_XSD_PATTERN,    BOUND, &m_sPattern, cppu::UnoType<decltype(m_sPattern)>::get() );

        registerProperty( PROPERTY_XSD_IS_BASIC,    PROPERTY_ID_XSD_IS_BASIC,   READONLY, &m_bIsBasic,      cppu::UnoType<decltype(m_bIsBasic)>::get() );
        registerProperty( PROPERTY_XSD_TYPE_CLASS,  PROPERTY_ID_XSD_TYPE_CLASS, READONLY, &m_nTypeClass,    cppu::UnoType<decltype(m_nTypeClass)>::get() );
    }


    void OXSDDataType::initializeClone( const OXSDDataType& _rCloneSource )
    {
        m_bIsBasic   = false;
        m_nTypeClass = _rCloneSource.m_nTypeClass;
        m_sPattern   = _rCloneSource.m_sPattern;
        m_nWST       = _rCloneSource.m_nWST;
    }


    rtl::Reference<OXSDDataType> OXSDDataType::clone( const OUString& _rNewName ) const
    {
        rtl::Reference<OXSDDataType> pClone = createClone( _rNewName );
        pClone->initializeClone( *this );
        return pClone;
    }


    OUString SAL_CALL OXSDDataType::getName(  )
    {
        return m_sName;
    }


    void SAL_CALL OXSDDataType::setName( const OUString& aName )
    {
        // TODO: check the name for conflicts in the repository
        setFastPropertyValue( PROPERTY_ID_NAME, Any(aName) );
        SAL_WARN_IF( m_sName != aName, "forms.misc", "OXSDDataType::setName: inconsistency!" );
    }


    OUString SAL_CALL OXSDDataType::getPattern()
    {
        return m_sPattern;
    }


    void SAL_CALL OXSDDataType::setPattern( const OUString& _pattern )
    {
        setFastPropertyValue( PROPERTY_ID_XSD_PATTERN, Any(_pattern) );
        SAL_WARN_IF( m_sPattern != _pattern, "forms.misc", "OXSDDataType::setPattern: inconsistency!" );
    }


    sal_Int16 SAL_CALL OXSDDataType::getWhiteSpaceTreatment()
    {
        return m_nWST;
    }


    void SAL_CALL OXSDDataType::setWhiteSpaceTreatment( sal_Int16 _whitespacetreatment )
    {
        setFastPropertyValue( PROPERTY_ID_XSD_WHITESPACE, Any(_whitespacetreatment) );
        SAL_WARN_IF( m_nWST != _whitespacetreatment, "forms.misc", "OXSDDataType::setWhiteSpaceTreatment: inconsistency!" );
    }


    bool SAL_CALL OXSDDataType::getIsBasic()
    {
        return m_bIsBasic;
    }


    sal_Int16 SAL_CALL OXSDDataType::getTypeClass()
    {
        return m_nTypeClass;
    }


    bool OXSDDataType::validate( const OUString& sValue )
    {
        return bool(!_validate( sValue ));
    }


  OUString OXSDDataType::explainInvalid( const OUString& sValue )
    {
        // get reason
        TranslateId pReason = _validate( sValue );

        // get resource and return localized string
        return (!pReason)
            ? OUString()
            : getResource( pReason, sValue,
                                   _explainInvalid( pReason ) );
    }

    OUString OXSDDataType::_explainInvalid(TranslateId rReason)
    {
        if ( RID_STR_XFORMS_PATTERN_DOESNT_MATCH == rReason )
        {
            OSL_ENSURE( !m_sPattern.isEmpty(), "OXSDDataType::_explainInvalid: how can this error occur without a regular expression?" );
            return m_sPattern;
        }
        return OUString();
    }

    namespace
    {
        void lcl_initializePatternMatcher( ::std::unique_ptr< RegexMatcher >& _rpMatcher, const OUString& _rPattern )
        {
            UErrorCode nMatchStatus = U_ZERO_ERROR;
            UnicodeString aIcuPattern( reinterpret_cast<const UChar *>(_rPattern.getStr()), _rPattern.getLength() );
            _rpMatcher.reset( new RegexMatcher( aIcuPattern, 0, nMatchStatus ) );
            OSL_ENSURE( U_SUCCESS( nMatchStatus ), "lcl_initializePatternMatcher: invalid pattern property!" );
                // if asserts, then something changed our pattern without going to convertFastPropertyValue/checkPropertySanity
        }

        bool lcl_matchString( RegexMatcher& _rMatcher, const OUString& _rText )
        {
            UErrorCode nMatchStatus = U_ZERO_ERROR;
            UnicodeString aInput( reinterpret_cast<const UChar *>(_rText.getStr()), _rText.getLength() );
            _rMatcher.reset( aInput );
            if ( _rMatcher.matches( nMatchStatus ) )
            {
                int32_t nStart = _rMatcher.start( nMatchStatus );
                int32_t nEnd   = _rMatcher.end  ( nMatchStatus );
                if ( ( nStart == 0 ) && ( nEnd == _rText.getLength() ) )
                    return true;
            }

            return false;
        }
    }

    TranslateId OXSDDataType::_validate( const OUString& _rValue )
    {
        // care for the regular expression
        if ( !m_sPattern.isEmpty() )
        {
            // ensure our pattern matcher is up to date
            if ( m_bPatternMatcherDirty )
            {
                lcl_initializePatternMatcher( m_pPatternMatcher, m_sPattern );
                m_bPatternMatcherDirty = false;
            }

            // let it match the string
            if (!lcl_matchString(*m_pPatternMatcher, _rValue))
                return RID_STR_XFORMS_PATTERN_DOESNT_MATCH;
        }

        return {};
    }


    bool OXSDDataType::convertFastPropertyValue( std::unique_lock<std::mutex>& /*rGuard*/, Any& _rConvertedValue, Any& _rOldValue, sal_Int32 _nHandle, const Any& _rValue )
    {
        // let the base class do the conversion
        if ( !OPropertyContainerHelper::convertFastPropertyValue( _rConvertedValue, _rOldValue, _nHandle, _rValue ) )
            return false;

        // sanity checks
        OUString sErrorMessage;
        if ( !checkPropertySanity( _nHandle, _rConvertedValue, sErrorMessage ) )
        {
            throw IllegalArgumentException(sErrorMessage, *this, 0);
        }

        return true;
    }


    void OXSDDataType::setFastPropertyValue_NoBroadcast( std::unique_lock<std::mutex>& /*rGuard*/, sal_Int32 _nHandle, const Any& _rValue )
    {
        OPropertyContainerHelper::setFastPropertyValue( _nHandle, _rValue );
        if ( _nHandle == PROPERTY_ID_XSD_PATTERN )
            m_bPatternMatcherDirty = true;
    }


    bool OXSDDataType::checkPropertySanity( sal_Int32 _nHandle, const cpo::uno::Any& _rNewValue, OUString& _rErrorMessage )
    {
        if ( _nHandle == PROPERTY_ID_XSD_PATTERN )
        {
            OUString sPattern;
            OSL_VERIFY( _rNewValue >>= sPattern );

            UnicodeString aIcuPattern( reinterpret_cast<const UChar *>(sPattern.getStr()), sPattern.getLength() );
            UErrorCode nMatchStatus = U_ZERO_ERROR;
            RegexMatcher aMatcher( aIcuPattern, 0, nMatchStatus );
            if ( U_FAILURE( nMatchStatus ) )
            {
                _rErrorMessage = u"This is no valid pattern."_ustr;
                return false;
            }
        }
        return true;
    }


    OValueLimitedType_Base::OValueLimitedType_Base( const OUString& _rName, sal_Int16 _nTypeClass )
        :OXSDDataType( _rName, _nTypeClass )
        ,m_fCachedMaxInclusive( 0 )
        ,m_fCachedMaxExclusive( 0 )
        ,m_fCachedMinInclusive( 0 )
        ,m_fCachedMinExclusive( 0 )
    {
    }


    void OValueLimitedType_Base::initializeClone( const OXSDDataType& _rCloneSource )
    {
        OXSDDataType::initializeClone( _rCloneSource );
        initializeTypedClone( static_cast< const OValueLimitedType_Base& >( _rCloneSource ) );
    }


    void OValueLimitedType_Base::initializeTypedClone( const OValueLimitedType_Base& _rCloneSource )
    {
        m_aMaxInclusive   = _rCloneSource.m_aMaxInclusive;
        m_aMaxExclusive   = _rCloneSource.m_aMaxExclusive;
        m_aMinInclusive   = _rCloneSource.m_aMinInclusive;
        m_aMinExclusive   = _rCloneSource.m_aMinExclusive;
        m_fCachedMaxInclusive   = _rCloneSource.m_fCachedMaxInclusive;
        m_fCachedMaxExclusive   = _rCloneSource.m_fCachedMaxExclusive;
        m_fCachedMinInclusive   = _rCloneSource.m_fCachedMinInclusive;
        m_fCachedMinExclusive   = _rCloneSource.m_fCachedMinExclusive;
    }


    void OValueLimitedType_Base::setFastPropertyValue_NoBroadcast(
        std::unique_lock<std::mutex>& rGuard,
        sal_Int32 _nHandle, const cpo::uno::Any& _rValue )
    {
        OXSDDataType::setFastPropertyValue_NoBroadcast( rGuard, _nHandle, _rValue );

        // if one of our limit properties has been set, translate it into a double
        // value, for later efficient validation
        switch ( _nHandle )
        {
        case PROPERTY_ID_XSD_MAX_INCLUSIVE_INT:
        case PROPERTY_ID_XSD_MAX_INCLUSIVE_DOUBLE:
        case PROPERTY_ID_XSD_MAX_INCLUSIVE_DATE:
        case PROPERTY_ID_XSD_MAX_INCLUSIVE_TIME:
        case PROPERTY_ID_XSD_MAX_INCLUSIVE_DATE_TIME:
            if ( m_aMaxInclusive.hasValue() )
                normalizeValue( m_aMaxInclusive, m_fCachedMaxInclusive );
            else
                m_fCachedMaxInclusive = 0;
            break;
        case PROPERTY_ID_XSD_MAX_EXCLUSIVE_INT:
        case PROPERTY_ID_XSD_MAX_EXCLUSIVE_DOUBLE:
        case PROPERTY_ID_XSD_MAX_EXCLUSIVE_DATE:
        case PROPERTY_ID_XSD_MAX_EXCLUSIVE_TIME:
        case PROPERTY_ID_XSD_MAX_EXCLUSIVE_DATE_TIME:
            if ( m_aMaxExclusive.hasValue() )
                normalizeValue( m_aMaxExclusive, m_fCachedMaxExclusive );
            else
                m_fCachedMaxExclusive = 0;
            break;
        case PROPERTY_ID_XSD_MIN_INCLUSIVE_INT:
        case PROPERTY_ID_XSD_MIN_INCLUSIVE_DOUBLE:
        case PROPERTY_ID_XSD_MIN_INCLUSIVE_DATE:
        case PROPERTY_ID_XSD_MIN_INCLUSIVE_TIME:
        case PROPERTY_ID_XSD_MIN_INCLUSIVE_DATE_TIME:
            if ( m_aMinInclusive.hasValue() )
                normalizeValue( m_aMinInclusive, m_fCachedMinInclusive );
            else
                m_fCachedMinInclusive = 0;
            break;
        case PROPERTY_ID_XSD_MIN_EXCLUSIVE_INT:
        case PROPERTY_ID_XSD_MIN_EXCLUSIVE_DOUBLE:
        case PROPERTY_ID_XSD_MIN_EXCLUSIVE_DATE:
        case PROPERTY_ID_XSD_MIN_EXCLUSIVE_TIME:
        case PROPERTY_ID_XSD_MIN_EXCLUSIVE_DATE_TIME:
            if ( m_aMinExclusive.hasValue() )
                normalizeValue( m_aMinExclusive, m_fCachedMinExclusive );
            else
                m_fCachedMinExclusive = 0;
            break;
        }
    }


    bool OValueLimitedType_Base::_getValue( const OUString& rValue, double& fValue )
    {
        // convert to double
        rtl_math_ConversionStatus eStatus;
        sal_Int32 nEnd;
        double f = ::rtl::math::stringToDouble(
            rValue.replace(',','.'), '.', u'\0', &eStatus, &nEnd );

        // error checking...
        bool bReturn = false;
        if( eStatus == rtl_math_ConversionStatus_Ok
            && nEnd == rValue.getLength() )
        {
            bReturn = true;
            fValue = f;
        }
        return bReturn;
    }

    TranslateId OValueLimitedType_Base::_validate( const OUString& rValue )
    {
        TranslateId pReason = OXSDDataType::_validate( rValue );
        if (!pReason)
        {

            // convert value and check format
            double f;
            if( ! _getValue( rValue, f ) )
                pReason = RID_STR_XFORMS_VALUE_IS_NOT_A;

            // check range
            else if( ( m_aMaxInclusive.hasValue() ) && f > m_fCachedMaxInclusive )
                pReason = RID_STR_XFORMS_VALUE_MAX_INCL;
            else if( ( m_aMaxExclusive.hasValue() ) && f >= m_fCachedMaxExclusive )
                pReason = RID_STR_XFORMS_VALUE_MAX_EXCL;
            else if( ( m_aMinInclusive.hasValue() ) && f < m_fCachedMinInclusive )
                pReason = RID_STR_XFORMS_VALUE_MIN_INCL;
            else if( ( m_aMinExclusive.hasValue() ) && f <= m_fCachedMinExclusive )
                pReason = RID_STR_XFORMS_VALUE_MIN_EXCL;
        }
        return pReason;
    }

    OUString OValueLimitedType_Base::_explainInvalid(TranslateId rReason)
    {
        OUStringBuffer sInfo;
        if (rReason == RID_STR_XFORMS_VALUE_IS_NOT_A)
            sInfo.append( getName() );
        else if (rReason == RID_STR_XFORMS_VALUE_MAX_INCL)
            sInfo.append( typedValueAsHumanReadableString( m_aMaxInclusive ) );
        else if (rReason == RID_STR_XFORMS_VALUE_MAX_EXCL)
            sInfo.append( typedValueAsHumanReadableString( m_aMaxExclusive ) );
        else if (rReason == RID_STR_XFORMS_VALUE_MIN_INCL)
            sInfo.append( typedValueAsHumanReadableString( m_aMinInclusive ) );
        else if (rReason == RID_STR_XFORMS_VALUE_MIN_EXCL)
            sInfo.append( typedValueAsHumanReadableString( m_aMinExclusive ) );
        return sInfo.makeStringAndClear();
    }

    OStringType::OStringType( const OUString& _rName, sal_Int16 _nTypeClass )
        :OStringType_Base( _rName, _nTypeClass )
    {
    }


    void OStringType::registerProperties()
    {
        OStringType_Base::registerProperties();

        registerMayBeVoidProperty( PROPERTY_XSD_LENGTH, PROPERTY_ID_XSD_LENGTH, css::beans::PropertyAttribute::BOUND | css::beans::PropertyAttribute::MAYBEVOID,
            &m_aLength, cppu::UnoType<sal_Int32>::get() );

        registerMayBeVoidProperty( PROPERTY_XSD_MIN_LENGTH, PROPERTY_ID_XSD_MIN_LENGTH, css::beans::PropertyAttribute::BOUND | css::beans::PropertyAttribute::MAYBEVOID,
            &m_aMinLength, cppu::UnoType<sal_Int32>::get() );

        registerMayBeVoidProperty( PROPERTY_XSD_MAX_LENGTH, PROPERTY_ID_XSD_MAX_LENGTH, css::beans::PropertyAttribute::BOUND | css::beans::PropertyAttribute::MAYBEVOID,
            &m_aMaxLength, cppu::UnoType<sal_Int32>::get() );
    }


    rtl::Reference<OXSDDataType> OStringType::createClone( const OUString& _rName ) const
    {
        return new OStringType( _rName, getTypeClass() );
    }
    void OStringType::initializeClone( const OXSDDataType& _rCloneSource )
    {
        OStringType_Base::initializeClone( _rCloneSource );
        initializeTypedClone( static_cast< const OStringType& >( _rCloneSource ) );
    }



    void OStringType::initializeTypedClone( const OStringType& _rCloneSource )
    {
        m_aLength       = _rCloneSource.m_aLength;
        m_aMinLength    = _rCloneSource.m_aMinLength;
        m_aMaxLength    = _rCloneSource.m_aMaxLength;
    }


    bool OStringType::checkPropertySanity( sal_Int32 _nHandle, const Any& _rNewValue, OUString& _rErrorMessage )
    {
        // let the base class do the conversion
        if ( !OStringType_Base::checkPropertySanity( _nHandle, _rNewValue, _rErrorMessage ) )
            return false;

        _rErrorMessage.clear();
        switch ( _nHandle )
        {
            case PROPERTY_ID_XSD_LENGTH:
            case PROPERTY_ID_XSD_MIN_LENGTH:
            case PROPERTY_ID_XSD_MAX_LENGTH:
            {
                sal_Int32 nValue( 0 );
                OSL_VERIFY( _rNewValue >>= nValue );
                if ( nValue < 0 )
                    _rErrorMessage = u"Length limits must denote positive integer values."_ustr;
                        // TODO/eforms: localize the error message
            }
            break;
        }

        return _rErrorMessage.isEmpty();
    }


    TranslateId OStringType::_validate( const OUString& rValue )
    {
        // check regexp, whitespace etc. in parent class
        TranslateId pReason = OStringType_Base::_validate( rValue );

        if (!pReason)
        {
            // check string constraints
            sal_Int32 nLength = rValue.getLength();
            sal_Int32 nLimit = 0;
            if ( m_aLength >>= nLimit )
            {
                if ( nLimit != nLength )
                    pReason = RID_STR_XFORMS_VALUE_LENGTH;
            }
            else
            {
                if ( ( m_aMaxLength >>= nLimit ) && ( nLength > nLimit ) )
                    pReason = RID_STR_XFORMS_VALUE_MAX_LENGTH;
                else if ( ( m_aMinLength >>= nLimit ) && ( nLength < nLimit ) )
                    pReason = RID_STR_XFORMS_VALUE_MIN_LENGTH;
            }
        }
        return pReason;
    }

    OUString OStringType::_explainInvalid(TranslateId rReason)
    {
        sal_Int32 nValue = 0;
        OUStringBuffer sInfo;
        if (rReason == RID_STR_XFORMS_VALUE_LENGTH)
        {
            if( m_aLength >>= nValue )
                sInfo.append( nValue );
        }
        else if (rReason == RID_STR_XFORMS_VALUE_MAX_LENGTH)
        {
            if( m_aMaxLength >>= nValue )
                sInfo.append( nValue );
        }
        else if (rReason == RID_STR_XFORMS_VALUE_MIN_LENGTH)
        {
            if( m_aMinLength >>= nValue )
                sInfo.append( nValue );
        }
        else if (rReason)
        {
            sInfo.append(OStringType_Base::_explainInvalid(rReason));
        }
        return sInfo.makeStringAndClear();
    }

    OAnyURIType::OAnyURIType( const OUString& _rName, sal_Int16 _nTypeClass )
        : OAnyURIType_Base(_rName, _nTypeClass)
        , m_xURLTransformer(css::util::URLTransformer::create(::comphelper::getProcessComponentContext()))
    {
    }

    void OAnyURIType::registerProperties()
    {
        OAnyURIType_Base::registerProperties();

        registerMayBeVoidProperty( PROPERTY_XSD_LENGTH, PROPERTY_ID_XSD_LENGTH, css::beans::PropertyAttribute::BOUND | css::beans::PropertyAttribute::MAYBEVOID,
            &m_aLength, cppu::UnoType<sal_Int32>::get() );

        registerMayBeVoidProperty( PROPERTY_XSD_MIN_LENGTH, PROPERTY_ID_XSD_MIN_LENGTH, css::beans::PropertyAttribute::BOUND | css::beans::PropertyAttribute::MAYBEVOID,
            &m_aMinLength, cppu::UnoType<sal_Int32>::get() );

        registerMayBeVoidProperty( PROPERTY_XSD_MAX_LENGTH, PROPERTY_ID_XSD_MAX_LENGTH, css::beans::PropertyAttribute::BOUND | css::beans::PropertyAttribute::MAYBEVOID,
            &m_aMaxLength, cppu::UnoType<sal_Int32>::get() );
    }

    rtl::Reference<OXSDDataType> OAnyURIType::createClone( const OUString& _rName ) const
    {
        return new OAnyURIType( _rName, getTypeClass() );
    }

    void OAnyURIType::initializeClone( const OXSDDataType& _rCloneSource )
    {
        OAnyURIType_Base::initializeClone( _rCloneSource );
        initializeTypedClone( static_cast< const OAnyURIType& >( _rCloneSource ) );
    }



    void OAnyURIType::initializeTypedClone( const OAnyURIType& _rCloneSource )
    {
        m_aLength       = _rCloneSource.m_aLength;
        m_aMinLength    = _rCloneSource.m_aMinLength;
        m_aMaxLength    = _rCloneSource.m_aMaxLength;
    }


    bool OAnyURIType::checkPropertySanity( sal_Int32 _nHandle, const Any& _rNewValue, OUString& _rErrorMessage )
    {
        // let the base class do the conversion
        if ( !OAnyURIType_Base::checkPropertySanity( _nHandle, _rNewValue, _rErrorMessage ) )
            return false;

        _rErrorMessage.clear();
        switch ( _nHandle )
        {
            case PROPERTY_ID_XSD_LENGTH:
            case PROPERTY_ID_XSD_MIN_LENGTH:
            case PROPERTY_ID_XSD_MAX_LENGTH:
            {
                sal_Int32 nValue( 0 );
                OSL_VERIFY( _rNewValue >>= nValue );
                if ( nValue < 0 )
                    _rErrorMessage = u"Length limits must denote positive integer values."_ustr;
                        // TODO/eforms: localize the error message
            }
            break;
        }

        return _rErrorMessage.isEmpty();
    }


    TranslateId OAnyURIType::_validate( const OUString& rValue )
    {
        // check regexp, whitespace etc. in parent class
        TranslateId pReason = OAnyURIType_Base::_validate( rValue );

        if (!pReason)
        {
            // check AnyURI constraints
            sal_Int32 nLength = rValue.getLength();
            sal_Int32 nLimit = 0;
            if ( m_aLength >>= nLimit )
            {
                if ( nLimit != nLength )
                    pReason = RID_STR_XFORMS_VALUE_LENGTH;
            }
            else
            {
                if ( ( m_aMaxLength >>= nLimit ) && ( nLength > nLimit ) )
                    pReason = RID_STR_XFORMS_VALUE_MAX_LENGTH;
                else if ( ( m_aMinLength >>= nLimit ) && ( nLength < nLimit ) )
                    pReason = RID_STR_XFORMS_VALUE_MIN_LENGTH;
            }
            // check URL
            css::util::URL aCommandURL;
            aCommandURL.Complete = rValue;
            if (!m_xURLTransformer->parseStrict(aCommandURL))
                pReason = RID_STR_XFORMS_INVALID_VALUE;

        }
        return pReason;
    }

    OUString OAnyURIType::_explainInvalid(TranslateId rReason)
    {
        sal_Int32 nValue = 0;
        OUStringBuffer sInfo;
        if (rReason == RID_STR_XFORMS_VALUE_LENGTH)
        {
            if( m_aLength >>= nValue )
                sInfo.append( nValue );
        }
        else if (rReason == RID_STR_XFORMS_VALUE_MAX_LENGTH)
        {
            if( m_aMaxLength >>= nValue )
                sInfo.append( nValue );
        }
        else if (rReason == RID_STR_XFORMS_VALUE_MIN_LENGTH)
        {
            if( m_aMinLength >>= nValue )
                sInfo.append( nValue );
        }
        else if (rReason)
        {
            sInfo.append(OAnyURIType_Base::_explainInvalid(rReason));
        }
        return sInfo.makeStringAndClear();
    }

    OBooleanType::OBooleanType( const OUString& _rName )
        :OBooleanType_Base( _rName, DataTypeClass::BOOLEAN )
    {
    }

    rtl::Reference<OXSDDataType> OBooleanType::createClone( const OUString& _rName ) const
    {
        return new OBooleanType( _rName );
    }

    void OBooleanType::initializeClone( const OXSDDataType& _rCloneSource )
    {
        OBooleanType_Base::initializeClone( _rCloneSource );
    }

    TranslateId OBooleanType::_validate( const OUString& sValue )
    {
        TranslateId pInvalidityReason = OBooleanType_Base::_validate( sValue );
        if ( pInvalidityReason )
            return pInvalidityReason;

        bool bValid = sValue == "0" || sValue == "1" || sValue == "true" || sValue == "false";
        return bValid ? TranslateId() : RID_STR_XFORMS_INVALID_VALUE;
    }

    OUString OBooleanType::_explainInvalid(TranslateId rReason)
    {
        return !rReason ? OUString() : getName();
    }

    ODecimalType::ODecimalType( const OUString& _rName, sal_Int16 _nTypeClass )
        :ODecimalType_Base( _rName, _nTypeClass )
    {
    }

    rtl::Reference<OXSDDataType> ODecimalType::createClone( const OUString& _rName ) const
    {
        return new ODecimalType( _rName, getTypeClass() );
    }
    void ODecimalType::initializeClone( const OXSDDataType& _rCloneSource )
    {
        ODecimalType_Base::initializeClone( _rCloneSource );
        initializeTypedClone( static_cast< const ODecimalType& >( _rCloneSource ) );
    }

    void ODecimalType::initializeTypedClone( const ODecimalType& _rCloneSource )
    {
        m_aTotalDigits    = _rCloneSource.m_aTotalDigits;
        m_aFractionDigits = _rCloneSource.m_aFractionDigits;
    }


    void ODecimalType::registerProperties()
    {
        ODecimalType_Base::registerProperties();

        registerMayBeVoidProperty( PROPERTY_XSD_TOTAL_DIGITS, PROPERTY_ID_XSD_TOTAL_DIGITS, css::beans::PropertyAttribute::BOUND | css::beans::PropertyAttribute::MAYBEVOID,
            &m_aTotalDigits, cppu::UnoType<sal_Int32>::get() );

        registerMayBeVoidProperty( PROPERTY_XSD_FRACTION_DIGITS, PROPERTY_ID_XSD_FRACTION_DIGITS, css::beans::PropertyAttribute::BOUND | css::beans::PropertyAttribute::MAYBEVOID,
            &m_aFractionDigits, cppu::UnoType<sal_Int32>::get() );
    }


    // validate decimals and return code for which facets failed
    // to be used by: ODecimalType::validate and ODecimalType::explainInvalid
    TranslateId ODecimalType::_validate( const OUString& rValue )
    {
        TranslateId pReason = ODecimalType_Base::_validate( rValue );

        // check digits (if no other cause is available so far)
        if (!pReason)
        {
            sal_Int32 nLength = rValue.getLength();
            sal_Int32 n = 0;
            sal_Int32 nTotalDigits = 0;
            sal_Int32 nFractionDigits = 0;
            const sal_Unicode* pValue = rValue.getStr();
            for( ; n < nLength && pValue[n] != '.'; n++ )
                if( pValue[n] >= '0'
                    && pValue[n] <= '9')
                    nTotalDigits++;
            for( ; n < nLength; n++ )
                if( pValue[n] >= '0'
                    && pValue[n] <= '9')
                    nFractionDigits++;
            nTotalDigits += nFractionDigits;

            sal_Int32 nValue = 0;
            if( ( m_aTotalDigits >>= nValue ) &&  nTotalDigits > nValue )
                pReason = RID_STR_XFORMS_VALUE_TOTAL_DIGITS;
            else if( ( m_aFractionDigits >>= nValue ) &&
                     ( nFractionDigits > nValue ) )
                pReason = RID_STR_XFORMS_VALUE_FRACTION_DIGITS;
        }

        return pReason;
    }

    OUString ODecimalType::_explainInvalid(TranslateId rReason)
    {
        sal_Int32 nValue = 0;
        OUStringBuffer sInfo;
        if (rReason == RID_STR_XFORMS_VALUE_TOTAL_DIGITS)
        {
            if( m_aTotalDigits >>= nValue )
                sInfo.append( nValue );
        }
        else if (rReason == RID_STR_XFORMS_VALUE_FRACTION_DIGITS)
        {
            if( m_aFractionDigits >>= nValue )
                sInfo.append( nValue );
        }
        else
        {
            sInfo.append(ODecimalType_Base::_explainInvalid(rReason));
        }
        return sInfo.makeStringAndClear();
    }

    OUString ODecimalType::typedValueAsHumanReadableString( const Any& _rValue ) const
    {
        double fValue( 0 );
        normalizeValue( _rValue, fValue );
        return OUString::number( fValue );
    }


    void ODecimalType::normalizeValue( const Any& _rValue, double& _rDoubleValue ) const
    {
        OSL_VERIFY( _rValue >>= _rDoubleValue );
    }


    ODateType::ODateType(const OUString& _rName)
        :ODateType_Base(_rName, DataTypeClass::DATE)
    {
    }
    rtl::Reference<OXSDDataType> ODateType::createClone(const OUString& _rName) const
    {
        return new ODateType(_rName);
    }
    void ODateType::initializeClone( const OXSDDataType& _rCloneSource )
    {
        ODateType_Base::initializeClone(_rCloneSource);
        initializeTypedClone(static_cast< const ODateType& >(_rCloneSource));
    }

    TranslateId ODateType::_validate( const OUString& _rValue )
    {
        return ODateType_Base::_validate( _rValue );
    }

    bool ODateType::_getValue( const OUString& value, double& fValue )
    {
        Any aTypeValue;
        try
        {
            aTypeValue = Convert::get().toAny( value, getCppuType() );
        }
        catch (css::lang::IllegalArgumentException)
        {
            return false;
        }

        Date aValue;
        if ( !( aTypeValue >>= aValue ) )
            return false;

        ::Date aToolsDate( aValue.Day, aValue.Month, aValue.Year );
        fValue = aToolsDate.GetDate();
        return true;
    }


    OUString ODateType::typedValueAsHumanReadableString( const Any& _rValue ) const
    {
        OSL_PRECOND( _rValue.getValueType().equals( getCppuType() ), "ODateType::typedValueAsHumanReadableString: unexpected type" );
        return Convert::get().toXSD( _rValue );
    }


    void ODateType::normalizeValue( const Any& _rValue, double& _rDoubleValue ) const
    {
        Date aValue;
        OSL_VERIFY( _rValue >>= aValue );
        ::Date aToolsDate( aValue.Day, aValue.Month, aValue.Year );
        _rDoubleValue = aToolsDate.GetDate();
    }


    OTimeType::OTimeType(const OUString& _rName)
        :OTimeType_Base(_rName, DataTypeClass::TIME)
    {
    }
    rtl::Reference<OXSDDataType> OTimeType::createClone(const OUString& _rName) const
    {
        return new OTimeType(_rName);
    }
    void OTimeType::initializeClone( const OXSDDataType& _rCloneSource )
    {
        OTimeType_Base::initializeClone(_rCloneSource);
        initializeTypedClone(static_cast< const OTimeType& >(_rCloneSource));
    }

    TranslateId OTimeType::_validate( const OUString& _rValue )
    {
        return OTimeType_Base::_validate( _rValue );
    }

    bool OTimeType::_getValue( const OUString& value, double& fValue )
    {
        Any aTypedValue;
        try
        {
            aTypedValue = Convert::get().toAny( value, getCppuType() );
        }
        catch (css::lang::IllegalArgumentException)
        {
            return false;
        }

        css::util::Time aValue;
        if ( !( aTypedValue >>= aValue ) )
            return false;

        ::tools::Time aToolsTime( aValue );
        // no loss/rounding; IEEE 754 double-precision floating-point
        // has a mantissa of 53 bits; we need at the very most 50 bits:
        // format of aToolsTime.GetTime() is (in decimal) hhmmssnnnnnnnnn
        // and 999999999999999 = 0x38D7EA4C67FFF
        // in reality I doubt we need (much) more than
        //     240000000000000 = 0x0DA475ABF0000
        // that is 48 bits
        fValue = aToolsTime.GetTime();
        return true;
    }


    OUString OTimeType::typedValueAsHumanReadableString( const Any& _rValue ) const
    {
        OSL_PRECOND( _rValue.getValueType().equals( getCppuType() ), "OTimeType::typedValueAsHumanReadableString: unexpected type" );
        return Convert::get().toXSD( _rValue );
    }


    void OTimeType::normalizeValue( const Any& _rValue, double& _rDoubleValue ) const
    {
        css::util::Time aValue;
        OSL_VERIFY( _rValue >>= aValue );
        ::tools::Time aToolsTime( aValue );
        _rDoubleValue = aToolsTime.GetTime();
    }


    ODateTimeType::ODateTimeType(const OUString& _rName)
        :ODateTimeType_Base(_rName, DataTypeClass::DATETIME)
    {
    }
    rtl::Reference<OXSDDataType> ODateTimeType::createClone(const OUString& _rName) const
    {
        return new ODateTimeType(_rName);
    }
    void ODateTimeType::initializeClone( const OXSDDataType& _rCloneSource )
    {
        ODateTimeType_Base::initializeClone(_rCloneSource);
        initializeTypedClone(static_cast< const ODateTimeType& >(_rCloneSource));
    }

    TranslateId ODateTimeType::_validate( const OUString& _rValue )
    {
        return ODateTimeType_Base::_validate( _rValue );
    }

    namespace
    {
        double lcl_normalizeDateTime( const DateTime& _rValue )
        {
            ::DateTime aToolsValue(_rValue);

            double fValue = 0;
            // days since 1899-12-30 (which is relatively arbitrary but fixed date)
            fValue += ::Date( aToolsValue ) - ::Date( 30, 12, 1899 );
            // time
            fValue += aToolsValue.GetTimeInDays();
            return fValue;
        }
    }


    bool ODateTimeType::_getValue( const OUString& value, double& fValue )
    {
        Any aTypedValue;
        try
        {
            aTypedValue = Convert::get().toAny( value, getCppuType() );
        }
        catch (css::uno::RuntimeException)
        {
            return false;
        }

        DateTime aValue;
        if ( !( aTypedValue >>= aValue ) )
            return false;

        fValue = lcl_normalizeDateTime( aValue );
        return true;
    }


    OUString ODateTimeType::typedValueAsHumanReadableString( const Any& _rValue ) const
    {
        OSL_PRECOND( _rValue.getValueType().equals( getCppuType() ), "OTimeType::typedValueAsHumanReadableString: unexpected type" );
        OUString sString = Convert::get().toXSD( _rValue );

        // ISO 8601 notation has a "T" to separate between date and time. Our only concession
        // to the "human readable" in the method name is to replace this T with a whitespace.
        OSL_ENSURE( sString.indexOf( 'T' ) != -1, "ODateTimeType::typedValueAsHumanReadableString: hmm - no ISO notation?" );
        return sString.replace( 'T', ' ' );
    }


    void ODateTimeType::normalizeValue( const Any& _rValue, double& _rDoubleValue ) const
    {
        DateTime aValue;
        OSL_VERIFY( _rValue >>= aValue );
        _rDoubleValue = lcl_normalizeDateTime( aValue );
    }

    OShortIntegerType::OShortIntegerType( const OUString& _rName, sal_Int16 _nTypeClass )
        :OShortIntegerType_Base( _rName, _nTypeClass )
    {
    }

    rtl::Reference<OXSDDataType> OShortIntegerType::createClone( const OUString& _rName ) const
    {
        return new OShortIntegerType( _rName, getTypeClass() );
    }
    void OShortIntegerType::initializeClone( const OXSDDataType& _rCloneSource )
    {
        OShortIntegerType_Base::initializeClone( _rCloneSource );
        initializeTypedClone( static_cast< const OShortIntegerType& >( _rCloneSource ) );
    }

    static bool lcl_getValueYear( std::u16string_view value, double& fValue )
    {
        if (value.size() > 4)
        {
             fValue = 0;
             return false;
        }
        if (o3tl::equalsAscii(value, "0"))
        {
            fValue = 0;
            return true;
        }
        sal_Int32 int32Value = o3tl::toInt32(value);
        if (
            int32Value == 0 ||
            int32Value < 0 ||
            int32Value > 10000
           )
        {
             fValue = 0;
             return false;
        }
        fValue = static_cast<double>(static_cast<sal_Int16>(int32Value));
        return true;
    }

    static bool lcl_getValueMonth( std::u16string_view value, double& fValue )
    {
        if (value.size() > 2)
        {
             fValue = 0;
             return false;
        }
        sal_Int32 int32Value = o3tl::toInt32(value);
        if (
            int32Value == 0 ||
            int32Value < 1 ||
            int32Value > 12
           )
        {
             fValue = 0;
             return false;
        }
        fValue = static_cast<double>(static_cast<sal_Int16>(int32Value));
        return true;
    }

    static bool lcl_getValueDay( std::u16string_view value, double& fValue )
    {
        if (value.size() > 2)
        {
             fValue = 0;
             return false;
        }
        sal_Int32 int32Value = o3tl::toInt32(value);
        if (
            int32Value == 0 ||
            int32Value < 1 ||
            int32Value > 31
           )
        {
             fValue = 0;
             return false;
        }
        fValue = static_cast<double>(static_cast<sal_Int16>(int32Value));
        return true;
    }

    bool OShortIntegerType::_getValue( const OUString& value, double& fValue )
    {
        switch (this->getTypeClass())
        {
            case css::xsd::DataTypeClass::gYear:
                return lcl_getValueYear(value, fValue);

            case css::xsd::DataTypeClass::gMonth:
                return lcl_getValueMonth(value, fValue);

            case css::xsd::DataTypeClass::gDay:
                return lcl_getValueDay(value, fValue);
            default:
                // for the moment, the only types which derive from OShortIntegerType are:
                // gYear, gMonth and gDay, see ODataTypeRepository ctr
                return false;
        }
    }


    OUString OShortIntegerType::typedValueAsHumanReadableString( const Any& _rValue ) const
    {
        sal_Int16 nValue( 0 );
        OSL_VERIFY( _rValue >>= nValue );
        return OUString::number( nValue );
    }


    void OShortIntegerType::normalizeValue( const Any& _rValue, double& _rDoubleValue ) const
    {
        sal_Int16 nValue( 0 );
        OSL_VERIFY( _rValue >>= nValue );
        _rDoubleValue = nValue;
    }


template< typename CONCRETE_DATA_TYPE_IMPL, typename SUPERCLASS >
ODerivedDataType< CONCRETE_DATA_TYPE_IMPL, SUPERCLASS >::ODerivedDataType( const OUString& _rName, sal_Int16 _nTypeClass )
    :SUPERCLASS( _rName, _nTypeClass )
    ,m_bPropertiesRegistered( false )
{
}


template< typename CONCRETE_DATA_TYPE_IMPL, typename SUPERCLASS >
::cppu::IPropertyArrayHelper* ODerivedDataType< CONCRETE_DATA_TYPE_IMPL, SUPERCLASS >::createArrayHelper( ) const
{
    cpo::uno::Sequence< css::beans::Property > aProps;
    ODerivedDataType< CONCRETE_DATA_TYPE_IMPL, SUPERCLASS >::describeProperties( aProps );
    return new ::cppu::OPropertyArrayHelper( aProps );
}


template< typename CONCRETE_DATA_TYPE_IMPL, typename SUPERCLASS >
::cppu::IPropertyArrayHelper& ODerivedDataType< CONCRETE_DATA_TYPE_IMPL, SUPERCLASS >::getInfoHelper()
{
    if ( !m_bPropertiesRegistered )
    {
        this->registerProperties();
        m_bPropertiesRegistered = true;
    }

    return *ODerivedDataType< CONCRETE_DATA_TYPE_IMPL, SUPERCLASS >::getArrayHelper();
}


template< typename VALUE_TYPE >
OValueLimitedType< VALUE_TYPE >::OValueLimitedType( const OUString& _rName, sal_Int16 _nTypeClass )
    :OValueLimitedType_Base( _rName, _nTypeClass )
{
}

} // namespace xforms


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