diff options
Diffstat (limited to 'forms/source/xforms/binding.cxx')
-rw-r--r-- | forms/source/xforms/binding.cxx | 1409 |
1 files changed, 1409 insertions, 0 deletions
diff --git a/forms/source/xforms/binding.cxx b/forms/source/xforms/binding.cxx new file mode 100644 index 000000000000..3df2385796f0 --- /dev/null +++ b/forms/source/xforms/binding.cxx @@ -0,0 +1,1409 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +// MARKER(update_precomp.py): autogen include statement, do not remove +#include "precompiled_forms.hxx" + +#include "binding.hxx" + +#include "model.hxx" +#include "unohelper.hxx" +#include "NameContainer.hxx" +#include "evaluationcontext.hxx" +#include "convert.hxx" +#include "resourcehelper.hxx" +#include "xmlhelper.hxx" +#include "xformsevent.hxx" + +#include <rtl/ustrbuf.hxx> +#include <osl/diagnose.h> + +#include <tools/diagnose_ex.h> + +#include <algorithm> +#include <functional> + +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/xml/dom/XNodeList.hpp> +#include <com/sun/star/xml/dom/XNode.hpp> +#include <com/sun/star/xml/dom/XDocument.hpp> +#include <com/sun/star/xml/dom/XElement.hpp> +#include <com/sun/star/xml/dom/NodeType.hpp> +#include <com/sun/star/xml/dom/events/XEventTarget.hpp> +#include <com/sun/star/xml/dom/events/XEventListener.hpp> +#include <com/sun/star/xml/dom/events/XDocumentEvent.hpp> +#include <com/sun/star/lang/XUnoTunnel.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <com/sun/star/container/XSet.hpp> +#include <com/sun/star/container/XNameContainer.hpp> + +#include <comphelper/propertysetinfo.hxx> +#include <unotools/textsearch.hxx> +#include <cppuhelper/typeprovider.hxx> + +using namespace com::sun::star::xml::xpath; +using namespace com::sun::star::xml::dom::events; + +using rtl::OUString; +using rtl::OUStringBuffer; +using std::vector; +using xforms::Binding; +using xforms::MIP; +using xforms::Model; +using xforms::getResource; +using xforms::EvaluationContext; +using com::sun::star::beans::PropertyVetoException; +using com::sun::star::beans::UnknownPropertyException; +using com::sun::star::beans::XPropertySet; +using com::sun::star::container::XSet; +using com::sun::star::container::XNameAccess; +using com::sun::star::form::binding::IncompatibleTypesException; +using com::sun::star::form::binding::InvalidBindingStateException; +using com::sun::star::form::binding::XValueBinding; +using com::sun::star::lang::EventObject; +using com::sun::star::lang::IllegalArgumentException; +using com::sun::star::lang::IndexOutOfBoundsException; +using com::sun::star::lang::NoSupportException; +using com::sun::star::lang::NullPointerException; +using com::sun::star::lang::WrappedTargetException; +using com::sun::star::lang::XUnoTunnel; +using com::sun::star::uno::Any; +using com::sun::star::uno::Reference; +using com::sun::star::uno::RuntimeException; +using com::sun::star::uno::Sequence; +using com::sun::star::uno::UNO_QUERY; +using com::sun::star::uno::UNO_QUERY_THROW; +using com::sun::star::uno::XInterface; +using com::sun::star::uno::Exception; +using com::sun::star::uno::makeAny; +using com::sun::star::util::XModifyListener; +using com::sun::star::xforms::XDataTypeRepository; +using com::sun::star::xml::dom::NodeType_ATTRIBUTE_NODE; +using com::sun::star::xml::dom::NodeType_TEXT_NODE; +using com::sun::star::xml::dom::XNode; +using com::sun::star::xml::dom::XNodeList; +using com::sun::star::xml::dom::events::XEventListener; +using com::sun::star::xml::dom::events::XEventTarget; +using com::sun::star::xsd::XDataType; + + + + +#define EXCEPT(msg) OUSTRING(msg),static_cast<XValueBinding*>(this) + +#define HANDLE_BindingID 0 +#define HANDLE_BindingExpression 1 +#define HANDLE_Model 2 +#define HANDLE_ModelID 3 +#define HANDLE_BindingNamespaces 4 +#define HANDLE_ReadonlyExpression 5 +#define HANDLE_RelevantExpression 6 +#define HANDLE_RequiredExpression 7 +#define HANDLE_ConstraintExpression 8 +#define HANDLE_CalculateExpression 9 +#define HANDLE_Type 10 +#define HANDLE_ReadOnly 11 // from com.sun.star.form.binding.ValueBinding, for interaction with a bound form control +#define HANDLE_Relevant 12 // from com.sun.star.form.binding.ValueBinding, for interaction with a bound form control +#define HANDLE_ModelNamespaces 13 +#define HANDLE_ExternalData 14 + + +Binding::Binding() : + mxModel(), + msBindingID(), + maBindingExpression(), + maReadonly(), + mxNamespaces( new NameContainer<OUString>() ), + mbInCalculate( false ), + mnDeferModifyNotifications( 0 ), + mbValueModified( false ), + mbBindingModified( false ) + +{ + initializePropertySet(); +} + +Binding::~Binding() throw() +{ + _setModel(NULL); +} + + +Binding::Model_t Binding::getModel() const +{ + return mxModel; +} + +void Binding::_setModel( const Model_t& xModel ) +{ + PropertyChangeNotifier aNotifyModelChange( *this, HANDLE_Model ); + PropertyChangeNotifier aNotifyModelIDChange( *this, HANDLE_ModelID ); + + // prepare binding for removal of old model + clear(); // remove all cached data (e.g. XPath evaluation results) + XNameContainer_t xNamespaces = getModelNamespaces(); // save namespaces + + mxModel = xModel; + + // set namespaces (and move to model, if appropriate) + setBindingNamespaces( xNamespaces ); + _checkBindingID(); + + notifyAndCachePropertyValue( HANDLE_ExternalData ); +} + + +OUString Binding::getModelID() const +{ + Model* pModel = getModelImpl(); + return ( pModel == NULL ) ? OUString() : pModel->getID(); +} + + +Binding::XNodeList_t Binding::getXNodeList() +{ + // first make sure we are bound + if( ! maBindingExpression.hasValue() ) + bind( sal_False ); + + return maBindingExpression.getXNodeList(); +} + +bool Binding::isSimpleBinding() const +{ + return maBindingExpression.isSimpleExpression() + && maReadonly.isSimpleExpression() + && maRelevant.isSimpleExpression() + && maRequired.isSimpleExpression() + && maConstraint.isSimpleExpression() + && maCalculate.isSimpleExpression(); +} + +bool Binding::isSimpleBindingExpression() const +{ + return maBindingExpression.isSimpleExpression(); +} + +void Binding::update() +{ + // clear all expressions (to remove cached node references) + maBindingExpression.clear(); + maReadonly.clear(); + maRelevant.clear(); + maRequired.clear(); + maConstraint.clear(); + maCalculate.clear(); + + // let's just pretend the binding has been modified -> full rebind() + bindingModified(); +} + +void Binding::deferNotifications( bool bDefer ) +{ + mnDeferModifyNotifications += ( bDefer ? 1 : -1 ); + OSL_ENSURE( mnDeferModifyNotifications >= 0, "you're deferring too much" ); + + if( mnDeferModifyNotifications == 0 ) + { + if( mbBindingModified ) + bindingModified(); + if( mbValueModified ) + valueModified(); + } + + OSL_ENSURE( ( mnDeferModifyNotifications > 0 ) + || ( ! mbBindingModified && ! mbValueModified ), + "deferred modifications not delivered?" ); +} + +bool Binding::isValid() +{ + // TODO: determine whether node is suitable, not just whether it exists + return maBindingExpression.getNode().is() && + isValid_DataType() && + maMIP.isConstraint() && + ( ! maMIP.isRequired() || + ( maBindingExpression.hasValue() && + maBindingExpression.getString().getLength() > 0 ) ); +} + +bool Binding::isUseful() +{ + // we are useful, if + // 0) we don't have a model + // (at least, in this case we shouldn't be removed from the model) + // 1) we have a proper name + // 2) we have some MIPs, + // 3) we are bound to some control + // (this can be assumed if some listeners are set) + bool bUseful = + getModelImpl() == NULL +// || msBindingID.getLength() > 0 + || msTypeName.getLength() > 0 + || ! maReadonly.isEmptyExpression() + || ! maRelevant.isEmptyExpression() + || ! maRequired.isEmptyExpression() + || ! maConstraint.isEmptyExpression() + || ! maCalculate.isEmptyExpression() + || ! maModifyListeners.empty() + || ! maListEntryListeners.empty() + || ! maValidityListeners.empty(); + + return bUseful; +} + +OUString Binding::explainInvalid() +{ + OUString sReason; + if( ! maBindingExpression.getNode().is() ) + { + sReason = ( maBindingExpression.getExpression().getLength() == 0 ) + ? getResource( RID_STR_XFORMS_NO_BINDING_EXPRESSION ) + : getResource( RID_STR_XFORMS_INVALID_BINDING_EXPRESSION ); + } + else if( ! isValid_DataType() ) + { + sReason = explainInvalid_DataType(); + if( sReason.getLength() == 0 ) + { + // no explanation given by data type? Then give generic message + sReason = getResource( RID_STR_XFORMS_INVALID_VALUE, + maMIP.getTypeName() ); + } + } + else if( ! maMIP.isConstraint() ) + { + sReason = maMIP.getConstraintExplanation(); + } + else if( maMIP.isRequired() && maBindingExpression.hasValue() && + ( maBindingExpression.getString().getLength() == 0 ) ) + { + sReason = getResource( RID_STR_XFORMS_REQUIRED ); + } + // else: no explanation given; should only happen if data is valid + + OSL_ENSURE( ( sReason.getLength() == 0 ) == isValid(), + "invalid data should have an explanation!" ); + + return sReason; +} + + + +EvaluationContext Binding::getEvaluationContext() const +{ + OSL_ENSURE( getModelImpl() != NULL, "need model impl" ); + EvaluationContext aContext = getModelImpl()->getEvaluationContext(); + aContext.mxNamespaces = getBindingNamespaces(); + return aContext; +} + +::std::vector<EvaluationContext> Binding::getMIPEvaluationContexts() +{ + OSL_ENSURE( getModelImpl() != NULL, "need model impl" ); + + // bind (in case we were not bound before) + bind( sal_False ); + return _getMIPEvaluationContexts(); +} + + +Binding::IntSequence_t Binding::getUnoTunnelID() +{ + static cppu::OImplementationId aImplementationId; + return aImplementationId.getImplementationId(); +} + +Binding* SAL_CALL Binding::getBinding( const Reference<XPropertySet>& xPropertySet ) +{ + Reference<XUnoTunnel> xTunnel( xPropertySet, UNO_QUERY ); + return xTunnel.is() + ? reinterpret_cast<Binding*>( xTunnel->getSomething(getUnoTunnelID())) + : NULL; +} + + + + +OUString Binding::getBindingID() const +{ + return msBindingID; +} + +void Binding::setBindingID( const OUString& sBindingID ) +{ + msBindingID = sBindingID; +} + +OUString Binding::getBindingExpression() const +{ + return maBindingExpression.getExpression(); +} + +void Binding::setBindingExpression( const OUString& sBindingExpression) +{ + maBindingExpression.setExpression( sBindingExpression ); + bindingModified(); +} + +OUString Binding::getReadonlyExpression() const +{ + return maReadonly.getExpression(); +} + +void Binding::setReadonlyExpression( const OUString& sReadonly) +{ + maReadonly.setExpression( sReadonly ); + bindingModified(); +} + +OUString Binding::getRelevantExpression() const +{ + return maRelevant.getExpression(); +} + +void Binding::setRelevantExpression( const OUString& sRelevant ) +{ + maRelevant.setExpression( sRelevant ); + bindingModified(); +} + +OUString Binding::getRequiredExpression() const +{ + return maRequired.getExpression(); +} + +void Binding::setRequiredExpression( const OUString& sRequired ) +{ + maRequired.setExpression( sRequired ); + bindingModified(); +} + +OUString Binding::getConstraintExpression() const +{ + return maConstraint.getExpression(); +} + +void Binding::setConstraintExpression( const OUString& sConstraint ) +{ + maConstraint.setExpression( sConstraint ); + msExplainConstraint = getResource( RID_STR_XFORMS_INVALID_CONSTRAINT, + sConstraint ); + + // TODO: This should only re-evaluate the constraint, and notify + // the validity constraint listeners; instead we currently pretend + // the entire binding was notified, which does a little too much. + bindingModified(); +} + +OUString Binding::getCalculateExpression() const +{ + return maCalculate.getExpression(); +} + +void Binding::setCalculateExpression( const OUString& sCalculate ) +{ + maCalculate.setExpression( sCalculate ); + bindingModified(); +} + +OUString Binding::getType() const +{ + return msTypeName; +} + +void Binding::setType( const OUString& sTypeName ) +{ + msTypeName = sTypeName; + bindingModified(); +} + +Binding::XNameContainer_t Binding::getBindingNamespaces() const +{ + // return _getNamespaces(); + return mxNamespaces; +} + +void Binding::setBindingNamespaces( const XNameContainer_t& rNamespaces ) +{ + _setNamespaces( rNamespaces, true ); +} + +Binding::XNameContainer_t Binding::getModelNamespaces() const +{ + return _getNamespaces(); +} + +void Binding::setModelNamespaces( const XNameContainer_t& rNamespaces ) +{ + _setNamespaces( rNamespaces, false ); +} + +bool Binding::getReadOnly() const +{ + return maMIP.isReadonly(); +} + +bool Binding::getRelevant() const +{ + return maMIP.isRelevant(); +} + +bool Binding::getExternalData() const +{ + bool bExternalData = true; + if ( !mxModel.is() ) + return bExternalData; + + try + { + Reference< XPropertySet > xModelProps( mxModel, UNO_QUERY_THROW ); + OSL_VERIFY( + xModelProps->getPropertyValue( ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "ExternalData" ) ) ) >>= bExternalData ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION(); + } + return bExternalData; +} + + +void Binding::checkLive() + throw( RuntimeException ) +{ + if( ! isLive() ) + throw RuntimeException( EXCEPT("Binding not initialized") ); +} + +void Binding::checkModel() + throw( RuntimeException ) +{ + if( ! mxModel.is() ) + throw RuntimeException( EXCEPT("Binding has no Model") ); +} + +bool Binding::isLive() const +{ + const Model* pModel = getModelImpl(); + return ( pModel != NULL ) ? pModel->isInitialized() : false; +} + +Model* Binding::getModelImpl() const +{ + return getModelImpl( mxModel ); +} + +Model* Binding::getModelImpl( const Model_t& xModel ) const +{ + Reference<XUnoTunnel> xTunnel( xModel, UNO_QUERY ); + Model* pModel = xTunnel.is() + ? reinterpret_cast<Model*>( + xTunnel->getSomething( Model::getUnoTunnelID() ) ) + : NULL; + return pModel; +} + +void lcl_addListenerToNode( Reference<XNode> xNode, + Reference<XEventListener> xListener ) +{ + Reference<XEventTarget> xTarget( xNode, UNO_QUERY ); + if( xTarget.is() ) + { + xTarget->addEventListener( OUSTRING("DOMCharacterDataModified"), + xListener, false ); + xTarget->addEventListener( OUSTRING("DOMCharacterDataModified"), + xListener, true ); + xTarget->addEventListener( OUSTRING("DOMAttrModified"), + xListener, false ); + xTarget->addEventListener( OUSTRING("DOMAttrModified"), + xListener, true ); + xTarget->addEventListener( OUSTRING("DOMAttrModified"), + xListener, true ); + xTarget->addEventListener( OUSTRING("xforms-generic"), + xListener, true ); + } +} + +void lcl_removeListenerFromNode( Reference<XNode> xNode, + Reference<XEventListener> xListener ) +{ + Reference<XEventTarget> xTarget( xNode, UNO_QUERY ); + if( xTarget.is() ) + { + xTarget->removeEventListener( OUSTRING("DOMCharacterDataModified"), + xListener, false ); + xTarget->removeEventListener( OUSTRING("DOMCharacterDataModified"), + xListener, true ); + xTarget->removeEventListener( OUSTRING("DOMAttrModified"), + xListener, false ); + xTarget->removeEventListener( OUSTRING("DOMAttrModified"), + xListener, true ); + xTarget->removeEventListener( OUSTRING("xforms-generic"), + xListener, true ); + } +} + +::std::vector<EvaluationContext> Binding::_getMIPEvaluationContexts() const +{ + OSL_ENSURE( getModelImpl() != NULL, "need model impl" ); + + // iterate over nodes of bind expression and create + // EvaluationContext for each + PathExpression::NodeVector_t aNodes = maBindingExpression.getNodeList(); + ::std::vector<EvaluationContext> aVector; + sal_Int32 nCount = 0; // count nodes for context position + for( PathExpression::NodeVector_t::iterator aIter = aNodes.begin(); + aIter != aNodes.end(); + aIter++, nCount++ ) + { + OSL_ENSURE( aIter->is(), "no node?" ); + + // create proper evaluation context for this MIP + aVector.push_back( EvaluationContext( *aIter, getModel(), + getBindingNamespaces(), + nCount, aNodes.size() ) ); + } + return aVector; +} + +void Binding::bind( bool bForceRebind ) +{ + checkModel(); + + // bind() will evaluate this binding as follows: + // 1) evaluate the binding expression + // 1b) if necessary, create node according to 'lazy author' rules + // 2) register suitable listeners on the instance (and remove old ones) + // 3) remove old MIPs defined by this binding + // 4) for every node in the binding nodeset do: + // 1) create proper evaluation context for this MIP + // 2) evaluate calculate expression (and push value into instance) + // 3) evaluate remaining MIPs + // 4) evaluate the locally defined MIPs, and push them to the model + + + // 1) evaluate the binding expression + EvaluationContext aContext = getEvaluationContext(); + maBindingExpression.evaluate( aContext ); + if( ! maBindingExpression.getNode().is() ) + { + // 1b) create node (if valid element name) + if( isValidQName( maBindingExpression.getExpression(), + aContext.mxNamespaces ) ) + { + aContext.mxContextNode->appendChild( + Reference<XNode>( + aContext.mxContextNode->getOwnerDocument()->createElement( + maBindingExpression.getExpression() ), + UNO_QUERY ) ); + maBindingExpression.evaluate( aContext ); + OSL_ENSURE( maBindingExpression.getNode().is(), + "we should bind to the newly inserted node!" ); + } + } + PathExpression::NodeVector_t aNodes = maBindingExpression.getNodeList(); + + // 2) register suitable listeners on the instance (and remove old ones) + if( maEventNodes.empty() || bForceRebind ) + { + for( XNodes_t::iterator aIter = maEventNodes.begin(); + aIter != maEventNodes.end(); + aIter ++ ) + lcl_removeListenerFromNode( *aIter, this ); + maEventNodes.clear(); + if( isSimpleBinding() ) + for( PathExpression::NodeVector_t::iterator aIter = aNodes.begin(); + aIter != aNodes.end(); + aIter++ ) + maEventNodes.push_back( *aIter ); + else + maEventNodes.push_back( + Reference<XNode>( aContext.mxContextNode->getOwnerDocument(), + UNO_QUERY_THROW ) ); + for( PathExpression::NodeVector_t::iterator aIter2 = maEventNodes.begin(); + aIter2 != maEventNodes.end(); + aIter2 ++ ) + lcl_addListenerToNode( *aIter2, this ); + } + + // 3) remove old MIPs defined by this binding + Model* pModel = getModelImpl(); + OSL_ENSURE( pModel != NULL, "need model" ); + pModel->removeMIPs( this ); + + // 4) calculate all MIPs + ::std::vector<EvaluationContext> aMIPContexts = _getMIPEvaluationContexts(); + for( ::std::vector<EvaluationContext>::iterator aIter = aMIPContexts.begin(); + aIter != aMIPContexts.end(); + aIter++ ) + { + EvaluationContext& rContext = *aIter; + + // evaluate calculate expression (and push value into instance) + // (prevent recursion using mbInCalculate + if( ! maCalculate.isEmptyExpression() ) + { + if( ! mbInCalculate ) + { + mbInCalculate = true; + maCalculate.evaluate( rContext ); + pModel->setSimpleContent( rContext.mxContextNode, + maCalculate.getString() ); + mbInCalculate = false; + } + } + + // now evaluate remaining MIPs in the apropriate context + maReadonly.evaluate( rContext ); + maRelevant.evaluate( rContext ); + maRequired.evaluate( rContext ); + maConstraint.evaluate( rContext ); + // type is static; does not need updating + + // evaluate the locally defined MIPs, and push them to the model + pModel->addMIP( this, rContext.mxContextNode, getLocalMIP() ); + } +} + + +// helper for Binding::valueModified +void lcl_modified( const Binding::XModifyListener_t xListener, + const Reference<XInterface> xSource ) +{ + OSL_ENSURE( xListener.is(), "no listener?" ); + xListener->modified( EventObject( xSource ) ); +} + +// helper for Binding::valueModified +void lcl_listentry( const Binding::XListEntryListener_t xListener, + const Reference<XInterface> xSource ) +{ + OSL_ENSURE( xListener.is(), "no listener?" ); + // TODO: send fine granular events + xListener->allEntriesChanged( EventObject( xSource ) ); +} + +// helper for Binding::valueModified +void lcl_validate( const Binding::XValidityConstraintListener_t xListener, + const Reference<XInterface> xSource ) +{ + OSL_ENSURE( xListener.is(), "no listener?" ); + xListener->validityConstraintChanged( EventObject( xSource ) ); +} + + +void Binding::valueModified() +{ + // defer notifications, if so desired + if( mnDeferModifyNotifications > 0 ) + { + mbValueModified = true; + return; + } + mbValueModified = false; + + // query MIP used by our first node (also note validity) + Reference<XNode> xNode = maBindingExpression.getNode(); + maMIP = getModelImpl()->queryMIP( xNode ); + + // distribute MIPs _used_ by this binding + if( xNode.is() ) + { + notifyAndCachePropertyValue( HANDLE_ReadOnly ); + notifyAndCachePropertyValue( HANDLE_Relevant ); + } + + // iterate over _value_ listeners and send each a modified signal, + // using this object as source (will also update validity, because + // control will query once the value has changed) + Reference<XInterface> xSource = static_cast<XPropertySet*>( this ); + ::std::for_each( maModifyListeners.begin(), + maModifyListeners.end(), + ::std::bind2nd( ::std::ptr_fun( lcl_modified ), xSource ) ); + ::std::for_each( maListEntryListeners.begin(), + maListEntryListeners.end(), + ::std::bind2nd( ::std::ptr_fun( lcl_listentry ), xSource ) ); + ::std::for_each( maValidityListeners.begin(), + maValidityListeners.end(), + ::std::bind2nd( ::std::ptr_fun( lcl_validate ), xSource ) ); + + // now distribute MIPs to childs + if( xNode.is() ) + distributeMIP( xNode->getFirstChild() ); +} + +void Binding::distributeMIP( const XNode_t & rxNode ) { + + typedef com::sun::star::xforms::XFormsEventConcrete XFormsEvent_t; + OUString sEventName( RTL_CONSTASCII_USTRINGPARAM("xforms-generic") ); + XFormsEvent_t *pEvent = new XFormsEvent_t; + pEvent->initXFormsEvent(sEventName, sal_True, sal_False); + Reference<XEvent> xEvent(pEvent); + + // naive depth-first traversal + XNode_t xNode( rxNode ); + while(xNode.is()) { + + // notifications should be triggered at the + // leaf nodes first, bubbling upwards the hierarchy. + XNode_t child(xNode->getFirstChild()); + if(child.is()) + distributeMIP(child); + + // we're standing at a particular node somewhere + // below the one which changed a property (MIP). + // bindings which are listening at this node will receive + // a notification message about what exactly happened. + Reference< XEventTarget > target(xNode,UNO_QUERY); + target->dispatchEvent(xEvent); + + xNode = xNode->getNextSibling(); + }; +} + +void Binding::bindingModified() +{ + // defer notifications, if so desired + if( mnDeferModifyNotifications > 0 ) + { + mbBindingModified = true; + return; + } + mbBindingModified = false; + + // rebind (if live); then call valueModified + // A binding should be inert until its model is fully constructed. + if( isLive() ) + { + bind( true ); + valueModified(); + } +} + + +MIP Binding::getLocalMIP() const +{ + MIP aMIP; + + if( maReadonly.hasValue() ) + aMIP.setReadonly( maReadonly.getBool( false ) ); + if( maRelevant.hasValue() ) + aMIP.setRelevant( maRelevant.getBool( true ) ); + if( maRequired.hasValue() ) + aMIP.setRequired( maRequired.getBool( false ) ); + if( maConstraint.hasValue() ) + { + aMIP.setConstraint( maConstraint.getBool( true ) ); + if( ! aMIP.isConstraint() ) + aMIP.setConstraintExplanation( msExplainConstraint ); + } + if( msTypeName.getLength() > 0 ) + aMIP.setTypeName( msTypeName ); + + // calculate: only handle presence of calculate; value set elsewhere + aMIP.setHasCalculate( !maCalculate.isEmptyExpression() ); + + return aMIP; +} + +Binding::XDataType_t Binding::getDataType() +{ + OSL_ENSURE( getModel().is(), "need model" ); + OSL_ENSURE( getModel()->getDataTypeRepository().is(), "need types" ); + + Reference<XDataTypeRepository> xRepository( + getModel()->getDataTypeRepository(), UNO_QUERY ); + OUString sTypeName = maMIP.getTypeName(); + + return ( xRepository.is() && xRepository->hasByName( sTypeName ) ) + ? Reference<XDataType>( xRepository->getByName( sTypeName ), UNO_QUERY) + : Reference<XDataType>( NULL ); +} + +bool Binding::isValid_DataType() +{ + Reference<XDataType> xDataType = getDataType(); + return xDataType.is() + ? xDataType->validate( maBindingExpression.getString() ) + : true; +} + +rtl::OUString Binding::explainInvalid_DataType() +{ + Reference<XDataType> xDataType = getDataType(); + return xDataType.is() + ? xDataType->explainInvalid( maBindingExpression.getString() ) + : OUString(); +} + +void Binding::clear() +{ + // remove MIPs contributed by this binding + Model* pModel = getModelImpl(); + if( pModel != NULL ) + pModel->removeMIPs( this ); + + // remove all references + for( XNodes_t::iterator aIter = maEventNodes.begin(); + aIter != maEventNodes.end(); + aIter ++ ) + lcl_removeListenerFromNode( *aIter, this ); + maEventNodes.clear(); + + // clear expressions + maBindingExpression.clear(); + maReadonly.clear(); + maRelevant.clear(); + maRequired.clear(); + maConstraint.clear(); + maCalculate.clear(); + + // TODO: what about our listeners? +} + + +void lcl_removeOtherNamespaces( const Binding::XNameContainer_t& xFrom, + Binding::XNameContainer_t& xTo ) +{ + OSL_ENSURE( xFrom.is(), "no source" ); + OSL_ENSURE( xTo.is(), "no target" ); + + // iterate over name in source + Sequence<OUString> aNames = xTo->getElementNames(); + sal_Int32 nNames = aNames.getLength(); + const OUString* pNames = aNames.getConstArray(); + for( sal_Int32 i = 0; i < nNames; i++ ) + { + const OUString& rName = pNames[i]; + + if( ! xFrom->hasByName( rName ) ) + xTo->removeByName( rName ); + } +} + +/** copy namespaces from one namespace container into another + * @param bOverwrite true: overwrite namespaces in target + * false: do not overwrite namespaces in target + * @param bMove true: move namespaces (i.e., delete in source) + * false: copy namespaces (do not modify source) + * @param bFromSource true: use elements from source + * false: use only elements from target + */ +void lcl_copyNamespaces( const Binding::XNameContainer_t& xFrom, + Binding::XNameContainer_t& xTo, + bool bOverwrite ) +{ + OSL_ENSURE( xFrom.is(), "no source" ); + OSL_ENSURE( xTo.is(), "no target" ); + + // iterate over name in source + Sequence<OUString> aNames = xFrom->getElementNames(); + sal_Int32 nNames = aNames.getLength(); + const OUString* pNames = aNames.getConstArray(); + for( sal_Int32 i = 0; i < nNames; i++ ) + { + const OUString& rName = pNames[i]; + + // determine whether to copy the value, and whether to delete + // it in the source: + + bool bInTarget = xTo->hasByName( rName ); + + // we copy: if property is in target, and + // if bOverwrite is set, or when the namespace prefix is free + bool bCopy = bOverwrite || ! bInTarget; + + // and now... ACTION! + if( bCopy ) + { + if( bInTarget ) + xTo->replaceByName( rName, xFrom->getByName( rName ) ); + else + xTo->insertByName( rName, xFrom->getByName( rName ) ); + } + } +} + +// implement get*Namespaces() +// (identical for both variants) +Binding::XNameContainer_t Binding::_getNamespaces() const +{ + XNameContainer_t xNamespaces = new NameContainer<OUString>(); + lcl_copyNamespaces( mxNamespaces, xNamespaces, true ); + + // merge model's with binding's own namespaces + Model* pModel = getModelImpl(); + if( pModel != NULL ) + lcl_copyNamespaces( pModel->getNamespaces(), xNamespaces, false ); + + return xNamespaces; +} + +// implement set*Namespaces() +// bBinding = true: setBindingNamespaces, otherwise: setModelNamespaces +void Binding::_setNamespaces( const XNameContainer_t& rNamespaces, + bool bBinding ) +{ + Model* pModel = getModelImpl(); + XNameContainer_t xModelNamespaces = ( pModel != NULL ) + ? pModel->getNamespaces() + : NULL; + OSL_ENSURE( ( pModel != NULL ) == xModelNamespaces.is(), "no model nmsp?"); + + // remove deleted namespaces + lcl_removeOtherNamespaces( rNamespaces, mxNamespaces ); + if( !bBinding && xModelNamespaces.is() ) + lcl_removeOtherNamespaces( rNamespaces, xModelNamespaces ); + + // copy namespaces as appropriate + Sequence<OUString> aNames = rNamespaces->getElementNames(); + sal_Int32 nNames = aNames.getLength(); + const OUString* pNames = aNames.getConstArray(); + for( sal_Int32 i = 0; i < nNames; i++ ) + { + const OUString& rName = pNames[i]; + Any aValue = rNamespaces->getByName( rName ); + + // determine whether the namespace should go into model's or + // into binding's namespaces + bool bLocal = + ! xModelNamespaces.is() + || mxNamespaces->hasByName( rName ) + || ( bBinding + && xModelNamespaces.is() + && xModelNamespaces->hasByName( rName ) ); + + // write namespace into the appropriate namespace container + XNameContainer_t& rWhich = bLocal ? mxNamespaces : xModelNamespaces; + OSL_ENSURE( rWhich.is(), "whoops" ); + if( rWhich->hasByName( rName ) ) + rWhich->replaceByName( rName, aValue ); + else + rWhich->insertByName( rName, aValue ); + + // always 'promote' namespaces from binding to model, if equal + if( xModelNamespaces.is() + && xModelNamespaces->hasByName( rName ) + && mxNamespaces->hasByName( rName ) + && xModelNamespaces->getByName( rName ) == mxNamespaces->getByName( rName ) ) + { + mxNamespaces->removeByName( rName ); + } + } + + // ... done. But we modified the binding! + bindingModified(); +} + +void Binding::_checkBindingID() +{ + if( getModel().is() ) + { + Reference<XNameAccess> xBindings( getModel()->getBindings(), UNO_QUERY_THROW ); + if( msBindingID.getLength() == 0 ) + { + // no binding ID? then make one up! + OUString sIDPrefix = getResource( RID_STR_XFORMS_BINDING_UI_NAME ); + sIDPrefix += String::CreateFromAscii( " " ); + sal_Int32 nNumber = 0; + OUString sName; + do + { + nNumber++; + sName = sIDPrefix + OUString::valueOf( nNumber ); + } + while( xBindings->hasByName( sName ) ); + setBindingID( sName ); + } + } +} + + + + +// +// XValueBinding +// + +Binding::Sequence_Type_t Binding::getSupportedValueTypes() + throw( RuntimeException ) +{ + return Convert::get().getTypes(); +} + +sal_Bool Binding::supportsType( const Type_t& rType ) + throw( RuntimeException ) +{ + return Convert::get().hasType( rType ); +} + +Binding::Any_t Binding::getValue( const Type_t& rType ) + throw( IncompatibleTypesException, + RuntimeException ) +{ + // first, check for model + checkLive(); + + // second, check for type + if( ! supportsType( rType ) ) + throw IncompatibleTypesException( EXCEPT( "type unsupported" ) ); + + // return string value (if present; else return empty Any) + Binding::Any_t result = Any(); + if(maBindingExpression.hasValue()) { + rtl::OUString pathExpr(maBindingExpression.getString()); + Convert &rConvert = Convert::get(); + result = rConvert.toAny(pathExpr,rType); + } + +// return maBindingExpression.hasValue() + // ? Convert::get().toAny( maBindingExpression.getString(), rType ) + // : Any(); + + return result; +} + +void Binding::setValue( const Any_t& aValue ) + throw( IncompatibleTypesException, + InvalidBindingStateException, + NoSupportException, + RuntimeException ) +{ + // first, check for model + checkLive(); + + // check for supported type + if( ! supportsType( aValue.getValueType() ) ) + throw IncompatibleTypesException( EXCEPT( "type unsupported" ) ); + + if( maBindingExpression.hasValue() ) + { + Binding::XNode_t xNode = maBindingExpression.getNode(); + if( xNode.is() ) + { + OUString sValue = Convert::get().toXSD( aValue ); + bool bSuccess = getModelImpl()->setSimpleContent( xNode, sValue ); + if( ! bSuccess ) + throw InvalidBindingStateException( EXCEPT( "can't set value" ) ); + } + else + throw InvalidBindingStateException( EXCEPT( "no suitable node found" ) ); + } + else + throw InvalidBindingStateException( EXCEPT( "no suitable node found" ) ); +} + + +// +// XListEntry Source +// + +sal_Int32 Binding::getListEntryCount() + throw( RuntimeException ) +{ + // first, check for model + checkLive(); + + // return size of node list + return maBindingExpression.getNodeList().size(); +} + +void lcl_getString( const Reference<XNode>& xNode, OUStringBuffer& rBuffer ) +{ + if( xNode->getNodeType() == NodeType_TEXT_NODE + || xNode->getNodeType() == NodeType_ATTRIBUTE_NODE ) + { + rBuffer.append( xNode->getNodeValue() ); + } + else + { + for( Reference<XNode> xChild = xNode->getFirstChild(); + xChild.is(); + xChild = xChild->getNextSibling() ) + { + lcl_getString( xChild, rBuffer ); + } + } +} + +OUString lcl_getString( const Reference<XNode>& xNode ) +{ + OUStringBuffer aBuffer; + lcl_getString( xNode, aBuffer ); + return aBuffer.makeStringAndClear(); +} + +OUString Binding::getListEntry( sal_Int32 nPosition ) + throw( IndexOutOfBoundsException, + RuntimeException ) +{ + // first, check for model + checkLive(); + + // check bounds and return proper item + PathExpression::NodeVector_t aNodes = maBindingExpression.getNodeList(); + if( nPosition < 0 || nPosition >= static_cast<sal_Int32>( aNodes.size() ) ) + throw IndexOutOfBoundsException( EXCEPT("") ); + return lcl_getString( aNodes[ nPosition ] ); +} + +Sequence<OUString> Binding::getAllListEntries() + throw( RuntimeException ) +{ + // first, check for model + checkLive(); + + // create sequence of string values + PathExpression::NodeVector_t aNodes = maBindingExpression.getNodeList(); + Sequence<OUString> aSequence( aNodes.size() ); + OUString* pSequence = aSequence.getArray(); + for( sal_Int32 n = 0; n < aSequence.getLength(); n++ ) + { + pSequence[n] = lcl_getString( aNodes[n] ); + } + + return aSequence; +} + +void Binding::addListEntryListener( const XListEntryListener_t& xListener ) + throw( NullPointerException, + RuntimeException ) +{ + OSL_ENSURE( xListener.is(), "need listener!" ); + if( ::std::find( maListEntryListeners.begin(), + maListEntryListeners.end(), + xListener) + == maListEntryListeners.end() ) + maListEntryListeners.push_back( xListener ); +} + +void Binding::removeListEntryListener( const XListEntryListener_t& xListener ) + throw( NullPointerException, + RuntimeException ) +{ + XListEntryListeners_t::iterator aIter = + ::std::find( maListEntryListeners.begin(), maListEntryListeners.end(), + xListener ); + if( aIter != maListEntryListeners.end() ) + maListEntryListeners.erase( aIter ); +} + + +// +// XValidator +// + +sal_Bool Binding::isValid( const Any_t& ) + throw( RuntimeException ) +{ + // first, check for model + checkLive(); + + // ignore value; determine validate only on current data + return isValid(); +} + +rtl::OUString Binding::explainInvalid( + const Any_t& /*Value*/ ) + throw( RuntimeException ) +{ + // first, check for model + checkLive(); + + // ignore value; determine explanation only on current data + return explainInvalid(); +} + +void Binding::addValidityConstraintListener( + const XValidityConstraintListener_t& xListener ) + throw( NullPointerException, + RuntimeException ) +{ + OSL_ENSURE( xListener.is(), "need listener!" ); + if( ::std::find(maValidityListeners.begin(), maValidityListeners.end(), xListener) + == maValidityListeners.end() ) + maValidityListeners.push_back( xListener ); +} + +void Binding::removeValidityConstraintListener( + const XValidityConstraintListener_t& xListener ) + throw( NullPointerException, + RuntimeException ) +{ + XValidityConstraintListeners_t::iterator aIter = + ::std::find( maValidityListeners.begin(), maValidityListeners.end(), + xListener ); + if( aIter != maValidityListeners.end() ) + maValidityListeners.erase( aIter ); +} + + + +// +// xml::dom::event::XEventListener +// + +void Binding::handleEvent( const XEvent_t& xEvent ) + throw( RuntimeException ) +{ + OUString sType(xEvent->getType()); + //OUString sEventMIPChanged(RTL_CONSTASCII_USTRINGPARAM("xforms-generic")); + //if(sType.equals(sEventMIPChanged)) { + if(!sType.compareToAscii("xforms-generic")) { + + // the modification of the 'mnDeferModifyNotifications'-member + // is necessary to prevent infinite notication looping. + // This can happend in case the binding which caused + // the notification chain is listening to those events + // as well... + bool bPreserveValueModified = mbValueModified; + mnDeferModifyNotifications++; + valueModified(); + --mnDeferModifyNotifications; + mbValueModified = bPreserveValueModified; + return; + } + + // if we're a dynamic binding, we better re-bind, too! + bind( false ); + + // our value was maybe modified + valueModified(); +} + + +// +// lang::XUnoTunnel +// + +sal_Int64 Binding::getSomething( const IntSequence_t& xId ) + throw( RuntimeException ) +{ + return reinterpret_cast<sal_Int64>( ( xId == getUnoTunnelID() ) ? this : NULL ); +} + +// +// XCloneable +// + +Binding::XCloneable_t SAL_CALL Binding::createClone() + throw( RuntimeException ) +{ + Reference< XPropertySet > xClone; + + Model* pModel = getModelImpl(); + if ( pModel ) + xClone = pModel->cloneBinding( this ); + else + { + xClone = new Binding; + copy( this, xClone ); + } + return XCloneable_t( xClone, UNO_QUERY ); +} + +// +// property set implementations +// + +#define REGISTER_PROPERTY( property, type ) \ + registerProperty( PROPERTY( property, type ), \ + new DirectPropertyAccessor< Binding, type >( this, &Binding::set##property, &Binding::get##property ) ); + +#define REGISTER_PROPERTY_RO( property, type ) \ + registerProperty( PROPERTY_RO( property, type ), \ + new DirectPropertyAccessor< Binding, type >( this, NULL, &Binding::get##property ) ); + +#define REGISTER_BOOL_PROPERTY_RO( property ) \ + registerProperty( PROPERTY_RO( property, sal_Bool ), \ + new BooleanPropertyAccessor< Binding, bool >( this, NULL, &Binding::get##property ) ); + +void Binding::initializePropertySet() +{ + REGISTER_PROPERTY ( BindingID, OUString ); + REGISTER_PROPERTY ( BindingExpression, OUString ); + REGISTER_PROPERTY_RO ( Model, Model_t ); + REGISTER_PROPERTY ( BindingNamespaces, XNameContainer_t ); + REGISTER_PROPERTY ( ModelNamespaces, XNameContainer_t ); + REGISTER_PROPERTY_RO ( ModelID, OUString ); + REGISTER_PROPERTY ( ReadonlyExpression, OUString ); + REGISTER_PROPERTY ( RelevantExpression, OUString ); + REGISTER_PROPERTY ( RequiredExpression, OUString ); + REGISTER_PROPERTY ( ConstraintExpression, OUString ); + REGISTER_PROPERTY ( CalculateExpression, OUString ); + REGISTER_PROPERTY ( Type, OUString ); + REGISTER_PROPERTY_RO ( ReadOnly, bool ); + REGISTER_PROPERTY_RO ( Relevant, bool ); + REGISTER_BOOL_PROPERTY_RO( ExternalData ); + + initializePropertyValueCache( HANDLE_ReadOnly ); + initializePropertyValueCache( HANDLE_Relevant ); + initializePropertyValueCache( HANDLE_ExternalData ); +} + +void Binding::addModifyListener( + const XModifyListener_t& xListener ) + throw( RuntimeException ) +{ + OSL_ENSURE( xListener.is(), "need listener!" ); + if( ::std::find( maModifyListeners.begin(), maModifyListeners.end(), xListener ) + == maModifyListeners.end() ) + maModifyListeners.push_back( xListener ); + + // HACK: currently, we have to 'push' some MIPs to the control + // (read-only, relevant, etc.) To enable this, we need to update + // the control at least once when it registers here. + valueModified(); +} + +void Binding::removeModifyListener( + const XModifyListener_t& xListener ) + throw( RuntimeException ) +{ + ModifyListeners_t::iterator aIter = + ::std::find( maModifyListeners.begin(), maModifyListeners.end(), xListener ); + if( aIter != maModifyListeners.end() ) + maModifyListeners.erase( aIter ); +} + + + + +rtl::OUString Binding::getName() + throw( RuntimeException ) +{ + return getBindingID(); +} + +void SAL_CALL Binding::setName( const rtl::OUString& rName ) + throw( RuntimeException ) +{ + // use the XPropertySet methods, so the change in the name is notified to the + // property listeners + setFastPropertyValue( HANDLE_BindingID, makeAny( rName ) ); +} |