android: Update app-specific/MIME type icons
[LibreOffice.git] / forms / source / xforms / binding.cxx
blob93952e033ac155d76b941c2174682babdb463402
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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"
23 #include "model.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>
39 #include <algorithm>
40 #include <functional>
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;
61 using std::vector;
62 using xforms::Binding;
63 using xforms::MIP;
64 using xforms::Model;
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
109 Binding::Binding() :
110 mxNamespaces( new NameContainer<OUString>() ),
111 mbInCalculate( false ),
112 mnDeferModifyNotifications( 0 ),
113 mbValueModified( false ),
114 mbBindingModified( false )
116 initializePropertySet();
119 Binding::~Binding()
121 _setModel(nullptr);
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
133 mxModel = xModel;
135 // set namespaces (and move to model, if appropriate)
136 setBindingNamespaces( xNamespaces );
137 _checkBindingID();
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() )
154 bind();
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();
178 maReadonly.clear();
179 maRelevant.clear();
180 maRequired.clear();
181 maConstraint.clear();
182 maCalculate.clear();
184 // let's just pretend the binding has been modified -> full rebind()
185 bindingModified();
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 )
196 bindingModified();
197 if( mbValueModified )
198 valueModified();
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() ) ||
216 isValid_DataType()
217 ) &&
218 maMIP.isConstraint() &&
219 ( ! maMIP.isRequired() ||
220 ( maBindingExpression.hasValue() &&
221 !maBindingExpression.getString().isEmpty() ) );
224 bool Binding::isUseful() const
226 // we are useful, if
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)
233 bool bUseful =
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();
246 return bUseful;
249 OUString Binding::explainInvalid()
251 OUString sReason;
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!" );
282 return sReason;
286 EvaluationContext Binding::getEvaluationContext() const
288 OSL_ENSURE( getModelImpl() != nullptr, "need model impl" );
289 EvaluationContext aContext = getModelImpl()->getEvaluationContext();
290 aContext.mxNamespaces = getBindingNamespaces();
291 return aContext;
294 ::std::vector<EvaluationContext> Binding::getMIPEvaluationContexts()
296 OSL_ENSURE( getModelImpl() != nullptr, "need model impl" );
298 // bind (in case we were not bound before)
299 bind();
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 );
324 bindingModified();
327 OUString Binding::getReadonlyExpression() const
329 return maReadonly.getExpression();
332 void Binding::setReadonlyExpression( const OUString& sReadonly)
334 maReadonly.setExpression( sReadonly );
335 bindingModified();
338 OUString Binding::getRelevantExpression() const
340 return maRelevant.getExpression();
343 void Binding::setRelevantExpression( const OUString& sRelevant )
345 maRelevant.setExpression( sRelevant );
346 bindingModified();
349 OUString Binding::getRequiredExpression() const
351 return maRequired.getExpression();
354 void Binding::setRequiredExpression( const OUString& sRequired )
356 maRequired.setExpression( sRequired );
357 bindingModified();
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,
369 sConstraint );
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.
374 bindingModified();
377 OUString Binding::getCalculateExpression() const
379 return maCalculate.getExpression();
382 void Binding::setCalculateExpression( const OUString& sCalculate )
384 maCalculate.setExpression( sCalculate );
385 bindingModified();
389 void Binding::setType( const OUString& sTypeName )
391 msTypeName = sTypeName;
392 bindingModified();
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;
423 if ( !mxModel.is() )
424 return bExternalData;
428 Reference< XPropertySet > xModelProps( mxModel, UNO_QUERY_THROW );
429 OSL_VERIFY(
430 xModelProps->getPropertyValue( "ExternalData" ) >>= bExternalData );
432 catch( const Exception& )
434 DBG_UNHANDLED_EXCEPTION("forms.xforms");
436 return bExternalData;
440 void Binding::checkLive()
442 if( ! isLive() )
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 );
461 if( !xTarget.is() )
462 return;
464 xTarget->addEventListener( "DOMCharacterDataModified",
465 xListener, false );
466 xTarget->addEventListener( "DOMCharacterDataModified",
467 xListener, true );
468 xTarget->addEventListener( "DOMAttrModified",
469 xListener, false );
470 xTarget->addEventListener( "DOMAttrModified",
471 xListener, true );
472 xTarget->addEventListener( "xforms-generic",
473 xListener, true );
476 static void lcl_removeListenerFromNode( const Reference<XNode>& xNode,
477 const Reference<XEventListener>& xListener )
479 Reference<XEventTarget> xTarget( xNode, UNO_QUERY );
480 if( !xTarget.is() )
481 return;
483 xTarget->removeEventListener( "DOMCharacterDataModified",
484 xListener, false );
485 xTarget->removeEventListener( "DOMCharacterDataModified",
486 xListener, true );
487 xTarget->removeEventListener( "DOMAttrModified",
488 xListener, false );
489 xTarget->removeEventListener( "DOMAttrModified",
490 xListener, true );
491 xTarget->removeEventListener( "xforms-generic",
492 xListener, true );
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() );
510 return aVector;
513 void Binding::bind( bool bForceRebind )
515 if( ! mxModel.is() )
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());
557 else
558 maEventNodes.emplace_back( aContext.mxContextNode->getOwnerDocument(),
559 UNO_QUERY_THROW );
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;
634 return;
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
643 if( xNode.is() )
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
664 if( xNode.is() )
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 );
675 while(xNode.is()) {
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());
680 if(child.is())
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;
700 return;
702 mbBindingModified = false;
704 // rebind (if live); then call valueModified
705 // A binding should be inert until its model is fully constructed.
706 if( isLive() )
708 bind( true );
709 valueModified();
714 MIP Binding::getLocalMIP() const
716 MIP aMIP;
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() );
736 return aMIP;
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() )
765 : OUString();
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();
780 // clear expressions
781 maBindingExpression.clear();
782 maReadonly.clear();
783 maRelevant.clear();
784 maRequired.clear();
785 maConstraint.clear();
786 maCalculate.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,
821 bool bOverwrite )
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
835 // it in the source:
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!
844 if( bCopy )
846 if( bInTarget )
847 xTo->replaceByName( rName, xFrom->getByName( rName ) );
848 else
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 );
866 return xNamespaces;
869 // implement set*Namespaces()
870 // bBinding = true: setBindingNamespaces, otherwise: setModelNamespaces
871 void Binding::_setNamespaces( const css::uno::Reference<css::container::XNameContainer>& rNamespaces,
872 bool bBinding )
874 Model* pModel = getModelImpl();
875 css::uno::Reference<css::container::XNameContainer> xModelNamespaces = ( pModel != nullptr )
876 ? pModel->getNamespaces()
877 : nullptr;
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
896 bool bLocal =
897 ! xModelNamespaces.is()
898 || mxNamespaces->hasByName( rName )
899 || ( bBinding
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 );
908 else
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!
922 bindingModified();
925 void Binding::_checkBindingID()
927 if( !getModel().is() )
928 return;
930 Reference<XNameAccess> xBindings( getModel()->getBindings(), UNO_QUERY_THROW );
931 if( !msBindingID.isEmpty() )
932 return;
934 // no binding ID? then make one up!
935 OUString sIDPrefix = getResource( RID_STR_XFORMS_BINDING_UI_NAME ) + " ";
936 sal_Int32 nNumber = 0;
937 OUString sName;
940 nNumber++;
941 sName = sIDPrefix + OUString::number( nNumber );
943 while( xBindings->hasByName( sName ) );
944 setBindingID( sName );
948 // XValueBinding
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
964 checkLive();
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);
978 return result;
981 void Binding::setValue( const css::uno::Any& aValue )
983 // first, check for model
984 checkLive();
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();
994 if( !xNode.is() )
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 );
999 if( ! bSuccess )
1000 throw InvalidBindingStateException("can't set value", static_cast<XValueBinding*>(this));
1006 // XListEntry Source
1009 sal_Int32 Binding::getListEntryCount()
1011 // first, check for model
1012 checkLive();
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() );
1025 else
1027 for( Reference<XNode> xChild = xNode->getFirstChild();
1028 xChild.is();
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
1046 checkLive();
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
1058 checkLive();
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] );
1069 return aSequence;
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(),
1077 xListener)
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(),
1086 xListener );
1087 if( aIter != maListEntryListeners.end() )
1088 maListEntryListeners.erase( aIter );
1092 // XValidator
1095 sal_Bool Binding::isValid( const css::uno::Any& )
1097 // first, check for model
1098 checkLive();
1100 // ignore value; determine validate only on current data
1101 return isValid();
1104 OUString Binding::explainInvalid(
1105 const css::uno::Any& /*Value*/ )
1107 // first, check for model
1108 checkLive();
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(),
1128 xListener );
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
1148 // as well...
1149 bool bPreserveValueModified = mbValueModified;
1150 mnDeferModifyNotifications++;
1151 valueModified();
1152 --mnDeferModifyNotifications;
1153 mbValueModified = bPreserveValueModified;
1154 return;
1157 // if we're a dynamic binding, we better re-bind, too!
1158 bind();
1160 // our value was maybe modified
1161 valueModified();
1165 // lang::XUnoTunnel
1168 sal_Int64 Binding::getSomething( const css::uno::Sequence<sal_Int8>& xId )
1170 return comphelper::getSomethingImpl(xId, this);
1174 // XCloneable
1177 css::uno::Reference<css::util::XCloneable> SAL_CALL Binding::createClone()
1179 Reference< XPropertySet > xClone;
1181 Model* pModel = getModelImpl();
1182 if ( pModel )
1183 xClone = pModel->cloneBinding( this );
1184 else
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.
1258 valueModified();
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: */