/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 .
*/
using std::vector; using xforms::Binding; using xforms::MIP; using xforms::Model; using xforms::getResource; using xforms::EvaluationContext; using com::sun::star::beans::XPropertySet; using com::sun::star::container::XNameAccess; using com::sun::star::form::binding::IncompatibleTypesException; using com::sun::star::form::binding::InvalidBindingStateException; using com::sun::star::form::binding::XValueBinding; using com::sun::star::lang::EventObject; using com::sun::star::lang::IndexOutOfBoundsException; using com::sun::star::uno::Any; using com::sun::star::uno::Reference; using com::sun::star::uno::RuntimeException; using com::sun::star::uno::Sequence; using com::sun::star::uno::UNO_QUERY; using com::sun::star::uno::UNO_QUERY_THROW; using com::sun::star::uno::XInterface; using com::sun::star::uno::Exception; using com::sun::star::util::XModifyListener; using com::sun::star::xforms::XDataTypeRepository; using com::sun::star::xml::dom::NodeType_ATTRIBUTE_NODE; using com::sun::star::xml::dom::NodeType_TEXT_NODE; using com::sun::star::xml::dom::XNode; using com::sun::star::xml::dom::XNodeList; using com::sun::star::xml::dom::events::XEventListener; using com::sun::star::xml::dom::events::XEventTarget; using com::sun::star::xsd::XDataType;
#define HANDLE_BindingID 0 #define HANDLE_BindingExpression 1 #define HANDLE_Model 2 #define HANDLE_ModelID 3 #define HANDLE_BindingNamespaces 4 #define HANDLE_ReadonlyExpression 5 #define HANDLE_RelevantExpression 6 #define HANDLE_RequiredExpression 7 #define HANDLE_ConstraintExpression 8 #define HANDLE_CalculateExpression 9 #define HANDLE_Type 10 #define HANDLE_ReadOnly 11 // from com.sun.star.form.binding.ValueBinding, for interaction with a bound form control #define HANDLE_Relevant 12 // from com.sun.star.form.binding.ValueBinding, for interaction with a bound form control #define HANDLE_ModelNamespaces 13 #define HANDLE_ExternalData 14
// prepare binding for removal of old model
clear(); // remove all cached data (e.g. XPath evaluation results)
css::uno::Reference<css::container::XNameContainer> xNamespaces = getModelNamespaces(); // save namespaces
mxModel = xModel;
// set namespaces (and move to model, if appropriate)
setBindingNamespaces( xNamespaces );
_checkBindingID();
bool Binding::isValid() const
{ // TODO: determine whether node is suitable, not just whether it exists return maBindingExpression.getNode().is() &&
( // tdf#155121, validity rules should be apply when field is required or // when the field is not required but not empty // so if the field is not required and empty, do not check validity
(! maMIP.isRequired() && maBindingExpression.hasValue()
&& maBindingExpression.getString().isEmpty() ) ||
isValid_DataType()
) &&
maMIP.isConstraint() &&
( ! maMIP.isRequired() ||
( maBindingExpression.hasValue() &&
!maBindingExpression.getString().isEmpty() ) );
}
bool Binding::isUseful() const
{ // we are useful, if // 0) we don't have a model // (at least, in this case we shouldn't be removed from the model) // 1) we have a proper name // 2) we have some MIPs, // 3) we are bound to some control // (this can be assumed if some listeners are set) bool bUseful =
mxModel == nullptr // || msBindingID.getLength() > 0
|| ! msTypeName.isEmpty()
|| ! maReadonly.isEmptyExpression()
|| ! maRelevant.isEmptyExpression()
|| ! maRequired.isEmptyExpression()
|| ! maConstraint.isEmptyExpression()
|| ! maCalculate.isEmptyExpression()
|| ! maModifyListeners.empty()
|| ! maListEntryListeners.empty()
|| ! maValidityListeners.empty();
return bUseful;
}
OUString Binding::explainInvalid()
{
OUString sReason; if( ! maBindingExpression.getNode().is() )
{
sReason = ( maBindingExpression.getExpression().isEmpty() )
? getResource( RID_STR_XFORMS_NO_BINDING_EXPRESSION )
: getResource( RID_STR_XFORMS_INVALID_BINDING_EXPRESSION );
} elseif( ! isValid_DataType() )
{
sReason = explainInvalid_DataType(); if( sReason.isEmpty() )
{ // no explanation given by data type? Then give generic message
sReason = getResource( RID_STR_XFORMS_INVALID_VALUE,
maMIP.getTypeName() );
}
} elseif( ! maMIP.isConstraint() )
{
sReason = maMIP.getConstraintExplanation();
} elseif( maMIP.isRequired() && maBindingExpression.hasValue() &&
maBindingExpression.getString().isEmpty() )
{
sReason = getResource( RID_STR_XFORMS_REQUIRED );
} // else: no explanation given; should only happen if data is valid
OSL_ENSURE( sReason.isEmpty() == isValid(), "invalid data should have an explanation!" );
// TODO: This should only re-evaluate the constraint, and notify // the validity constraint listeners; instead we currently pretend // the entire binding was notified, which does a little too much.
bindingModified();
}
// iterate over nodes of bind expression and create // EvaluationContext for each
PathExpression::NodeVector_t aNodes = maBindingExpression.getNodeList();
::std::vector<EvaluationContext> aVector; for (autoconst& node : aNodes)
{
OSL_ENSURE( node.is(), "no node?" );
// create proper evaluation context for this MIP
aVector.emplace_back( node, mxModel, getBindingNamespaces() );
} return aVector;
}
void Binding::bind( bool bForceRebind )
{ if( ! mxModel.is() ) throw RuntimeException(u"Binding has no Model"_ustr, static_cast<XValueBinding*>(this));
// bind() will evaluate this binding as follows: // 1) evaluate the binding expression // 1b) if necessary, create node according to 'lazy author' rules // 2) register suitable listeners on the instance (and remove old ones) // 3) remove old MIPs defined by this binding // 4) for every node in the binding nodeset do: // 1) create proper evaluation context for this MIP // 2) evaluate calculate expression (and push value into instance) // 3) evaluate remaining MIPs // 4) evaluate the locally defined MIPs, and push them to the model
// now evaluate remaining MIPs in the appropriate context
maReadonly.evaluate( rContext );
maRelevant.evaluate( rContext );
maRequired.evaluate( rContext );
maConstraint.evaluate( rContext ); // type is static; does not need updating
// evaluate the locally defined MIPs, and push them to the model
mxModel->addMIP( this, rContext.mxContextNode, getLocalMIP() );
}
}
// query MIP used by our first node (also note validity)
Reference<XNode> xNode = maBindingExpression.getNode();
maMIP = mxModel->queryMIP( xNode );
// distribute MIPs _used_ by this binding if( xNode.is() )
{
notifyAndCachePropertyValue( HANDLE_ReadOnly );
notifyAndCachePropertyValue( HANDLE_Relevant );
}
// iterate over _value_ listeners and send each a modified signal, // using this object as source (will also update validity, because // control will query once the value has changed)
Reference<XInterface> xSource = static_cast<XPropertySet*>( this );
::std::for_each( maModifyListeners.begin(),
maModifyListeners.end(),
[xSource](const css::uno::Reference<css::util::XModifyListener>& xListener)
{ return lcl_modified(xListener, xSource);
});
::std::for_each( maListEntryListeners.begin(),
maListEntryListeners.end(),
[xSource](const css::uno::Reference<css::form::binding::XListEntryListener>& xListener)
{ return lcl_listentry(xListener,xSource);
});
::std::for_each( maValidityListeners.begin(),
maValidityListeners.end(),
[xSource](const css::uno::Reference<css::form::validation::XValidityConstraintListener>& xListener)
{
lcl_validate(xListener, xSource);
});
// now distribute MIPs to children if( xNode.is() )
distributeMIP( xNode->getFirstChild() );
}
// notifications should be triggered at the // leaf nodes first, bubbling upwards the hierarchy.
css::uno::Reference<css::xml::dom::XNode> child(xNode->getFirstChild()); if(child.is())
distributeMIP(child);
// we're standing at a particular node somewhere // below the one which changed a property (MIP). // bindings which are listening at this node will receive // a notification message about what exactly happened.
Reference< XEventTarget > target(xNode,UNO_QUERY);
target->dispatchEvent(pEvent);
// rebind (if live); then call valueModified // A binding should be inert until its model is fully constructed. if( isLive() )
{
bind( true );
valueModified();
}
}
/** copy namespaces from one namespace container into another * @param bOverwrite true: overwrite namespaces in target * false: do not overwrite namespaces in target * @param bMove true: move namespaces (i.e., delete in source) * false: copy namespaces (do not modify source) * @param bFromSource true: use elements from source * false: use only elements from target
*/ staticvoid lcl_copyNamespaces( const css::uno::Reference<css::container::XNameContainer>& xFrom,
css::uno::Reference<css::container::XNameContainer> const & xTo, bool bOverwrite )
{
OSL_ENSURE( xFrom.is(), "no source" );
OSL_ENSURE( xTo.is(), "no target" );
// iterate over name in source
Sequence<OUString> aNames = xFrom->getElementNames();
sal_Int32 nNames = aNames.getLength(); const OUString* pNames = aNames.getConstArray(); for( sal_Int32 i = 0; i < nNames; i++ )
{ const OUString& rName = pNames[i];
// determine whether to copy the value, and whether to delete // it in the source:
bool bInTarget = xTo->hasByName( rName );
// we copy: if property is in target, and // if bOverwrite is set, or when the namespace prefix is free bool bCopy = bOverwrite || ! bInTarget;
// the modification of the 'mnDeferModifyNotifications'-member // is necessary to prevent infinite notification looping. // This can happened in case the binding which caused // the notification chain is listening to those events // as well... bool bPreserveValueModified = mbValueModified;
mnDeferModifyNotifications++;
valueModified();
--mnDeferModifyNotifications;
mbValueModified = bPreserveValueModified; return;
}
// if we're a dynamic binding, we better re-bind, too!
bind();
// our value was maybe modified
valueModified();
}
// HACK: currently, we have to 'push' some MIPs to the control // (read-only, relevant, etc.) To enable this, we need to update // the control at least once when it registers here.
valueModified();
}
void SAL_CALL Binding::setName( const OUString& rName )
{ // use the XPropertySet methods, so the change in the name is notified to the // property listeners
setFastPropertyValue( HANDLE_BindingID, Any( rName ) );
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.