Update git submodules
[LibreOffice.git] / forms / source / xforms / binding.cxx
blob40dd16586227a0467219983e714ae65c70aa5fed
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 rtl::Reference<Model>& 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 return ( mxModel == nullptr ) ? OUString() : mxModel->getID();
149 css::uno::Reference<css::xml::dom::XNodeList> Binding::getXNodeList()
151 // first make sure we are bound
152 if( ! maBindingExpression.hasValue() )
153 bind();
155 return maBindingExpression.getXNodeList();
158 bool Binding::isSimpleBinding() const
160 return maBindingExpression.isSimpleExpression()
161 && maReadonly.isSimpleExpression()
162 && maRelevant.isSimpleExpression()
163 && maRequired.isSimpleExpression()
164 && maConstraint.isSimpleExpression()
165 && maCalculate.isSimpleExpression();
168 bool Binding::isSimpleBindingExpression() const
170 return maBindingExpression.isSimpleExpression();
173 void Binding::update()
175 // clear all expressions (to remove cached node references)
176 maBindingExpression.clear();
177 maReadonly.clear();
178 maRelevant.clear();
179 maRequired.clear();
180 maConstraint.clear();
181 maCalculate.clear();
183 // let's just pretend the binding has been modified -> full rebind()
184 bindingModified();
187 void Binding::deferNotifications( bool bDefer )
189 mnDeferModifyNotifications += ( bDefer ? 1 : -1 );
190 OSL_ENSURE( mnDeferModifyNotifications >= 0, "you're deferring too much" );
192 if( mnDeferModifyNotifications == 0 )
194 if( mbBindingModified )
195 bindingModified();
196 if( mbValueModified )
197 valueModified();
200 OSL_ENSURE( ( mnDeferModifyNotifications > 0 )
201 || ( ! mbBindingModified && ! mbValueModified ),
202 "deferred modifications not delivered?" );
205 bool Binding::isValid() const
207 // TODO: determine whether node is suitable, not just whether it exists
208 return maBindingExpression.getNode().is() &&
210 // tdf#155121, validity rules should be apply when field is required or
211 // when the field is not required but not empty
212 // so if the field is not required and empty, do not check validity
213 (! maMIP.isRequired() && maBindingExpression.hasValue()
214 && maBindingExpression.getString().isEmpty() ) ||
215 isValid_DataType()
216 ) &&
217 maMIP.isConstraint() &&
218 ( ! maMIP.isRequired() ||
219 ( maBindingExpression.hasValue() &&
220 !maBindingExpression.getString().isEmpty() ) );
223 bool Binding::isUseful() const
225 // we are useful, if
226 // 0) we don't have a model
227 // (at least, in this case we shouldn't be removed from the model)
228 // 1) we have a proper name
229 // 2) we have some MIPs,
230 // 3) we are bound to some control
231 // (this can be assumed if some listeners are set)
232 bool bUseful =
233 mxModel == nullptr
234 // || msBindingID.getLength() > 0
235 || ! msTypeName.isEmpty()
236 || ! maReadonly.isEmptyExpression()
237 || ! maRelevant.isEmptyExpression()
238 || ! maRequired.isEmptyExpression()
239 || ! maConstraint.isEmptyExpression()
240 || ! maCalculate.isEmptyExpression()
241 || ! maModifyListeners.empty()
242 || ! maListEntryListeners.empty()
243 || ! maValidityListeners.empty();
245 return bUseful;
248 OUString Binding::explainInvalid()
250 OUString sReason;
251 if( ! maBindingExpression.getNode().is() )
253 sReason = ( maBindingExpression.getExpression().isEmpty() )
254 ? getResource( RID_STR_XFORMS_NO_BINDING_EXPRESSION )
255 : getResource( RID_STR_XFORMS_INVALID_BINDING_EXPRESSION );
257 else if( ! isValid_DataType() )
259 sReason = explainInvalid_DataType();
260 if( sReason.isEmpty() )
262 // no explanation given by data type? Then give generic message
263 sReason = getResource( RID_STR_XFORMS_INVALID_VALUE,
264 maMIP.getTypeName() );
267 else if( ! maMIP.isConstraint() )
269 sReason = maMIP.getConstraintExplanation();
271 else if( maMIP.isRequired() && maBindingExpression.hasValue() &&
272 maBindingExpression.getString().isEmpty() )
274 sReason = getResource( RID_STR_XFORMS_REQUIRED );
276 // else: no explanation given; should only happen if data is valid
278 OSL_ENSURE( sReason.isEmpty() == isValid(),
279 "invalid data should have an explanation!" );
281 return sReason;
285 EvaluationContext Binding::getEvaluationContext() const
287 OSL_ENSURE( mxModel != nullptr, "need model impl" );
288 EvaluationContext aContext = mxModel->getEvaluationContext();
289 aContext.mxNamespaces = getBindingNamespaces();
290 return aContext;
293 ::std::vector<EvaluationContext> Binding::getMIPEvaluationContexts()
295 OSL_ENSURE( mxModel != nullptr, "need model impl" );
297 // bind (in case we were not bound before)
298 bind();
299 return _getMIPEvaluationContexts();
303 css::uno::Sequence<sal_Int8> Binding::getUnoTunnelId()
305 static const comphelper::UnoIdInit aImplementationId;
306 return aImplementationId.getSeq();
310 void Binding::setBindingID( const OUString& sBindingID )
312 msBindingID = sBindingID;
315 OUString Binding::getBindingExpression() const
317 return maBindingExpression.getExpression();
320 void Binding::setBindingExpression( const OUString& sBindingExpression)
322 maBindingExpression.setExpression( sBindingExpression );
323 bindingModified();
326 OUString Binding::getReadonlyExpression() const
328 return maReadonly.getExpression();
331 void Binding::setReadonlyExpression( const OUString& sReadonly)
333 maReadonly.setExpression( sReadonly );
334 bindingModified();
337 OUString Binding::getRelevantExpression() const
339 return maRelevant.getExpression();
342 void Binding::setRelevantExpression( const OUString& sRelevant )
344 maRelevant.setExpression( sRelevant );
345 bindingModified();
348 OUString Binding::getRequiredExpression() const
350 return maRequired.getExpression();
353 void Binding::setRequiredExpression( const OUString& sRequired )
355 maRequired.setExpression( sRequired );
356 bindingModified();
359 OUString Binding::getConstraintExpression() const
361 return maConstraint.getExpression();
364 void Binding::setConstraintExpression( const OUString& sConstraint )
366 maConstraint.setExpression( sConstraint );
367 msExplainConstraint = getResource( RID_STR_XFORMS_INVALID_CONSTRAINT,
368 sConstraint );
370 // TODO: This should only re-evaluate the constraint, and notify
371 // the validity constraint listeners; instead we currently pretend
372 // the entire binding was notified, which does a little too much.
373 bindingModified();
376 OUString Binding::getCalculateExpression() const
378 return maCalculate.getExpression();
381 void Binding::setCalculateExpression( const OUString& sCalculate )
383 maCalculate.setExpression( sCalculate );
384 bindingModified();
388 void Binding::setType( const OUString& sTypeName )
390 msTypeName = sTypeName;
391 bindingModified();
394 void Binding::setBindingNamespaces( const css::uno::Reference<css::container::XNameContainer>& rNamespaces )
396 _setNamespaces( rNamespaces, true );
399 css::uno::Reference<css::container::XNameContainer> Binding::getModelNamespaces() const
401 return _getNamespaces();
404 void Binding::setModelNamespaces( const css::uno::Reference<css::container::XNameContainer>& rNamespaces )
406 _setNamespaces( rNamespaces, false );
409 bool Binding::getReadOnly() const
411 return maMIP.isReadonly();
414 bool Binding::getRelevant() const
416 return maMIP.isRelevant();
419 bool Binding::getExternalData() const
421 bool bExternalData = true;
422 if ( !mxModel.is() )
423 return bExternalData;
427 OSL_VERIFY(
428 mxModel->getPropertyValue( u"ExternalData"_ustr ) >>= bExternalData );
430 catch( const Exception& )
432 DBG_UNHANDLED_EXCEPTION("forms.xforms");
434 return bExternalData;
438 void Binding::checkLive()
440 if( ! isLive() )
441 throw RuntimeException(u"Binding not initialized"_ustr, static_cast<XValueBinding*>(this));
444 bool Binding::isLive() const
446 return mxModel && mxModel->isInitialized();
450 static void lcl_addListenerToNode( const Reference<XNode>& xNode,
451 const Reference<XEventListener>& xListener )
453 Reference<XEventTarget> xTarget( xNode, UNO_QUERY );
454 if( !xTarget.is() )
455 return;
457 xTarget->addEventListener( u"DOMCharacterDataModified"_ustr,
458 xListener, false );
459 xTarget->addEventListener( u"DOMCharacterDataModified"_ustr,
460 xListener, true );
461 xTarget->addEventListener( u"DOMAttrModified"_ustr,
462 xListener, false );
463 xTarget->addEventListener( u"DOMAttrModified"_ustr,
464 xListener, true );
465 xTarget->addEventListener( u"xforms-generic"_ustr,
466 xListener, true );
469 static void lcl_removeListenerFromNode( const Reference<XNode>& xNode,
470 const Reference<XEventListener>& xListener )
472 Reference<XEventTarget> xTarget( xNode, UNO_QUERY );
473 if( !xTarget.is() )
474 return;
476 xTarget->removeEventListener( u"DOMCharacterDataModified"_ustr,
477 xListener, false );
478 xTarget->removeEventListener( u"DOMCharacterDataModified"_ustr,
479 xListener, true );
480 xTarget->removeEventListener( u"DOMAttrModified"_ustr,
481 xListener, false );
482 xTarget->removeEventListener( u"DOMAttrModified"_ustr,
483 xListener, true );
484 xTarget->removeEventListener( u"xforms-generic"_ustr,
485 xListener, true );
488 ::std::vector<EvaluationContext> Binding::_getMIPEvaluationContexts() const
490 OSL_ENSURE( mxModel != nullptr, "need model impl" );
492 // iterate over nodes of bind expression and create
493 // EvaluationContext for each
494 PathExpression::NodeVector_t aNodes = maBindingExpression.getNodeList();
495 ::std::vector<EvaluationContext> aVector;
496 for (auto const& node : aNodes)
498 OSL_ENSURE( node.is(), "no node?" );
500 // create proper evaluation context for this MIP
501 aVector.emplace_back( node, mxModel, getBindingNamespaces() );
503 return aVector;
506 void Binding::bind( bool bForceRebind )
508 if( ! mxModel.is() )
509 throw RuntimeException(u"Binding has no Model"_ustr, static_cast<XValueBinding*>(this));
511 // bind() will evaluate this binding as follows:
512 // 1) evaluate the binding expression
513 // 1b) if necessary, create node according to 'lazy author' rules
514 // 2) register suitable listeners on the instance (and remove old ones)
515 // 3) remove old MIPs defined by this binding
516 // 4) for every node in the binding nodeset do:
517 // 1) create proper evaluation context for this MIP
518 // 2) evaluate calculate expression (and push value into instance)
519 // 3) evaluate remaining MIPs
520 // 4) evaluate the locally defined MIPs, and push them to the model
523 // 1) evaluate the binding expression
524 EvaluationContext aContext = getEvaluationContext();
525 maBindingExpression.evaluate( aContext );
526 if( ! maBindingExpression.getNode().is() )
528 // 1b) create node (if valid element name)
529 if( isValidQName( maBindingExpression.getExpression(),
530 aContext.mxNamespaces ) )
532 aContext.mxContextNode->appendChild(
533 aContext.mxContextNode->getOwnerDocument()->createElement(
534 maBindingExpression.getExpression() ) );
535 maBindingExpression.evaluate( aContext );
536 OSL_ENSURE( maBindingExpression.getNode().is(),
537 "we should bind to the newly inserted node!" );
540 PathExpression::NodeVector_t aNodes = maBindingExpression.getNodeList();
542 // 2) register suitable listeners on the instance (and remove old ones)
543 if( maEventNodes.empty() || bForceRebind )
545 for (auto const& eventNode : maEventNodes)
546 lcl_removeListenerFromNode( eventNode, this );
547 maEventNodes.clear();
548 if( isSimpleBinding() )
549 maEventNodes.insert(maEventNodes.end(), aNodes.begin(), aNodes.end());
550 else
551 maEventNodes.emplace_back( aContext.mxContextNode->getOwnerDocument(),
552 UNO_QUERY_THROW );
553 for (auto const& eventNode : maEventNodes)
554 lcl_addListenerToNode( eventNode, this );
557 // 3) remove old MIPs defined by this binding
558 OSL_ENSURE( mxModel != nullptr, "need model" );
559 mxModel->removeMIPs( this );
561 // 4) calculate all MIPs
562 ::std::vector<EvaluationContext> aMIPContexts = _getMIPEvaluationContexts();
563 for (auto & context : aMIPContexts)
565 EvaluationContext& rContext = context;
567 // evaluate calculate expression (and push value into instance)
568 // (prevent recursion using mbInCalculate
569 if( ! maCalculate.isEmptyExpression() )
571 if( ! mbInCalculate )
573 mbInCalculate = true;
574 maCalculate.evaluate( rContext );
575 mxModel->setSimpleContent( rContext.mxContextNode,
576 maCalculate.getString() );
577 mbInCalculate = false;
581 // now evaluate remaining MIPs in the appropriate context
582 maReadonly.evaluate( rContext );
583 maRelevant.evaluate( rContext );
584 maRequired.evaluate( rContext );
585 maConstraint.evaluate( rContext );
586 // type is static; does not need updating
588 // evaluate the locally defined MIPs, and push them to the model
589 mxModel->addMIP( this, rContext.mxContextNode, getLocalMIP() );
594 // helper for Binding::valueModified
595 static void lcl_modified( const css::uno::Reference<css::util::XModifyListener>& xListener,
596 const Reference<XInterface>& xSource )
598 OSL_ENSURE( xListener.is(), "no listener?" );
599 xListener->modified( EventObject( xSource ) );
602 // helper for Binding::valueModified
603 static void lcl_listentry( const css::uno::Reference<css::form::binding::XListEntryListener>& xListener,
604 const Reference<XInterface>& xSource )
606 OSL_ENSURE( xListener.is(), "no listener?" );
607 // TODO: send fine granular events
608 xListener->allEntriesChanged( EventObject( xSource ) );
611 // helper for Binding::valueModified
612 static void lcl_validate( const css::uno::Reference<css::form::validation::XValidityConstraintListener>& xListener,
613 const Reference<XInterface>& xSource )
615 OSL_ENSURE( xListener.is(), "no listener?" );
616 xListener->validityConstraintChanged( EventObject( xSource ) );
620 void Binding::valueModified()
622 // defer notifications, if so desired
623 if( mnDeferModifyNotifications > 0 )
625 mbValueModified = true;
626 return;
628 mbValueModified = false;
630 // query MIP used by our first node (also note validity)
631 Reference<XNode> xNode = maBindingExpression.getNode();
632 maMIP = mxModel->queryMIP( xNode );
634 // distribute MIPs _used_ by this binding
635 if( xNode.is() )
637 notifyAndCachePropertyValue( HANDLE_ReadOnly );
638 notifyAndCachePropertyValue( HANDLE_Relevant );
641 // iterate over _value_ listeners and send each a modified signal,
642 // using this object as source (will also update validity, because
643 // control will query once the value has changed)
644 Reference<XInterface> xSource = static_cast<XPropertySet*>( this );
645 ::std::for_each( maModifyListeners.begin(),
646 maModifyListeners.end(),
647 ::std::bind( lcl_modified, std::placeholders::_1, xSource ) );
648 ::std::for_each( maListEntryListeners.begin(),
649 maListEntryListeners.end(),
650 ::std::bind( lcl_listentry, std::placeholders::_1, xSource ) );
651 ::std::for_each( maValidityListeners.begin(),
652 maValidityListeners.end(),
653 ::std::bind( lcl_validate, std::placeholders::_1, xSource ) );
655 // now distribute MIPs to children
656 if( xNode.is() )
657 distributeMIP( xNode->getFirstChild() );
660 void Binding::distributeMIP( const css::uno::Reference<css::xml::dom::XNode> & rxNode ) {
662 rtl::Reference<css::xforms::XFormsEventConcrete> pEvent = new css::xforms::XFormsEventConcrete;
663 pEvent->initXFormsEvent(u"xforms-generic"_ustr, true, false);
665 // naive depth-first traversal
666 css::uno::Reference<css::xml::dom::XNode> xNode( rxNode );
667 while(xNode.is()) {
669 // notifications should be triggered at the
670 // leaf nodes first, bubbling upwards the hierarchy.
671 css::uno::Reference<css::xml::dom::XNode> child(xNode->getFirstChild());
672 if(child.is())
673 distributeMIP(child);
675 // we're standing at a particular node somewhere
676 // below the one which changed a property (MIP).
677 // bindings which are listening at this node will receive
678 // a notification message about what exactly happened.
679 Reference< XEventTarget > target(xNode,UNO_QUERY);
680 target->dispatchEvent(pEvent);
682 xNode = xNode->getNextSibling();
686 void Binding::bindingModified()
688 // defer notifications, if so desired
689 if( mnDeferModifyNotifications > 0 )
691 mbBindingModified = true;
692 return;
694 mbBindingModified = false;
696 // rebind (if live); then call valueModified
697 // A binding should be inert until its model is fully constructed.
698 if( isLive() )
700 bind( true );
701 valueModified();
706 MIP Binding::getLocalMIP() const
708 MIP aMIP;
710 if( maReadonly.hasValue() )
711 aMIP.setReadonly( maReadonly.getBool() );
712 if( maRelevant.hasValue() )
713 aMIP.setRelevant( maRelevant.getBool( true ) );
714 if( maRequired.hasValue() )
715 aMIP.setRequired( maRequired.getBool() );
716 if( maConstraint.hasValue() )
718 aMIP.setConstraint( maConstraint.getBool( true ) );
719 if( ! aMIP.isConstraint() )
720 aMIP.setConstraintExplanation( msExplainConstraint );
722 if( !msTypeName.isEmpty() )
723 aMIP.setTypeName( msTypeName );
725 // calculate: only handle presence of calculate; value set elsewhere
726 aMIP.setHasCalculate( !maCalculate.isEmptyExpression() );
728 return aMIP;
731 css::uno::Reference<css::xsd::XDataType> Binding::getDataType() const
733 OSL_ENSURE( mxModel.is(), "need model" );
734 OSL_ENSURE( mxModel->getDataTypeRepository().is(), "need types" );
736 Reference<XDataTypeRepository> xRepository =
737 mxModel->getDataTypeRepository();
738 OUString sTypeName = maMIP.getTypeName();
740 return ( xRepository.is() && xRepository->hasByName( sTypeName ) )
741 ? Reference<XDataType>( xRepository->getByName( sTypeName ), UNO_QUERY)
742 : Reference<XDataType>( nullptr );
745 bool Binding::isValid_DataType() const
747 Reference<XDataType> xDataType = getDataType();
748 return !xDataType.is()
749 || xDataType->validate( maBindingExpression.getString() );
752 OUString Binding::explainInvalid_DataType()
754 Reference<XDataType> xDataType = getDataType();
755 return xDataType.is()
756 ? xDataType->explainInvalid( maBindingExpression.getString() )
757 : OUString();
760 void Binding::clear()
762 // remove MIPs contributed by this binding
763 if( mxModel != nullptr )
764 mxModel->removeMIPs( this );
766 // remove all references
767 for (auto const& eventNode : maEventNodes)
768 lcl_removeListenerFromNode( eventNode, this );
769 maEventNodes.clear();
771 // clear expressions
772 maBindingExpression.clear();
773 maReadonly.clear();
774 maRelevant.clear();
775 maRequired.clear();
776 maConstraint.clear();
777 maCalculate.clear();
779 // TODO: what about our listeners?
783 static void lcl_removeOtherNamespaces( const css::uno::Reference<css::container::XNameContainer>& xFrom,
784 css::uno::Reference<css::container::XNameContainer> const & xTo )
786 OSL_ENSURE( xFrom.is(), "no source" );
787 OSL_ENSURE( xTo.is(), "no target" );
789 // iterate over name in source
790 Sequence<OUString> aNames = xTo->getElementNames();
791 sal_Int32 nNames = aNames.getLength();
792 const OUString* pNames = aNames.getConstArray();
793 for( sal_Int32 i = 0; i < nNames; i++ )
795 const OUString& rName = pNames[i];
797 if( ! xFrom->hasByName( rName ) )
798 xTo->removeByName( rName );
802 /** copy namespaces from one namespace container into another
803 * @param bOverwrite true: overwrite namespaces in target
804 * false: do not overwrite namespaces in target
805 * @param bMove true: move namespaces (i.e., delete in source)
806 * false: copy namespaces (do not modify source)
807 * @param bFromSource true: use elements from source
808 * false: use only elements from target
810 static void lcl_copyNamespaces( const css::uno::Reference<css::container::XNameContainer>& xFrom,
811 css::uno::Reference<css::container::XNameContainer> const & xTo,
812 bool bOverwrite )
814 OSL_ENSURE( xFrom.is(), "no source" );
815 OSL_ENSURE( xTo.is(), "no target" );
817 // iterate over name in source
818 Sequence<OUString> aNames = xFrom->getElementNames();
819 sal_Int32 nNames = aNames.getLength();
820 const OUString* pNames = aNames.getConstArray();
821 for( sal_Int32 i = 0; i < nNames; i++ )
823 const OUString& rName = pNames[i];
825 // determine whether to copy the value, and whether to delete
826 // it in the source:
828 bool bInTarget = xTo->hasByName( rName );
830 // we copy: if property is in target, and
831 // if bOverwrite is set, or when the namespace prefix is free
832 bool bCopy = bOverwrite || ! bInTarget;
834 // and now... ACTION!
835 if( bCopy )
837 if( bInTarget )
838 xTo->replaceByName( rName, xFrom->getByName( rName ) );
839 else
840 xTo->insertByName( rName, xFrom->getByName( rName ) );
845 // implement get*Namespaces()
846 // (identical for both variants)
847 css::uno::Reference<css::container::XNameContainer> Binding::_getNamespaces() const
849 css::uno::Reference<css::container::XNameContainer> xNamespaces = new NameContainer<OUString>();
850 lcl_copyNamespaces( mxNamespaces, xNamespaces, true );
852 // merge model's with binding's own namespaces
853 if( mxModel != nullptr )
854 lcl_copyNamespaces( mxModel->getNamespaces(), xNamespaces, false );
856 return xNamespaces;
859 // implement set*Namespaces()
860 // bBinding = true: setBindingNamespaces, otherwise: setModelNamespaces
861 void Binding::_setNamespaces( const css::uno::Reference<css::container::XNameContainer>& rNamespaces,
862 bool bBinding )
864 css::uno::Reference<css::container::XNameContainer> xModelNamespaces = ( mxModel != nullptr )
865 ? mxModel->getNamespaces()
866 : nullptr;
867 OSL_ENSURE( ( mxModel != nullptr ) == xModelNamespaces.is(), "no model nmsp?");
869 // remove deleted namespaces
870 lcl_removeOtherNamespaces( rNamespaces, mxNamespaces );
871 if( !bBinding && xModelNamespaces.is() )
872 lcl_removeOtherNamespaces( rNamespaces, xModelNamespaces );
874 // copy namespaces as appropriate
875 Sequence<OUString> aNames = rNamespaces->getElementNames();
876 sal_Int32 nNames = aNames.getLength();
877 const OUString* pNames = aNames.getConstArray();
878 for( sal_Int32 i = 0; i < nNames; i++ )
880 const OUString& rName = pNames[i];
881 Any aValue = rNamespaces->getByName( rName );
883 // determine whether the namespace should go into model's or
884 // into binding's namespaces
885 bool bLocal =
886 ! xModelNamespaces.is()
887 || mxNamespaces->hasByName( rName )
888 || ( bBinding
889 && xModelNamespaces.is()
890 && xModelNamespaces->hasByName( rName ) );
892 // write namespace into the appropriate namespace container
893 css::uno::Reference<css::container::XNameContainer>& rWhich = bLocal ? mxNamespaces : xModelNamespaces;
894 OSL_ENSURE( rWhich.is(), "whoops" );
895 if( rWhich->hasByName( rName ) )
896 rWhich->replaceByName( rName, aValue );
897 else
898 rWhich->insertByName( rName, aValue );
900 // always 'promote' namespaces from binding to model, if equal
901 if( xModelNamespaces.is()
902 && xModelNamespaces->hasByName( rName )
903 && mxNamespaces->hasByName( rName )
904 && xModelNamespaces->getByName( rName ) == mxNamespaces->getByName( rName ) )
906 mxNamespaces->removeByName( rName );
910 // ... done. But we modified the binding!
911 bindingModified();
914 void Binding::_checkBindingID()
916 if( !mxModel.is() )
917 return;
919 Reference<XNameAccess> xBindings( mxModel->getBindings(), UNO_QUERY_THROW );
920 if( !msBindingID.isEmpty() )
921 return;
923 // no binding ID? then make one up!
924 OUString sIDPrefix = getResource( RID_STR_XFORMS_BINDING_UI_NAME ) + " ";
925 sal_Int32 nNumber = 0;
926 OUString sName;
929 nNumber++;
930 sName = sIDPrefix + OUString::number( nNumber );
932 while( xBindings->hasByName( sName ) );
933 setBindingID( sName );
937 // XValueBinding
940 css::uno::Sequence<css::uno::Type> Binding::getSupportedValueTypes()
942 return Convert::get().getTypes();
945 sal_Bool Binding::supportsType( const css::uno::Type& rType )
947 return Convert::get().hasType( rType );
950 css::uno::Any Binding::getValue( const css::uno::Type& rType )
952 // first, check for model
953 checkLive();
955 // second, check for type
956 if( ! supportsType( rType ) )
957 throw IncompatibleTypesException(u"type unsupported"_ustr, static_cast<XValueBinding*>(this));
959 // return string value (if present; else return empty Any)
960 css::uno::Any result;
961 if(maBindingExpression.hasValue()) {
962 OUString pathExpr(maBindingExpression.getString());
963 Convert &rConvert = Convert::get();
964 result = rConvert.toAny(pathExpr,rType);
967 return result;
970 void Binding::setValue( const css::uno::Any& aValue )
972 // first, check for model
973 checkLive();
975 // check for supported type
976 if( ! supportsType( aValue.getValueType() ) )
977 throw IncompatibleTypesException(u"type unsupported"_ustr, static_cast<XValueBinding*>(this));
979 if( !maBindingExpression.hasValue() )
980 throw InvalidBindingStateException(u"no suitable node found"_ustr, static_cast<XValueBinding*>(this));
982 css::uno::Reference<css::xml::dom::XNode> xNode = maBindingExpression.getNode();
983 if( !xNode.is() )
984 throw InvalidBindingStateException(u"no suitable node found"_ustr, static_cast<XValueBinding*>(this));
986 OUString sValue = Convert::get().toXSD( aValue );
987 bool bSuccess = mxModel->setSimpleContent( xNode, sValue );
988 if( ! bSuccess )
989 throw InvalidBindingStateException(u"can't set value"_ustr, static_cast<XValueBinding*>(this));
995 // XListEntry Source
998 sal_Int32 Binding::getListEntryCount()
1000 // first, check for model
1001 checkLive();
1003 // return size of node list
1004 return maBindingExpression.getNodeList().size();
1007 static void lcl_getString( const Reference<XNode>& xNode, OUStringBuffer& rBuffer )
1009 if( xNode->getNodeType() == NodeType_TEXT_NODE
1010 || xNode->getNodeType() == NodeType_ATTRIBUTE_NODE )
1012 rBuffer.append( xNode->getNodeValue() );
1014 else
1016 for( Reference<XNode> xChild = xNode->getFirstChild();
1017 xChild.is();
1018 xChild = xChild->getNextSibling() )
1020 lcl_getString( xChild, rBuffer );
1025 static OUString lcl_getString( const Reference<XNode>& xNode )
1027 OUStringBuffer aBuffer;
1028 lcl_getString( xNode, aBuffer );
1029 return aBuffer.makeStringAndClear();
1032 OUString Binding::getListEntry( sal_Int32 nPosition )
1034 // first, check for model
1035 checkLive();
1037 // check bounds and return proper item
1038 PathExpression::NodeVector_t aNodes = maBindingExpression.getNodeList();
1039 if( nPosition < 0 || o3tl::make_unsigned(nPosition) >= aNodes.size() )
1040 throw IndexOutOfBoundsException(u""_ustr, static_cast<XValueBinding*>(this));
1041 return lcl_getString( aNodes[ nPosition ] );
1044 Sequence<OUString> Binding::getAllListEntries()
1046 // first, check for model
1047 checkLive();
1049 // create sequence of string values
1050 PathExpression::NodeVector_t aNodes = maBindingExpression.getNodeList();
1051 Sequence<OUString> aSequence( aNodes.size() );
1052 OUString* pSequence = aSequence.getArray();
1053 for( sal_Int32 n = 0; n < aSequence.getLength(); n++ )
1055 pSequence[n] = lcl_getString( aNodes[n] );
1058 return aSequence;
1061 void Binding::addListEntryListener( const css::uno::Reference<css::form::binding::XListEntryListener>& xListener )
1063 OSL_ENSURE( xListener.is(), "need listener!" );
1064 if( ::std::find( maListEntryListeners.begin(),
1065 maListEntryListeners.end(),
1066 xListener)
1067 == maListEntryListeners.end() )
1068 maListEntryListeners.push_back( xListener );
1071 void Binding::removeListEntryListener( const css::uno::Reference<css::form::binding::XListEntryListener>& xListener )
1073 XListEntryListeners_t::iterator aIter =
1074 ::std::find( maListEntryListeners.begin(), maListEntryListeners.end(),
1075 xListener );
1076 if( aIter != maListEntryListeners.end() )
1077 maListEntryListeners.erase( aIter );
1081 // XValidator
1084 sal_Bool Binding::isValid( const css::uno::Any& )
1086 // first, check for model
1087 checkLive();
1089 // ignore value; determine validate only on current data
1090 return isValid();
1093 OUString Binding::explainInvalid(
1094 const css::uno::Any& /*Value*/ )
1096 // first, check for model
1097 checkLive();
1099 // ignore value; determine explanation only on current data
1100 return explainInvalid();
1103 void Binding::addValidityConstraintListener(
1104 const css::uno::Reference<css::form::validation::XValidityConstraintListener>& xListener )
1106 OSL_ENSURE( xListener.is(), "need listener!" );
1107 if( ::std::find(maValidityListeners.begin(), maValidityListeners.end(), xListener)
1108 == maValidityListeners.end() )
1109 maValidityListeners.push_back( xListener );
1112 void Binding::removeValidityConstraintListener(
1113 const css::uno::Reference<css::form::validation::XValidityConstraintListener>& xListener )
1115 XValidityConstraintListeners_t::iterator aIter =
1116 ::std::find( maValidityListeners.begin(), maValidityListeners.end(),
1117 xListener );
1118 if( aIter != maValidityListeners.end() )
1119 maValidityListeners.erase( aIter );
1123 // xml::dom::event::XEventListener
1126 void Binding::handleEvent( const css::uno::Reference<css::xml::dom::events::XEvent>& xEvent )
1128 OUString sType(xEvent->getType());
1129 //OUString sEventMIPChanged("xforms-generic");
1130 //if(sType.equals(sEventMIPChanged)) {
1131 if(sType == "xforms-generic") {
1133 // the modification of the 'mnDeferModifyNotifications'-member
1134 // is necessary to prevent infinite notification looping.
1135 // This can happened in case the binding which caused
1136 // the notification chain is listening to those events
1137 // as well...
1138 bool bPreserveValueModified = mbValueModified;
1139 mnDeferModifyNotifications++;
1140 valueModified();
1141 --mnDeferModifyNotifications;
1142 mbValueModified = bPreserveValueModified;
1143 return;
1146 // if we're a dynamic binding, we better re-bind, too!
1147 bind();
1149 // our value was maybe modified
1150 valueModified();
1154 // lang::XUnoTunnel
1157 sal_Int64 Binding::getSomething( const css::uno::Sequence<sal_Int8>& xId )
1159 return comphelper::getSomethingImpl(xId, this);
1163 // XCloneable
1166 css::uno::Reference<css::util::XCloneable> SAL_CALL Binding::createClone()
1168 Reference< XPropertySet > xClone;
1170 if ( mxModel )
1171 xClone = mxModel->cloneBinding( this );
1172 else
1174 xClone = new Binding;
1175 copy( this, xClone );
1177 return css::uno::Reference<css::util::XCloneable>( xClone, UNO_QUERY );
1180 css::uno::Reference<css::xforms::XModel> Binding::getModel() const
1182 return mxModel;
1185 // property set implementations
1187 void Binding::initializePropertySet()
1189 registerProperty( css::beans::Property(u"BindingID"_ustr, HANDLE_BindingID, cppu::UnoType<OUString>::get(), css::beans::PropertyAttribute::BOUND ),
1190 new DirectPropertyAccessor< Binding, OUString >(this, &Binding::setBindingID, &Binding::getBindingID));
1192 registerProperty( css::beans::Property(u"BindingExpression"_ustr, HANDLE_BindingExpression, cppu::UnoType<OUString>::get(), css::beans::PropertyAttribute::BOUND ),
1193 new DirectPropertyAccessor< Binding, OUString >(this, &Binding::setBindingExpression, &Binding::getBindingExpression));
1195 registerProperty( css::beans::Property(u"Model"_ustr, HANDLE_Model, cppu::UnoType<css::uno::Reference<css::xforms::XModel>>::get(), css::beans::PropertyAttribute::BOUND | css::beans::PropertyAttribute::READONLY ),
1196 new DirectPropertyAccessor< Binding, css::uno::Reference<css::xforms::XModel> >(this, nullptr, &Binding::getModel));
1198 registerProperty( css::beans::Property(u"BindingNamespaces"_ustr, HANDLE_BindingNamespaces, cppu::UnoType<css::uno::Reference<css::container::XNameContainer>>::get(), css::beans::PropertyAttribute::BOUND ),
1199 new DirectPropertyAccessor< Binding, css::uno::Reference<css::container::XNameContainer> >(this, &Binding::setBindingNamespaces, &Binding::getBindingNamespaces));
1201 registerProperty( css::beans::Property(u"ModelNamespaces"_ustr, HANDLE_ModelNamespaces, cppu::UnoType<css::uno::Reference<css::container::XNameContainer>>::get(), css::beans::PropertyAttribute::BOUND ),
1202 new DirectPropertyAccessor< Binding, css::uno::Reference<css::container::XNameContainer> >(this, &Binding::setModelNamespaces, &Binding::getModelNamespaces));
1204 registerProperty( css::beans::Property(u"ModelID"_ustr, HANDLE_ModelID, cppu::UnoType<OUString>::get(), css::beans::PropertyAttribute::BOUND | css::beans::PropertyAttribute::READONLY ),
1205 new DirectPropertyAccessor< Binding, OUString >(this, nullptr, &Binding::getModelID));
1207 registerProperty( css::beans::Property(u"ReadonlyExpression"_ustr, HANDLE_ReadonlyExpression, cppu::UnoType<OUString>::get(), css::beans::PropertyAttribute::BOUND ),
1208 new DirectPropertyAccessor< Binding, OUString >(this, &Binding::setReadonlyExpression, &Binding::getReadonlyExpression));
1210 registerProperty( css::beans::Property(u"RelevantExpression"_ustr, HANDLE_RelevantExpression, cppu::UnoType<OUString>::get(), css::beans::PropertyAttribute::BOUND ),
1211 new DirectPropertyAccessor< Binding, OUString >(this, &Binding::setRelevantExpression, &Binding::getRelevantExpression));
1213 registerProperty( css::beans::Property(u"RequiredExpression"_ustr, HANDLE_RequiredExpression, cppu::UnoType<OUString>::get(), css::beans::PropertyAttribute::BOUND ),
1214 new DirectPropertyAccessor< Binding, OUString >(this, &Binding::setRequiredExpression, &Binding::getRequiredExpression));
1216 registerProperty( css::beans::Property(u"ConstraintExpression"_ustr, HANDLE_ConstraintExpression, cppu::UnoType<OUString>::get(), css::beans::PropertyAttribute::BOUND ),
1217 new DirectPropertyAccessor< Binding, OUString >(this, &Binding::setConstraintExpression, &Binding::getConstraintExpression));
1219 registerProperty( css::beans::Property(u"CalculateExpression"_ustr, HANDLE_CalculateExpression, cppu::UnoType<OUString>::get(), css::beans::PropertyAttribute::BOUND ),
1220 new DirectPropertyAccessor< Binding, OUString >(this, &Binding::setCalculateExpression, &Binding::getCalculateExpression));
1222 registerProperty( css::beans::Property(u"Type"_ustr, HANDLE_Type, cppu::UnoType<OUString>::get(), css::beans::PropertyAttribute::BOUND ),
1223 new DirectPropertyAccessor< Binding, OUString >(this, &Binding::setType, &Binding::getType));
1225 registerProperty( css::beans::Property(u"ReadOnly"_ustr, HANDLE_ReadOnly, cppu::UnoType<bool>::get(), css::beans::PropertyAttribute::BOUND | css::beans::PropertyAttribute::READONLY ),
1226 new DirectPropertyAccessor< Binding, bool >(this, nullptr, &Binding::getReadOnly));
1228 registerProperty( css::beans::Property(u"Relevant"_ustr, HANDLE_Relevant, cppu::UnoType<bool>::get(), css::beans::PropertyAttribute::BOUND | css::beans::PropertyAttribute::READONLY ),
1229 new DirectPropertyAccessor< Binding, bool >(this, nullptr, &Binding::getRelevant));
1231 registerProperty( css::beans::Property(u"ExternalData"_ustr, HANDLE_ExternalData, cppu::UnoType<sal_Bool>::get(), css::beans::PropertyAttribute::BOUND | css::beans::PropertyAttribute::READONLY ),
1232 new BooleanPropertyAccessor< Binding >(this, nullptr, &Binding::getExternalData));
1234 initializePropertyValueCache( HANDLE_ReadOnly );
1235 initializePropertyValueCache( HANDLE_Relevant );
1236 initializePropertyValueCache( HANDLE_ExternalData );
1239 void Binding::addModifyListener(
1240 const css::uno::Reference<css::util::XModifyListener>& xListener )
1242 OSL_ENSURE( xListener.is(), "need listener!" );
1243 if( ::std::find( maModifyListeners.begin(), maModifyListeners.end(), xListener )
1244 == maModifyListeners.end() )
1245 maModifyListeners.push_back( xListener );
1247 // HACK: currently, we have to 'push' some MIPs to the control
1248 // (read-only, relevant, etc.) To enable this, we need to update
1249 // the control at least once when it registers here.
1250 valueModified();
1253 void Binding::removeModifyListener(
1254 const css::uno::Reference<css::util::XModifyListener>& xListener )
1256 ModifyListeners_t::iterator aIter =
1257 ::std::find( maModifyListeners.begin(), maModifyListeners.end(), xListener );
1258 if( aIter != maModifyListeners.end() )
1259 maModifyListeners.erase( aIter );
1263 OUString Binding::getName()
1265 return getBindingID();
1268 void SAL_CALL Binding::setName( const OUString& rName )
1270 // use the XPropertySet methods, so the change in the name is notified to the
1271 // property listeners
1272 setFastPropertyValue( HANDLE_BindingID, Any( rName ) );
1275 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */