1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
21 #include "binding.hxx"
24 #include "unohelper.hxx"
25 #include "NameContainer.hxx"
26 #include "evaluationcontext.hxx"
27 #include "convert.hxx"
28 #include "resourcehelper.hxx"
29 #include "xmlhelper.hxx"
30 #include "xformsevent.hxx"
31 #include <strings.hrc>
33 #include <rtl/ustrbuf.hxx>
34 #include <o3tl/safeint.hxx>
35 #include <osl/diagnose.h>
37 #include <comphelper/diagnose_ex.hxx>
42 #include <com/sun/star/form/binding/IncompatibleTypesException.hpp>
43 #include <com/sun/star/form/binding/InvalidBindingStateException.hpp>
44 #include <com/sun/star/uno/Any.hxx>
45 #include <com/sun/star/xml/dom/XNodeList.hpp>
46 #include <com/sun/star/xml/dom/XNode.hpp>
47 #include <com/sun/star/xml/dom/XDocument.hpp>
48 #include <com/sun/star/xml/dom/XElement.hpp>
49 #include <com/sun/star/xml/dom/NodeType.hpp>
50 #include <com/sun/star/xml/dom/events/XEventTarget.hpp>
51 #include <com/sun/star/xml/dom/events/XEventListener.hpp>
52 #include <com/sun/star/lang/XUnoTunnel.hpp>
53 #include <com/sun/star/lang/IndexOutOfBoundsException.hpp>
54 #include <com/sun/star/container/XNameContainer.hpp>
56 #include <comphelper/servicehelper.hxx>
58 using namespace com::sun::star::xml::xpath
;
59 using namespace com::sun::star::xml::dom::events
;
62 using xforms::Binding
;
65 using xforms::getResource
;
66 using xforms::EvaluationContext
;
67 using com::sun::star::beans::XPropertySet
;
68 using com::sun::star::container::XNameAccess
;
69 using com::sun::star::form::binding::IncompatibleTypesException
;
70 using com::sun::star::form::binding::InvalidBindingStateException
;
71 using com::sun::star::form::binding::XValueBinding
;
72 using com::sun::star::lang::EventObject
;
73 using com::sun::star::lang::IndexOutOfBoundsException
;
74 using com::sun::star::uno::Any
;
75 using com::sun::star::uno::Reference
;
76 using com::sun::star::uno::RuntimeException
;
77 using com::sun::star::uno::Sequence
;
78 using com::sun::star::uno::UNO_QUERY
;
79 using com::sun::star::uno::UNO_QUERY_THROW
;
80 using com::sun::star::uno::XInterface
;
81 using com::sun::star::uno::Exception
;
82 using com::sun::star::util::XModifyListener
;
83 using com::sun::star::xforms::XDataTypeRepository
;
84 using com::sun::star::xml::dom::NodeType_ATTRIBUTE_NODE
;
85 using com::sun::star::xml::dom::NodeType_TEXT_NODE
;
86 using com::sun::star::xml::dom::XNode
;
87 using com::sun::star::xml::dom::XNodeList
;
88 using com::sun::star::xml::dom::events::XEventListener
;
89 using com::sun::star::xml::dom::events::XEventTarget
;
90 using com::sun::star::xsd::XDataType
;
92 #define HANDLE_BindingID 0
93 #define HANDLE_BindingExpression 1
94 #define HANDLE_Model 2
95 #define HANDLE_ModelID 3
96 #define HANDLE_BindingNamespaces 4
97 #define HANDLE_ReadonlyExpression 5
98 #define HANDLE_RelevantExpression 6
99 #define HANDLE_RequiredExpression 7
100 #define HANDLE_ConstraintExpression 8
101 #define HANDLE_CalculateExpression 9
102 #define HANDLE_Type 10
103 #define HANDLE_ReadOnly 11 // from com.sun.star.form.binding.ValueBinding, for interaction with a bound form control
104 #define HANDLE_Relevant 12 // from com.sun.star.form.binding.ValueBinding, for interaction with a bound form control
105 #define HANDLE_ModelNamespaces 13
106 #define HANDLE_ExternalData 14
110 mxNamespaces( new NameContainer
<OUString
>() ),
111 mbInCalculate( false ),
112 mnDeferModifyNotifications( 0 ),
113 mbValueModified( false ),
114 mbBindingModified( false )
116 initializePropertySet();
124 void Binding::_setModel( const css::uno::Reference
<css::xforms::XModel
>& xModel
)
126 PropertyChangeNotifier
aNotifyModelChange( *this, HANDLE_Model
);
127 PropertyChangeNotifier
aNotifyModelIDChange( *this, HANDLE_ModelID
);
129 // prepare binding for removal of old model
130 clear(); // remove all cached data (e.g. XPath evaluation results)
131 css::uno::Reference
<css::container::XNameContainer
> xNamespaces
= getModelNamespaces(); // save namespaces
135 // set namespaces (and move to model, if appropriate)
136 setBindingNamespaces( xNamespaces
);
139 notifyAndCachePropertyValue( HANDLE_ExternalData
);
143 OUString
Binding::getModelID() const
145 Model
* pModel
= getModelImpl();
146 return ( pModel
== nullptr ) ? OUString() : pModel
->getID();
150 css::uno::Reference
<css::xml::dom::XNodeList
> Binding::getXNodeList()
152 // first make sure we are bound
153 if( ! maBindingExpression
.hasValue() )
156 return maBindingExpression
.getXNodeList();
159 bool Binding::isSimpleBinding() const
161 return maBindingExpression
.isSimpleExpression()
162 && maReadonly
.isSimpleExpression()
163 && maRelevant
.isSimpleExpression()
164 && maRequired
.isSimpleExpression()
165 && maConstraint
.isSimpleExpression()
166 && maCalculate
.isSimpleExpression();
169 bool Binding::isSimpleBindingExpression() const
171 return maBindingExpression
.isSimpleExpression();
174 void Binding::update()
176 // clear all expressions (to remove cached node references)
177 maBindingExpression
.clear();
181 maConstraint
.clear();
184 // let's just pretend the binding has been modified -> full rebind()
188 void Binding::deferNotifications( bool bDefer
)
190 mnDeferModifyNotifications
+= ( bDefer
? 1 : -1 );
191 OSL_ENSURE( mnDeferModifyNotifications
>= 0, "you're deferring too much" );
193 if( mnDeferModifyNotifications
== 0 )
195 if( mbBindingModified
)
197 if( mbValueModified
)
201 OSL_ENSURE( ( mnDeferModifyNotifications
> 0 )
202 || ( ! mbBindingModified
&& ! mbValueModified
),
203 "deferred modifications not delivered?" );
206 bool Binding::isValid() const
208 // TODO: determine whether node is suitable, not just whether it exists
209 return maBindingExpression
.getNode().is() &&
211 // tdf#155121, validity rules should be apply when field is required or
212 // when the field is not required but not empty
213 // so if the field is not required and empty, do not check validity
214 (! maMIP
.isRequired() && maBindingExpression
.hasValue()
215 && maBindingExpression
.getString().isEmpty() ) ||
218 maMIP
.isConstraint() &&
219 ( ! maMIP
.isRequired() ||
220 ( maBindingExpression
.hasValue() &&
221 !maBindingExpression
.getString().isEmpty() ) );
224 bool Binding::isUseful() const
227 // 0) we don't have a model
228 // (at least, in this case we shouldn't be removed from the model)
229 // 1) we have a proper name
230 // 2) we have some MIPs,
231 // 3) we are bound to some control
232 // (this can be assumed if some listeners are set)
234 getModelImpl() == nullptr
235 // || msBindingID.getLength() > 0
236 || ! msTypeName
.isEmpty()
237 || ! maReadonly
.isEmptyExpression()
238 || ! maRelevant
.isEmptyExpression()
239 || ! maRequired
.isEmptyExpression()
240 || ! maConstraint
.isEmptyExpression()
241 || ! maCalculate
.isEmptyExpression()
242 || ! maModifyListeners
.empty()
243 || ! maListEntryListeners
.empty()
244 || ! maValidityListeners
.empty();
249 OUString
Binding::explainInvalid()
252 if( ! maBindingExpression
.getNode().is() )
254 sReason
= ( maBindingExpression
.getExpression().isEmpty() )
255 ? getResource( RID_STR_XFORMS_NO_BINDING_EXPRESSION
)
256 : getResource( RID_STR_XFORMS_INVALID_BINDING_EXPRESSION
);
258 else if( ! isValid_DataType() )
260 sReason
= explainInvalid_DataType();
261 if( sReason
.isEmpty() )
263 // no explanation given by data type? Then give generic message
264 sReason
= getResource( RID_STR_XFORMS_INVALID_VALUE
,
265 maMIP
.getTypeName() );
268 else if( ! maMIP
.isConstraint() )
270 sReason
= maMIP
.getConstraintExplanation();
272 else if( maMIP
.isRequired() && maBindingExpression
.hasValue() &&
273 maBindingExpression
.getString().isEmpty() )
275 sReason
= getResource( RID_STR_XFORMS_REQUIRED
);
277 // else: no explanation given; should only happen if data is valid
279 OSL_ENSURE( sReason
.isEmpty() == isValid(),
280 "invalid data should have an explanation!" );
286 EvaluationContext
Binding::getEvaluationContext() const
288 OSL_ENSURE( getModelImpl() != nullptr, "need model impl" );
289 EvaluationContext aContext
= getModelImpl()->getEvaluationContext();
290 aContext
.mxNamespaces
= getBindingNamespaces();
294 ::std::vector
<EvaluationContext
> Binding::getMIPEvaluationContexts()
296 OSL_ENSURE( getModelImpl() != nullptr, "need model impl" );
298 // bind (in case we were not bound before)
300 return _getMIPEvaluationContexts();
304 css::uno::Sequence
<sal_Int8
> Binding::getUnoTunnelId()
306 static const comphelper::UnoIdInit aImplementationId
;
307 return aImplementationId
.getSeq();
311 void Binding::setBindingID( const OUString
& sBindingID
)
313 msBindingID
= sBindingID
;
316 OUString
Binding::getBindingExpression() const
318 return maBindingExpression
.getExpression();
321 void Binding::setBindingExpression( const OUString
& sBindingExpression
)
323 maBindingExpression
.setExpression( sBindingExpression
);
327 OUString
Binding::getReadonlyExpression() const
329 return maReadonly
.getExpression();
332 void Binding::setReadonlyExpression( const OUString
& sReadonly
)
334 maReadonly
.setExpression( sReadonly
);
338 OUString
Binding::getRelevantExpression() const
340 return maRelevant
.getExpression();
343 void Binding::setRelevantExpression( const OUString
& sRelevant
)
345 maRelevant
.setExpression( sRelevant
);
349 OUString
Binding::getRequiredExpression() const
351 return maRequired
.getExpression();
354 void Binding::setRequiredExpression( const OUString
& sRequired
)
356 maRequired
.setExpression( sRequired
);
360 OUString
Binding::getConstraintExpression() const
362 return maConstraint
.getExpression();
365 void Binding::setConstraintExpression( const OUString
& sConstraint
)
367 maConstraint
.setExpression( sConstraint
);
368 msExplainConstraint
= getResource( RID_STR_XFORMS_INVALID_CONSTRAINT
,
371 // TODO: This should only re-evaluate the constraint, and notify
372 // the validity constraint listeners; instead we currently pretend
373 // the entire binding was notified, which does a little too much.
377 OUString
Binding::getCalculateExpression() const
379 return maCalculate
.getExpression();
382 void Binding::setCalculateExpression( const OUString
& sCalculate
)
384 maCalculate
.setExpression( sCalculate
);
389 void Binding::setType( const OUString
& sTypeName
)
391 msTypeName
= sTypeName
;
395 void Binding::setBindingNamespaces( const css::uno::Reference
<css::container::XNameContainer
>& rNamespaces
)
397 _setNamespaces( rNamespaces
, true );
400 css::uno::Reference
<css::container::XNameContainer
> Binding::getModelNamespaces() const
402 return _getNamespaces();
405 void Binding::setModelNamespaces( const css::uno::Reference
<css::container::XNameContainer
>& rNamespaces
)
407 _setNamespaces( rNamespaces
, false );
410 bool Binding::getReadOnly() const
412 return maMIP
.isReadonly();
415 bool Binding::getRelevant() const
417 return maMIP
.isRelevant();
420 bool Binding::getExternalData() const
422 bool bExternalData
= true;
424 return bExternalData
;
428 Reference
< XPropertySet
> xModelProps( mxModel
, UNO_QUERY_THROW
);
430 xModelProps
->getPropertyValue( "ExternalData" ) >>= bExternalData
);
432 catch( const Exception
& )
434 DBG_UNHANDLED_EXCEPTION("forms.xforms");
436 return bExternalData
;
440 void Binding::checkLive()
443 throw RuntimeException("Binding not initialized", static_cast<XValueBinding
*>(this));
446 bool Binding::isLive() const
448 const Model
* pModel
= getModelImpl();
449 return pModel
&& pModel
->isInitialized();
452 Model
* Binding::getModelImpl() const
454 return dynamic_cast<Model
*>( mxModel
.get() );
457 static void lcl_addListenerToNode( const Reference
<XNode
>& xNode
,
458 const Reference
<XEventListener
>& xListener
)
460 Reference
<XEventTarget
> xTarget( xNode
, UNO_QUERY
);
464 xTarget
->addEventListener( "DOMCharacterDataModified",
466 xTarget
->addEventListener( "DOMCharacterDataModified",
468 xTarget
->addEventListener( "DOMAttrModified",
470 xTarget
->addEventListener( "DOMAttrModified",
472 xTarget
->addEventListener( "xforms-generic",
476 static void lcl_removeListenerFromNode( const Reference
<XNode
>& xNode
,
477 const Reference
<XEventListener
>& xListener
)
479 Reference
<XEventTarget
> xTarget( xNode
, UNO_QUERY
);
483 xTarget
->removeEventListener( "DOMCharacterDataModified",
485 xTarget
->removeEventListener( "DOMCharacterDataModified",
487 xTarget
->removeEventListener( "DOMAttrModified",
489 xTarget
->removeEventListener( "DOMAttrModified",
491 xTarget
->removeEventListener( "xforms-generic",
495 ::std::vector
<EvaluationContext
> Binding::_getMIPEvaluationContexts() const
497 OSL_ENSURE( getModelImpl() != nullptr, "need model impl" );
499 // iterate over nodes of bind expression and create
500 // EvaluationContext for each
501 PathExpression::NodeVector_t aNodes
= maBindingExpression
.getNodeList();
502 ::std::vector
<EvaluationContext
> aVector
;
503 for (auto const& node
: aNodes
)
505 OSL_ENSURE( node
.is(), "no node?" );
507 // create proper evaluation context for this MIP
508 aVector
.emplace_back( node
, getModel(), getBindingNamespaces() );
513 void Binding::bind( bool bForceRebind
)
516 throw RuntimeException("Binding has no Model", static_cast<XValueBinding
*>(this));
518 // bind() will evaluate this binding as follows:
519 // 1) evaluate the binding expression
520 // 1b) if necessary, create node according to 'lazy author' rules
521 // 2) register suitable listeners on the instance (and remove old ones)
522 // 3) remove old MIPs defined by this binding
523 // 4) for every node in the binding nodeset do:
524 // 1) create proper evaluation context for this MIP
525 // 2) evaluate calculate expression (and push value into instance)
526 // 3) evaluate remaining MIPs
527 // 4) evaluate the locally defined MIPs, and push them to the model
530 // 1) evaluate the binding expression
531 EvaluationContext aContext
= getEvaluationContext();
532 maBindingExpression
.evaluate( aContext
);
533 if( ! maBindingExpression
.getNode().is() )
535 // 1b) create node (if valid element name)
536 if( isValidQName( maBindingExpression
.getExpression(),
537 aContext
.mxNamespaces
) )
539 aContext
.mxContextNode
->appendChild(
540 aContext
.mxContextNode
->getOwnerDocument()->createElement(
541 maBindingExpression
.getExpression() ) );
542 maBindingExpression
.evaluate( aContext
);
543 OSL_ENSURE( maBindingExpression
.getNode().is(),
544 "we should bind to the newly inserted node!" );
547 PathExpression::NodeVector_t aNodes
= maBindingExpression
.getNodeList();
549 // 2) register suitable listeners on the instance (and remove old ones)
550 if( maEventNodes
.empty() || bForceRebind
)
552 for (auto const& eventNode
: maEventNodes
)
553 lcl_removeListenerFromNode( eventNode
, this );
554 maEventNodes
.clear();
555 if( isSimpleBinding() )
556 maEventNodes
.insert(maEventNodes
.end(), aNodes
.begin(), aNodes
.end());
558 maEventNodes
.emplace_back( aContext
.mxContextNode
->getOwnerDocument(),
560 for (auto const& eventNode
: maEventNodes
)
561 lcl_addListenerToNode( eventNode
, this );
564 // 3) remove old MIPs defined by this binding
565 Model
* pModel
= getModelImpl();
566 OSL_ENSURE( pModel
!= nullptr, "need model" );
567 pModel
->removeMIPs( this );
569 // 4) calculate all MIPs
570 ::std::vector
<EvaluationContext
> aMIPContexts
= _getMIPEvaluationContexts();
571 for (auto & context
: aMIPContexts
)
573 EvaluationContext
& rContext
= context
;
575 // evaluate calculate expression (and push value into instance)
576 // (prevent recursion using mbInCalculate
577 if( ! maCalculate
.isEmptyExpression() )
579 if( ! mbInCalculate
)
581 mbInCalculate
= true;
582 maCalculate
.evaluate( rContext
);
583 pModel
->setSimpleContent( rContext
.mxContextNode
,
584 maCalculate
.getString() );
585 mbInCalculate
= false;
589 // now evaluate remaining MIPs in the appropriate context
590 maReadonly
.evaluate( rContext
);
591 maRelevant
.evaluate( rContext
);
592 maRequired
.evaluate( rContext
);
593 maConstraint
.evaluate( rContext
);
594 // type is static; does not need updating
596 // evaluate the locally defined MIPs, and push them to the model
597 pModel
->addMIP( this, rContext
.mxContextNode
, getLocalMIP() );
602 // helper for Binding::valueModified
603 static void lcl_modified( const css::uno::Reference
<css::util::XModifyListener
>& xListener
,
604 const Reference
<XInterface
>& xSource
)
606 OSL_ENSURE( xListener
.is(), "no listener?" );
607 xListener
->modified( EventObject( xSource
) );
610 // helper for Binding::valueModified
611 static void lcl_listentry( const css::uno::Reference
<css::form::binding::XListEntryListener
>& xListener
,
612 const Reference
<XInterface
>& xSource
)
614 OSL_ENSURE( xListener
.is(), "no listener?" );
615 // TODO: send fine granular events
616 xListener
->allEntriesChanged( EventObject( xSource
) );
619 // helper for Binding::valueModified
620 static void lcl_validate( const css::uno::Reference
<css::form::validation::XValidityConstraintListener
>& xListener
,
621 const Reference
<XInterface
>& xSource
)
623 OSL_ENSURE( xListener
.is(), "no listener?" );
624 xListener
->validityConstraintChanged( EventObject( xSource
) );
628 void Binding::valueModified()
630 // defer notifications, if so desired
631 if( mnDeferModifyNotifications
> 0 )
633 mbValueModified
= true;
636 mbValueModified
= false;
638 // query MIP used by our first node (also note validity)
639 Reference
<XNode
> xNode
= maBindingExpression
.getNode();
640 maMIP
= getModelImpl()->queryMIP( xNode
);
642 // distribute MIPs _used_ by this binding
645 notifyAndCachePropertyValue( HANDLE_ReadOnly
);
646 notifyAndCachePropertyValue( HANDLE_Relevant
);
649 // iterate over _value_ listeners and send each a modified signal,
650 // using this object as source (will also update validity, because
651 // control will query once the value has changed)
652 Reference
<XInterface
> xSource
= static_cast<XPropertySet
*>( this );
653 ::std::for_each( maModifyListeners
.begin(),
654 maModifyListeners
.end(),
655 ::std::bind( lcl_modified
, std::placeholders::_1
, xSource
) );
656 ::std::for_each( maListEntryListeners
.begin(),
657 maListEntryListeners
.end(),
658 ::std::bind( lcl_listentry
, std::placeholders::_1
, xSource
) );
659 ::std::for_each( maValidityListeners
.begin(),
660 maValidityListeners
.end(),
661 ::std::bind( lcl_validate
, std::placeholders::_1
, xSource
) );
663 // now distribute MIPs to children
665 distributeMIP( xNode
->getFirstChild() );
668 void Binding::distributeMIP( const css::uno::Reference
<css::xml::dom::XNode
> & rxNode
) {
670 rtl::Reference
<css::xforms::XFormsEventConcrete
> pEvent
= new css::xforms::XFormsEventConcrete
;
671 pEvent
->initXFormsEvent("xforms-generic", true, false);
673 // naive depth-first traversal
674 css::uno::Reference
<css::xml::dom::XNode
> xNode( rxNode
);
677 // notifications should be triggered at the
678 // leaf nodes first, bubbling upwards the hierarchy.
679 css::uno::Reference
<css::xml::dom::XNode
> child(xNode
->getFirstChild());
681 distributeMIP(child
);
683 // we're standing at a particular node somewhere
684 // below the one which changed a property (MIP).
685 // bindings which are listening at this node will receive
686 // a notification message about what exactly happened.
687 Reference
< XEventTarget
> target(xNode
,UNO_QUERY
);
688 target
->dispatchEvent(pEvent
);
690 xNode
= xNode
->getNextSibling();
694 void Binding::bindingModified()
696 // defer notifications, if so desired
697 if( mnDeferModifyNotifications
> 0 )
699 mbBindingModified
= true;
702 mbBindingModified
= false;
704 // rebind (if live); then call valueModified
705 // A binding should be inert until its model is fully constructed.
714 MIP
Binding::getLocalMIP() const
718 if( maReadonly
.hasValue() )
719 aMIP
.setReadonly( maReadonly
.getBool() );
720 if( maRelevant
.hasValue() )
721 aMIP
.setRelevant( maRelevant
.getBool( true ) );
722 if( maRequired
.hasValue() )
723 aMIP
.setRequired( maRequired
.getBool() );
724 if( maConstraint
.hasValue() )
726 aMIP
.setConstraint( maConstraint
.getBool( true ) );
727 if( ! aMIP
.isConstraint() )
728 aMIP
.setConstraintExplanation( msExplainConstraint
);
730 if( !msTypeName
.isEmpty() )
731 aMIP
.setTypeName( msTypeName
);
733 // calculate: only handle presence of calculate; value set elsewhere
734 aMIP
.setHasCalculate( !maCalculate
.isEmptyExpression() );
739 css::uno::Reference
<css::xsd::XDataType
> Binding::getDataType() const
741 OSL_ENSURE( getModel().is(), "need model" );
742 OSL_ENSURE( getModel()->getDataTypeRepository().is(), "need types" );
744 Reference
<XDataTypeRepository
> xRepository
=
745 getModel()->getDataTypeRepository();
746 OUString sTypeName
= maMIP
.getTypeName();
748 return ( xRepository
.is() && xRepository
->hasByName( sTypeName
) )
749 ? Reference
<XDataType
>( xRepository
->getByName( sTypeName
), UNO_QUERY
)
750 : Reference
<XDataType
>( nullptr );
753 bool Binding::isValid_DataType() const
755 Reference
<XDataType
> xDataType
= getDataType();
756 return !xDataType
.is()
757 || xDataType
->validate( maBindingExpression
.getString() );
760 OUString
Binding::explainInvalid_DataType()
762 Reference
<XDataType
> xDataType
= getDataType();
763 return xDataType
.is()
764 ? xDataType
->explainInvalid( maBindingExpression
.getString() )
768 void Binding::clear()
770 // remove MIPs contributed by this binding
771 Model
* pModel
= getModelImpl();
772 if( pModel
!= nullptr )
773 pModel
->removeMIPs( this );
775 // remove all references
776 for (auto const& eventNode
: maEventNodes
)
777 lcl_removeListenerFromNode( eventNode
, this );
778 maEventNodes
.clear();
781 maBindingExpression
.clear();
785 maConstraint
.clear();
788 // TODO: what about our listeners?
792 static void lcl_removeOtherNamespaces( const css::uno::Reference
<css::container::XNameContainer
>& xFrom
,
793 css::uno::Reference
<css::container::XNameContainer
> const & xTo
)
795 OSL_ENSURE( xFrom
.is(), "no source" );
796 OSL_ENSURE( xTo
.is(), "no target" );
798 // iterate over name in source
799 Sequence
<OUString
> aNames
= xTo
->getElementNames();
800 sal_Int32 nNames
= aNames
.getLength();
801 const OUString
* pNames
= aNames
.getConstArray();
802 for( sal_Int32 i
= 0; i
< nNames
; i
++ )
804 const OUString
& rName
= pNames
[i
];
806 if( ! xFrom
->hasByName( rName
) )
807 xTo
->removeByName( rName
);
811 /** copy namespaces from one namespace container into another
812 * @param bOverwrite true: overwrite namespaces in target
813 * false: do not overwrite namespaces in target
814 * @param bMove true: move namespaces (i.e., delete in source)
815 * false: copy namespaces (do not modify source)
816 * @param bFromSource true: use elements from source
817 * false: use only elements from target
819 static void lcl_copyNamespaces( const css::uno::Reference
<css::container::XNameContainer
>& xFrom
,
820 css::uno::Reference
<css::container::XNameContainer
> const & xTo
,
823 OSL_ENSURE( xFrom
.is(), "no source" );
824 OSL_ENSURE( xTo
.is(), "no target" );
826 // iterate over name in source
827 Sequence
<OUString
> aNames
= xFrom
->getElementNames();
828 sal_Int32 nNames
= aNames
.getLength();
829 const OUString
* pNames
= aNames
.getConstArray();
830 for( sal_Int32 i
= 0; i
< nNames
; i
++ )
832 const OUString
& rName
= pNames
[i
];
834 // determine whether to copy the value, and whether to delete
837 bool bInTarget
= xTo
->hasByName( rName
);
839 // we copy: if property is in target, and
840 // if bOverwrite is set, or when the namespace prefix is free
841 bool bCopy
= bOverwrite
|| ! bInTarget
;
843 // and now... ACTION!
847 xTo
->replaceByName( rName
, xFrom
->getByName( rName
) );
849 xTo
->insertByName( rName
, xFrom
->getByName( rName
) );
854 // implement get*Namespaces()
855 // (identical for both variants)
856 css::uno::Reference
<css::container::XNameContainer
> Binding::_getNamespaces() const
858 css::uno::Reference
<css::container::XNameContainer
> xNamespaces
= new NameContainer
<OUString
>();
859 lcl_copyNamespaces( mxNamespaces
, xNamespaces
, true );
861 // merge model's with binding's own namespaces
862 Model
* pModel
= getModelImpl();
863 if( pModel
!= nullptr )
864 lcl_copyNamespaces( pModel
->getNamespaces(), xNamespaces
, false );
869 // implement set*Namespaces()
870 // bBinding = true: setBindingNamespaces, otherwise: setModelNamespaces
871 void Binding::_setNamespaces( const css::uno::Reference
<css::container::XNameContainer
>& rNamespaces
,
874 Model
* pModel
= getModelImpl();
875 css::uno::Reference
<css::container::XNameContainer
> xModelNamespaces
= ( pModel
!= nullptr )
876 ? pModel
->getNamespaces()
878 OSL_ENSURE( ( pModel
!= nullptr ) == xModelNamespaces
.is(), "no model nmsp?");
880 // remove deleted namespaces
881 lcl_removeOtherNamespaces( rNamespaces
, mxNamespaces
);
882 if( !bBinding
&& xModelNamespaces
.is() )
883 lcl_removeOtherNamespaces( rNamespaces
, xModelNamespaces
);
885 // copy namespaces as appropriate
886 Sequence
<OUString
> aNames
= rNamespaces
->getElementNames();
887 sal_Int32 nNames
= aNames
.getLength();
888 const OUString
* pNames
= aNames
.getConstArray();
889 for( sal_Int32 i
= 0; i
< nNames
; i
++ )
891 const OUString
& rName
= pNames
[i
];
892 Any aValue
= rNamespaces
->getByName( rName
);
894 // determine whether the namespace should go into model's or
895 // into binding's namespaces
897 ! xModelNamespaces
.is()
898 || mxNamespaces
->hasByName( rName
)
900 && xModelNamespaces
.is()
901 && xModelNamespaces
->hasByName( rName
) );
903 // write namespace into the appropriate namespace container
904 css::uno::Reference
<css::container::XNameContainer
>& rWhich
= bLocal
? mxNamespaces
: xModelNamespaces
;
905 OSL_ENSURE( rWhich
.is(), "whoops" );
906 if( rWhich
->hasByName( rName
) )
907 rWhich
->replaceByName( rName
, aValue
);
909 rWhich
->insertByName( rName
, aValue
);
911 // always 'promote' namespaces from binding to model, if equal
912 if( xModelNamespaces
.is()
913 && xModelNamespaces
->hasByName( rName
)
914 && mxNamespaces
->hasByName( rName
)
915 && xModelNamespaces
->getByName( rName
) == mxNamespaces
->getByName( rName
) )
917 mxNamespaces
->removeByName( rName
);
921 // ... done. But we modified the binding!
925 void Binding::_checkBindingID()
927 if( !getModel().is() )
930 Reference
<XNameAccess
> xBindings( getModel()->getBindings(), UNO_QUERY_THROW
);
931 if( !msBindingID
.isEmpty() )
934 // no binding ID? then make one up!
935 OUString sIDPrefix
= getResource( RID_STR_XFORMS_BINDING_UI_NAME
) + " ";
936 sal_Int32 nNumber
= 0;
941 sName
= sIDPrefix
+ OUString::number( nNumber
);
943 while( xBindings
->hasByName( sName
) );
944 setBindingID( sName
);
951 css::uno::Sequence
<css::uno::Type
> Binding::getSupportedValueTypes()
953 return Convert::get().getTypes();
956 sal_Bool
Binding::supportsType( const css::uno::Type
& rType
)
958 return Convert::get().hasType( rType
);
961 css::uno::Any
Binding::getValue( const css::uno::Type
& rType
)
963 // first, check for model
966 // second, check for type
967 if( ! supportsType( rType
) )
968 throw IncompatibleTypesException("type unsupported", static_cast<XValueBinding
*>(this));
970 // return string value (if present; else return empty Any)
971 css::uno::Any result
;
972 if(maBindingExpression
.hasValue()) {
973 OUString
pathExpr(maBindingExpression
.getString());
974 Convert
&rConvert
= Convert::get();
975 result
= rConvert
.toAny(pathExpr
,rType
);
981 void Binding::setValue( const css::uno::Any
& aValue
)
983 // first, check for model
986 // check for supported type
987 if( ! supportsType( aValue
.getValueType() ) )
988 throw IncompatibleTypesException("type unsupported", static_cast<XValueBinding
*>(this));
990 if( !maBindingExpression
.hasValue() )
991 throw InvalidBindingStateException("no suitable node found", static_cast<XValueBinding
*>(this));
993 css::uno::Reference
<css::xml::dom::XNode
> xNode
= maBindingExpression
.getNode();
995 throw InvalidBindingStateException("no suitable node found", static_cast<XValueBinding
*>(this));
997 OUString sValue
= Convert::get().toXSD( aValue
);
998 bool bSuccess
= getModelImpl()->setSimpleContent( xNode
, sValue
);
1000 throw InvalidBindingStateException("can't set value", static_cast<XValueBinding
*>(this));
1006 // XListEntry Source
1009 sal_Int32
Binding::getListEntryCount()
1011 // first, check for model
1014 // return size of node list
1015 return maBindingExpression
.getNodeList().size();
1018 static void lcl_getString( const Reference
<XNode
>& xNode
, OUStringBuffer
& rBuffer
)
1020 if( xNode
->getNodeType() == NodeType_TEXT_NODE
1021 || xNode
->getNodeType() == NodeType_ATTRIBUTE_NODE
)
1023 rBuffer
.append( xNode
->getNodeValue() );
1027 for( Reference
<XNode
> xChild
= xNode
->getFirstChild();
1029 xChild
= xChild
->getNextSibling() )
1031 lcl_getString( xChild
, rBuffer
);
1036 static OUString
lcl_getString( const Reference
<XNode
>& xNode
)
1038 OUStringBuffer aBuffer
;
1039 lcl_getString( xNode
, aBuffer
);
1040 return aBuffer
.makeStringAndClear();
1043 OUString
Binding::getListEntry( sal_Int32 nPosition
)
1045 // first, check for model
1048 // check bounds and return proper item
1049 PathExpression::NodeVector_t aNodes
= maBindingExpression
.getNodeList();
1050 if( nPosition
< 0 || o3tl::make_unsigned(nPosition
) >= aNodes
.size() )
1051 throw IndexOutOfBoundsException("", static_cast<XValueBinding
*>(this));
1052 return lcl_getString( aNodes
[ nPosition
] );
1055 Sequence
<OUString
> Binding::getAllListEntries()
1057 // first, check for model
1060 // create sequence of string values
1061 PathExpression::NodeVector_t aNodes
= maBindingExpression
.getNodeList();
1062 Sequence
<OUString
> aSequence( aNodes
.size() );
1063 OUString
* pSequence
= aSequence
.getArray();
1064 for( sal_Int32 n
= 0; n
< aSequence
.getLength(); n
++ )
1066 pSequence
[n
] = lcl_getString( aNodes
[n
] );
1072 void Binding::addListEntryListener( const css::uno::Reference
<css::form::binding::XListEntryListener
>& xListener
)
1074 OSL_ENSURE( xListener
.is(), "need listener!" );
1075 if( ::std::find( maListEntryListeners
.begin(),
1076 maListEntryListeners
.end(),
1078 == maListEntryListeners
.end() )
1079 maListEntryListeners
.push_back( xListener
);
1082 void Binding::removeListEntryListener( const css::uno::Reference
<css::form::binding::XListEntryListener
>& xListener
)
1084 XListEntryListeners_t::iterator aIter
=
1085 ::std::find( maListEntryListeners
.begin(), maListEntryListeners
.end(),
1087 if( aIter
!= maListEntryListeners
.end() )
1088 maListEntryListeners
.erase( aIter
);
1095 sal_Bool
Binding::isValid( const css::uno::Any
& )
1097 // first, check for model
1100 // ignore value; determine validate only on current data
1104 OUString
Binding::explainInvalid(
1105 const css::uno::Any
& /*Value*/ )
1107 // first, check for model
1110 // ignore value; determine explanation only on current data
1111 return explainInvalid();
1114 void Binding::addValidityConstraintListener(
1115 const css::uno::Reference
<css::form::validation::XValidityConstraintListener
>& xListener
)
1117 OSL_ENSURE( xListener
.is(), "need listener!" );
1118 if( ::std::find(maValidityListeners
.begin(), maValidityListeners
.end(), xListener
)
1119 == maValidityListeners
.end() )
1120 maValidityListeners
.push_back( xListener
);
1123 void Binding::removeValidityConstraintListener(
1124 const css::uno::Reference
<css::form::validation::XValidityConstraintListener
>& xListener
)
1126 XValidityConstraintListeners_t::iterator aIter
=
1127 ::std::find( maValidityListeners
.begin(), maValidityListeners
.end(),
1129 if( aIter
!= maValidityListeners
.end() )
1130 maValidityListeners
.erase( aIter
);
1134 // xml::dom::event::XEventListener
1137 void Binding::handleEvent( const css::uno::Reference
<css::xml::dom::events::XEvent
>& xEvent
)
1139 OUString
sType(xEvent
->getType());
1140 //OUString sEventMIPChanged("xforms-generic");
1141 //if(sType.equals(sEventMIPChanged)) {
1142 if(sType
== "xforms-generic") {
1144 // the modification of the 'mnDeferModifyNotifications'-member
1145 // is necessary to prevent infinite notification looping.
1146 // This can happened in case the binding which caused
1147 // the notification chain is listening to those events
1149 bool bPreserveValueModified
= mbValueModified
;
1150 mnDeferModifyNotifications
++;
1152 --mnDeferModifyNotifications
;
1153 mbValueModified
= bPreserveValueModified
;
1157 // if we're a dynamic binding, we better re-bind, too!
1160 // our value was maybe modified
1168 sal_Int64
Binding::getSomething( const css::uno::Sequence
<sal_Int8
>& xId
)
1170 return comphelper::getSomethingImpl(xId
, this);
1177 css::uno::Reference
<css::util::XCloneable
> SAL_CALL
Binding::createClone()
1179 Reference
< XPropertySet
> xClone
;
1181 Model
* pModel
= getModelImpl();
1183 xClone
= pModel
->cloneBinding( this );
1186 xClone
= new Binding
;
1187 copy( this, xClone
);
1189 return css::uno::Reference
<css::util::XCloneable
>( xClone
, UNO_QUERY
);
1193 // property set implementations
1195 void Binding::initializePropertySet()
1197 registerProperty( css::beans::Property("BindingID", HANDLE_BindingID
, cppu::UnoType
<OUString
>::get(), css::beans::PropertyAttribute::BOUND
),
1198 new DirectPropertyAccessor
< Binding
, OUString
>(this, &Binding::setBindingID
, &Binding::getBindingID
));
1200 registerProperty( css::beans::Property("BindingExpression", HANDLE_BindingExpression
, cppu::UnoType
<OUString
>::get(), css::beans::PropertyAttribute::BOUND
),
1201 new DirectPropertyAccessor
< Binding
, OUString
>(this, &Binding::setBindingExpression
, &Binding::getBindingExpression
));
1203 registerProperty( css::beans::Property("Model", HANDLE_Model
, cppu::UnoType
<css::uno::Reference
<css::xforms::XModel
>>::get(), css::beans::PropertyAttribute::BOUND
| css::beans::PropertyAttribute::READONLY
),
1204 new DirectPropertyAccessor
< Binding
, css::uno::Reference
<css::xforms::XModel
> >(this, nullptr, &Binding::getModel
));
1206 registerProperty( css::beans::Property("BindingNamespaces", HANDLE_BindingNamespaces
, cppu::UnoType
<css::uno::Reference
<css::container::XNameContainer
>>::get(), css::beans::PropertyAttribute::BOUND
),
1207 new DirectPropertyAccessor
< Binding
, css::uno::Reference
<css::container::XNameContainer
> >(this, &Binding::setBindingNamespaces
, &Binding::getBindingNamespaces
));
1209 registerProperty( css::beans::Property("ModelNamespaces", HANDLE_ModelNamespaces
, cppu::UnoType
<css::uno::Reference
<css::container::XNameContainer
>>::get(), css::beans::PropertyAttribute::BOUND
),
1210 new DirectPropertyAccessor
< Binding
, css::uno::Reference
<css::container::XNameContainer
> >(this, &Binding::setModelNamespaces
, &Binding::getModelNamespaces
));
1212 registerProperty( css::beans::Property("ModelID", HANDLE_ModelID
, cppu::UnoType
<OUString
>::get(), css::beans::PropertyAttribute::BOUND
| css::beans::PropertyAttribute::READONLY
),
1213 new DirectPropertyAccessor
< Binding
, OUString
>(this, nullptr, &Binding::getModelID
));
1215 registerProperty( css::beans::Property("ReadonlyExpression", HANDLE_ReadonlyExpression
, cppu::UnoType
<OUString
>::get(), css::beans::PropertyAttribute::BOUND
),
1216 new DirectPropertyAccessor
< Binding
, OUString
>(this, &Binding::setReadonlyExpression
, &Binding::getReadonlyExpression
));
1218 registerProperty( css::beans::Property("RelevantExpression", HANDLE_RelevantExpression
, cppu::UnoType
<OUString
>::get(), css::beans::PropertyAttribute::BOUND
),
1219 new DirectPropertyAccessor
< Binding
, OUString
>(this, &Binding::setRelevantExpression
, &Binding::getRelevantExpression
));
1221 registerProperty( css::beans::Property("RequiredExpression", HANDLE_RequiredExpression
, cppu::UnoType
<OUString
>::get(), css::beans::PropertyAttribute::BOUND
),
1222 new DirectPropertyAccessor
< Binding
, OUString
>(this, &Binding::setRequiredExpression
, &Binding::getRequiredExpression
));
1224 registerProperty( css::beans::Property("ConstraintExpression", HANDLE_ConstraintExpression
, cppu::UnoType
<OUString
>::get(), css::beans::PropertyAttribute::BOUND
),
1225 new DirectPropertyAccessor
< Binding
, OUString
>(this, &Binding::setConstraintExpression
, &Binding::getConstraintExpression
));
1227 registerProperty( css::beans::Property("CalculateExpression", HANDLE_CalculateExpression
, cppu::UnoType
<OUString
>::get(), css::beans::PropertyAttribute::BOUND
),
1228 new DirectPropertyAccessor
< Binding
, OUString
>(this, &Binding::setCalculateExpression
, &Binding::getCalculateExpression
));
1230 registerProperty( css::beans::Property("Type", HANDLE_Type
, cppu::UnoType
<OUString
>::get(), css::beans::PropertyAttribute::BOUND
),
1231 new DirectPropertyAccessor
< Binding
, OUString
>(this, &Binding::setType
, &Binding::getType
));
1233 registerProperty( css::beans::Property("ReadOnly", HANDLE_ReadOnly
, cppu::UnoType
<bool>::get(), css::beans::PropertyAttribute::BOUND
| css::beans::PropertyAttribute::READONLY
),
1234 new DirectPropertyAccessor
< Binding
, bool >(this, nullptr, &Binding::getReadOnly
));
1236 registerProperty( css::beans::Property("Relevant", HANDLE_Relevant
, cppu::UnoType
<bool>::get(), css::beans::PropertyAttribute::BOUND
| css::beans::PropertyAttribute::READONLY
),
1237 new DirectPropertyAccessor
< Binding
, bool >(this, nullptr, &Binding::getRelevant
));
1239 registerProperty( css::beans::Property("ExternalData", HANDLE_ExternalData
, cppu::UnoType
<sal_Bool
>::get(), css::beans::PropertyAttribute::BOUND
| css::beans::PropertyAttribute::READONLY
),
1240 new BooleanPropertyAccessor
< Binding
>(this, nullptr, &Binding::getExternalData
));
1242 initializePropertyValueCache( HANDLE_ReadOnly
);
1243 initializePropertyValueCache( HANDLE_Relevant
);
1244 initializePropertyValueCache( HANDLE_ExternalData
);
1247 void Binding::addModifyListener(
1248 const css::uno::Reference
<css::util::XModifyListener
>& xListener
)
1250 OSL_ENSURE( xListener
.is(), "need listener!" );
1251 if( ::std::find( maModifyListeners
.begin(), maModifyListeners
.end(), xListener
)
1252 == maModifyListeners
.end() )
1253 maModifyListeners
.push_back( xListener
);
1255 // HACK: currently, we have to 'push' some MIPs to the control
1256 // (read-only, relevant, etc.) To enable this, we need to update
1257 // the control at least once when it registers here.
1261 void Binding::removeModifyListener(
1262 const css::uno::Reference
<css::util::XModifyListener
>& xListener
)
1264 ModifyListeners_t::iterator aIter
=
1265 ::std::find( maModifyListeners
.begin(), maModifyListeners
.end(), xListener
);
1266 if( aIter
!= maModifyListeners
.end() )
1267 maModifyListeners
.erase( aIter
);
1271 OUString
Binding::getName()
1273 return getBindingID();
1276 void SAL_CALL
Binding::setName( const OUString
& rName
)
1278 // use the XPropertySet methods, so the change in the name is notified to the
1279 // property listeners
1280 setFastPropertyValue( HANDLE_BindingID
, Any( rName
) );
1283 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */