diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
commit | ed5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch) | |
tree | 7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /forms/source/xforms | |
parent | Initial commit. (diff) | |
download | libreoffice-upstream.tar.xz libreoffice-upstream.zip |
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'forms/source/xforms')
56 files changed, 11228 insertions, 0 deletions
diff --git a/forms/source/xforms/NameContainer.hxx b/forms/source/xforms/NameContainer.hxx new file mode 100644 index 000000000..7c1b50993 --- /dev/null +++ b/forms/source/xforms/NameContainer.hxx @@ -0,0 +1,159 @@ +/* -*- 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 . + */ + +#pragma once + +#include <comphelper/sequence.hxx> +#include <cppuhelper/implbase.hxx> +#include <map> + +#include <com/sun/star/container/XNameContainer.hpp> +#include <com/sun/star/container/NoSuchElementException.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/Type.hxx> +#include <osl/diagnose.h> + +typedef cppu::WeakImplHelper< + css::container::XNameContainer +> NameContainer_t; + +template<class T> +class NameContainer : public NameContainer_t +{ + typedef std::map<OUString,T> map_t; + map_t maItems; + +protected: + typename map_t::const_iterator findItem( const OUString& rName ) + { + return maItems.find( rName ); + } + + bool hasItem( const OUString& rName ) + { + return findItem( rName ) != maItems.end(); + } + + void replace( const OUString& rName, + const T& aElement ) + { + OSL_ENSURE( hasItem( rName ), "unknown item" ); + maItems[ rName ] = aElement; + } + + void insert( const OUString& rName, + const T& aElement ) + { + OSL_ENSURE( ! hasItem( rName ), "item already in set" ); + maItems[ rName ] = aElement; + } + + void remove( const OUString& rName ) + { + OSL_ENSURE( hasItem( rName ), "item not in set" ); + maItems.erase( rName ); + } + + +public: + + NameContainer() {} + + + // methods for XElementAccess + + + virtual css::uno::Type SAL_CALL getElementType() override + { + return cppu::UnoType<T>::get(); + } + + virtual sal_Bool SAL_CALL hasElements() override + { + return ! maItems.empty(); + } + + + // methods for XNameAccess (inherits XElementAccess) + + + virtual css::uno::Any SAL_CALL getByName( + const OUString& rName ) override + { + typename map_t::const_iterator aIter = findItem( rName ); + if( aIter == maItems.end() ) + throw css::container::NoSuchElementException(); + return css::uno::Any( aIter->second ); + } + + virtual css::uno::Sequence<OUString> SAL_CALL getElementNames() override + { + return comphelper::mapKeysToSequence(maItems); + } + + virtual sal_Bool SAL_CALL hasByName( + const OUString& rName ) override + { + return hasItem( rName ); + } + + + // methods for XNameReplace (inherits XNameAccess) + + + virtual void SAL_CALL replaceByName( + const OUString& rName, + const css::uno::Any& aElement ) override + { + T aItem; + if( !(aElement >>= aItem) ) + throw css::lang::IllegalArgumentException(); + if( !hasByName( rName ) ) + throw css::container::NoSuchElementException(); + replace( rName, aItem ); + } + + + // methods for XNameContainer (inherits XNameReplace) + + + virtual void SAL_CALL insertByName( + const OUString& rName, + const css::uno::Any& aElement ) override + { + T aItem; + if( !(aElement >>= aItem) ) + throw css::lang::IllegalArgumentException(); + if( hasByName( rName ) ) + throw css::container::ElementExistException(); + insert( rName, aItem ); + } + + virtual void SAL_CALL removeByName( + const OUString& rName ) override + { + if( !hasByName( rName ) ) + throw css::container::NoSuchElementException(); + remove( rName ); + } + +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/forms/source/xforms/binding.cxx b/forms/source/xforms/binding.cxx new file mode 100644 index 000000000..32eeefc75 --- /dev/null +++ b/forms/source/xforms/binding.cxx @@ -0,0 +1,1279 @@ +/* -*- 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 . + */ + + +#include "binding.hxx" + +#include "model.hxx" +#include "unohelper.hxx" +#include "NameContainer.hxx" +#include "evaluationcontext.hxx" +#include "convert.hxx" +#include "resourcehelper.hxx" +#include "xmlhelper.hxx" +#include "xformsevent.hxx" +#include <strings.hrc> + +#include <rtl/ustrbuf.hxx> +#include <o3tl/safeint.hxx> +#include <osl/diagnose.h> + +#include <tools/diagnose_ex.h> + +#include <algorithm> +#include <functional> + +#include <com/sun/star/form/binding/IncompatibleTypesException.hpp> +#include <com/sun/star/form/binding/InvalidBindingStateException.hpp> +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/xml/dom/XNodeList.hpp> +#include <com/sun/star/xml/dom/XNode.hpp> +#include <com/sun/star/xml/dom/XDocument.hpp> +#include <com/sun/star/xml/dom/XElement.hpp> +#include <com/sun/star/xml/dom/NodeType.hpp> +#include <com/sun/star/xml/dom/events/XEventTarget.hpp> +#include <com/sun/star/xml/dom/events/XEventListener.hpp> +#include <com/sun/star/lang/XUnoTunnel.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <com/sun/star/container/XNameContainer.hpp> + +#include <comphelper/servicehelper.hxx> + +using namespace com::sun::star::xml::xpath; +using namespace com::sun::star::xml::dom::events; + +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::lang::XUnoTunnel; +using com::sun::star::uno::Any; +using com::sun::star::uno::Reference; +using com::sun::star::uno::RuntimeException; +using com::sun::star::uno::Sequence; +using com::sun::star::uno::UNO_QUERY; +using com::sun::star::uno::UNO_QUERY_THROW; +using com::sun::star::uno::XInterface; +using com::sun::star::uno::Exception; +using com::sun::star::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 + + +Binding::Binding() : + mxNamespaces( new NameContainer<OUString>() ), + mbInCalculate( false ), + mnDeferModifyNotifications( 0 ), + mbValueModified( false ), + mbBindingModified( false ) +{ + initializePropertySet(); +} + +Binding::~Binding() +{ + _setModel(nullptr); +} + +void Binding::_setModel( const css::uno::Reference<css::xforms::XModel>& xModel ) +{ + PropertyChangeNotifier aNotifyModelChange( *this, HANDLE_Model ); + PropertyChangeNotifier aNotifyModelIDChange( *this, HANDLE_ModelID ); + + // prepare binding for removal of old model + clear(); // remove all cached data (e.g. XPath evaluation results) + css::uno::Reference<css::container::XNameContainer> xNamespaces = getModelNamespaces(); // save namespaces + + mxModel = xModel; + + // set namespaces (and move to model, if appropriate) + setBindingNamespaces( xNamespaces ); + _checkBindingID(); + + notifyAndCachePropertyValue( HANDLE_ExternalData ); +} + + +OUString Binding::getModelID() const +{ + Model* pModel = getModelImpl(); + return ( pModel == nullptr ) ? OUString() : pModel->getID(); +} + + +css::uno::Reference<css::xml::dom::XNodeList> Binding::getXNodeList() +{ + // first make sure we are bound + if( ! maBindingExpression.hasValue() ) + bind(); + + return maBindingExpression.getXNodeList(); +} + +bool Binding::isSimpleBinding() const +{ + return maBindingExpression.isSimpleExpression() + && maReadonly.isSimpleExpression() + && maRelevant.isSimpleExpression() + && maRequired.isSimpleExpression() + && maConstraint.isSimpleExpression() + && maCalculate.isSimpleExpression(); +} + +bool Binding::isSimpleBindingExpression() const +{ + return maBindingExpression.isSimpleExpression(); +} + +void Binding::update() +{ + // clear all expressions (to remove cached node references) + maBindingExpression.clear(); + maReadonly.clear(); + maRelevant.clear(); + maRequired.clear(); + maConstraint.clear(); + maCalculate.clear(); + + // let's just pretend the binding has been modified -> full rebind() + bindingModified(); +} + +void Binding::deferNotifications( bool bDefer ) +{ + mnDeferModifyNotifications += ( bDefer ? 1 : -1 ); + OSL_ENSURE( mnDeferModifyNotifications >= 0, "you're deferring too much" ); + + if( mnDeferModifyNotifications == 0 ) + { + if( mbBindingModified ) + bindingModified(); + if( mbValueModified ) + valueModified(); + } + + OSL_ENSURE( ( mnDeferModifyNotifications > 0 ) + || ( ! mbBindingModified && ! mbValueModified ), + "deferred modifications not delivered?" ); +} + +bool Binding::isValid() const +{ + // TODO: determine whether node is suitable, not just whether it exists + return maBindingExpression.getNode().is() && + isValid_DataType() && + maMIP.isConstraint() && + ( ! maMIP.isRequired() || + ( maBindingExpression.hasValue() && + !maBindingExpression.getString().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 = + getModelImpl() == 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 ); + } + else if( ! 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() ); + } + } + else if( ! maMIP.isConstraint() ) + { + sReason = maMIP.getConstraintExplanation(); + } + else if( 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!" ); + + return sReason; +} + + +EvaluationContext Binding::getEvaluationContext() const +{ + OSL_ENSURE( getModelImpl() != nullptr, "need model impl" ); + EvaluationContext aContext = getModelImpl()->getEvaluationContext(); + aContext.mxNamespaces = getBindingNamespaces(); + return aContext; +} + +::std::vector<EvaluationContext> Binding::getMIPEvaluationContexts() +{ + OSL_ENSURE( getModelImpl() != nullptr, "need model impl" ); + + // bind (in case we were not bound before) + bind(); + return _getMIPEvaluationContexts(); +} + + +css::uno::Sequence<sal_Int8> Binding::getUnoTunnelId() +{ + static const comphelper::UnoIdInit aImplementationId; + return aImplementationId.getSeq(); +} + + +void Binding::setBindingID( const OUString& sBindingID ) +{ + msBindingID = sBindingID; +} + +OUString Binding::getBindingExpression() const +{ + return maBindingExpression.getExpression(); +} + +void Binding::setBindingExpression( const OUString& sBindingExpression) +{ + maBindingExpression.setExpression( sBindingExpression ); + bindingModified(); +} + +OUString Binding::getReadonlyExpression() const +{ + return maReadonly.getExpression(); +} + +void Binding::setReadonlyExpression( const OUString& sReadonly) +{ + maReadonly.setExpression( sReadonly ); + bindingModified(); +} + +OUString Binding::getRelevantExpression() const +{ + return maRelevant.getExpression(); +} + +void Binding::setRelevantExpression( const OUString& sRelevant ) +{ + maRelevant.setExpression( sRelevant ); + bindingModified(); +} + +OUString Binding::getRequiredExpression() const +{ + return maRequired.getExpression(); +} + +void Binding::setRequiredExpression( const OUString& sRequired ) +{ + maRequired.setExpression( sRequired ); + bindingModified(); +} + +OUString Binding::getConstraintExpression() const +{ + return maConstraint.getExpression(); +} + +void Binding::setConstraintExpression( const OUString& sConstraint ) +{ + maConstraint.setExpression( sConstraint ); + msExplainConstraint = getResource( RID_STR_XFORMS_INVALID_CONSTRAINT, + sConstraint ); + + // TODO: This should only re-evaluate the constraint, and notify + // the validity constraint listeners; instead we currently pretend + // the entire binding was notified, which does a little too much. + bindingModified(); +} + +OUString Binding::getCalculateExpression() const +{ + return maCalculate.getExpression(); +} + +void Binding::setCalculateExpression( const OUString& sCalculate ) +{ + maCalculate.setExpression( sCalculate ); + bindingModified(); +} + + +void Binding::setType( const OUString& sTypeName ) +{ + msTypeName = sTypeName; + bindingModified(); +} + +void Binding::setBindingNamespaces( const css::uno::Reference<css::container::XNameContainer>& rNamespaces ) +{ + _setNamespaces( rNamespaces, true ); +} + +css::uno::Reference<css::container::XNameContainer> Binding::getModelNamespaces() const +{ + return _getNamespaces(); +} + +void Binding::setModelNamespaces( const css::uno::Reference<css::container::XNameContainer>& rNamespaces ) +{ + _setNamespaces( rNamespaces, false ); +} + +bool Binding::getReadOnly() const +{ + return maMIP.isReadonly(); +} + +bool Binding::getRelevant() const +{ + return maMIP.isRelevant(); +} + +bool Binding::getExternalData() const +{ + bool bExternalData = true; + if ( !mxModel.is() ) + return bExternalData; + + try + { + Reference< XPropertySet > xModelProps( mxModel, UNO_QUERY_THROW ); + OSL_VERIFY( + xModelProps->getPropertyValue( "ExternalData" ) >>= bExternalData ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("forms.xforms"); + } + return bExternalData; +} + + +void Binding::checkLive() +{ + if( ! isLive() ) + throw RuntimeException("Binding not initialized", static_cast<XValueBinding*>(this)); +} + +bool Binding::isLive() const +{ + const Model* pModel = getModelImpl(); + return pModel && pModel->isInitialized(); +} + +Model* Binding::getModelImpl() const +{ + return comphelper::getFromUnoTunnel<Model>( mxModel ); +} + +static void lcl_addListenerToNode( const Reference<XNode>& xNode, + const Reference<XEventListener>& xListener ) +{ + Reference<XEventTarget> xTarget( xNode, UNO_QUERY ); + if( !xTarget.is() ) + return; + + xTarget->addEventListener( "DOMCharacterDataModified", + xListener, false ); + xTarget->addEventListener( "DOMCharacterDataModified", + xListener, true ); + xTarget->addEventListener( "DOMAttrModified", + xListener, false ); + xTarget->addEventListener( "DOMAttrModified", + xListener, true ); + xTarget->addEventListener( "DOMAttrModified", + xListener, true ); + xTarget->addEventListener( "xforms-generic", + xListener, true ); +} + +static void lcl_removeListenerFromNode( const Reference<XNode>& xNode, + const Reference<XEventListener>& xListener ) +{ + Reference<XEventTarget> xTarget( xNode, UNO_QUERY ); + if( !xTarget.is() ) + return; + + xTarget->removeEventListener( "DOMCharacterDataModified", + xListener, false ); + xTarget->removeEventListener( "DOMCharacterDataModified", + xListener, true ); + xTarget->removeEventListener( "DOMAttrModified", + xListener, false ); + xTarget->removeEventListener( "DOMAttrModified", + xListener, true ); + xTarget->removeEventListener( "xforms-generic", + xListener, true ); +} + +::std::vector<EvaluationContext> Binding::_getMIPEvaluationContexts() const +{ + OSL_ENSURE( getModelImpl() != nullptr, "need model impl" ); + + // iterate over nodes of bind expression and create + // EvaluationContext for each + PathExpression::NodeVector_t aNodes = maBindingExpression.getNodeList(); + ::std::vector<EvaluationContext> aVector; + for (auto const& node : aNodes) + { + OSL_ENSURE( node.is(), "no node?" ); + + // create proper evaluation context for this MIP + aVector.emplace_back( node, getModel(), getBindingNamespaces() ); + } + return aVector; +} + +void Binding::bind( bool bForceRebind ) +{ + if( ! mxModel.is() ) + throw RuntimeException("Binding has no Model", 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 + + + // 1) evaluate the binding expression + EvaluationContext aContext = getEvaluationContext(); + maBindingExpression.evaluate( aContext ); + if( ! maBindingExpression.getNode().is() ) + { + // 1b) create node (if valid element name) + if( isValidQName( maBindingExpression.getExpression(), + aContext.mxNamespaces ) ) + { + aContext.mxContextNode->appendChild( + aContext.mxContextNode->getOwnerDocument()->createElement( + maBindingExpression.getExpression() ) ); + maBindingExpression.evaluate( aContext ); + OSL_ENSURE( maBindingExpression.getNode().is(), + "we should bind to the newly inserted node!" ); + } + } + PathExpression::NodeVector_t aNodes = maBindingExpression.getNodeList(); + + // 2) register suitable listeners on the instance (and remove old ones) + if( maEventNodes.empty() || bForceRebind ) + { + for (auto const& eventNode : maEventNodes) + lcl_removeListenerFromNode( eventNode, this ); + maEventNodes.clear(); + if( isSimpleBinding() ) + maEventNodes.insert(maEventNodes.end(), aNodes.begin(), aNodes.end()); + else + maEventNodes.emplace_back( aContext.mxContextNode->getOwnerDocument(), + UNO_QUERY_THROW ); + for (auto const& eventNode : maEventNodes) + lcl_addListenerToNode( eventNode, this ); + } + + // 3) remove old MIPs defined by this binding + Model* pModel = getModelImpl(); + OSL_ENSURE( pModel != nullptr, "need model" ); + pModel->removeMIPs( this ); + + // 4) calculate all MIPs + ::std::vector<EvaluationContext> aMIPContexts = _getMIPEvaluationContexts(); + for (auto & context : aMIPContexts) + { + EvaluationContext& rContext = context; + + // evaluate calculate expression (and push value into instance) + // (prevent recursion using mbInCalculate + if( ! maCalculate.isEmptyExpression() ) + { + if( ! mbInCalculate ) + { + mbInCalculate = true; + maCalculate.evaluate( rContext ); + pModel->setSimpleContent( rContext.mxContextNode, + maCalculate.getString() ); + mbInCalculate = false; + } + } + + // now evaluate remaining MIPs in the 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 + pModel->addMIP( this, rContext.mxContextNode, getLocalMIP() ); + } +} + + +// helper for Binding::valueModified +static void lcl_modified( const css::uno::Reference<css::util::XModifyListener>& xListener, + const Reference<XInterface>& xSource ) +{ + OSL_ENSURE( xListener.is(), "no listener?" ); + xListener->modified( EventObject( xSource ) ); +} + +// helper for Binding::valueModified +static void lcl_listentry( const css::uno::Reference<css::form::binding::XListEntryListener>& xListener, + const Reference<XInterface>& xSource ) +{ + OSL_ENSURE( xListener.is(), "no listener?" ); + // TODO: send fine granular events + xListener->allEntriesChanged( EventObject( xSource ) ); +} + +// helper for Binding::valueModified +static void lcl_validate( const css::uno::Reference<css::form::validation::XValidityConstraintListener>& xListener, + const Reference<XInterface>& xSource ) +{ + OSL_ENSURE( xListener.is(), "no listener?" ); + xListener->validityConstraintChanged( EventObject( xSource ) ); +} + + +void Binding::valueModified() +{ + // defer notifications, if so desired + if( mnDeferModifyNotifications > 0 ) + { + mbValueModified = true; + return; + } + mbValueModified = false; + + // query MIP used by our first node (also note validity) + Reference<XNode> xNode = maBindingExpression.getNode(); + maMIP = getModelImpl()->queryMIP( xNode ); + + // distribute MIPs _used_ by this binding + if( xNode.is() ) + { + notifyAndCachePropertyValue( HANDLE_ReadOnly ); + notifyAndCachePropertyValue( HANDLE_Relevant ); + } + + // iterate over _value_ listeners and send each a modified signal, + // using this object as source (will also update validity, because + // control will query once the value has changed) + Reference<XInterface> xSource = static_cast<XPropertySet*>( this ); + ::std::for_each( maModifyListeners.begin(), + maModifyListeners.end(), + ::std::bind( lcl_modified, std::placeholders::_1, xSource ) ); + ::std::for_each( maListEntryListeners.begin(), + maListEntryListeners.end(), + ::std::bind( lcl_listentry, std::placeholders::_1, xSource ) ); + ::std::for_each( maValidityListeners.begin(), + maValidityListeners.end(), + ::std::bind( lcl_validate, std::placeholders::_1, xSource ) ); + + // now distribute MIPs to children + if( xNode.is() ) + distributeMIP( xNode->getFirstChild() ); +} + +void Binding::distributeMIP( const css::uno::Reference<css::xml::dom::XNode> & rxNode ) { + + rtl::Reference<css::xforms::XFormsEventConcrete> pEvent = new css::xforms::XFormsEventConcrete; + pEvent->initXFormsEvent("xforms-generic", true, false); + + // naive depth-first traversal + css::uno::Reference<css::xml::dom::XNode> xNode( rxNode ); + while(xNode.is()) { + + // 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); + + xNode = xNode->getNextSibling(); + } +} + +void Binding::bindingModified() +{ + // defer notifications, if so desired + if( mnDeferModifyNotifications > 0 ) + { + mbBindingModified = true; + return; + } + mbBindingModified = false; + + // rebind (if live); then call valueModified + // A binding should be inert until its model is fully constructed. + if( isLive() ) + { + bind( true ); + valueModified(); + } +} + + +MIP Binding::getLocalMIP() const +{ + MIP aMIP; + + if( maReadonly.hasValue() ) + aMIP.setReadonly( maReadonly.getBool() ); + if( maRelevant.hasValue() ) + aMIP.setRelevant( maRelevant.getBool( true ) ); + if( maRequired.hasValue() ) + aMIP.setRequired( maRequired.getBool() ); + if( maConstraint.hasValue() ) + { + aMIP.setConstraint( maConstraint.getBool( true ) ); + if( ! aMIP.isConstraint() ) + aMIP.setConstraintExplanation( msExplainConstraint ); + } + if( !msTypeName.isEmpty() ) + aMIP.setTypeName( msTypeName ); + + // calculate: only handle presence of calculate; value set elsewhere + aMIP.setHasCalculate( !maCalculate.isEmptyExpression() ); + + return aMIP; +} + +css::uno::Reference<css::xsd::XDataType> Binding::getDataType() const +{ + OSL_ENSURE( getModel().is(), "need model" ); + OSL_ENSURE( getModel()->getDataTypeRepository().is(), "need types" ); + + Reference<XDataTypeRepository> xRepository = + getModel()->getDataTypeRepository(); + OUString sTypeName = maMIP.getTypeName(); + + return ( xRepository.is() && xRepository->hasByName( sTypeName ) ) + ? Reference<XDataType>( xRepository->getByName( sTypeName ), UNO_QUERY) + : Reference<XDataType>( nullptr ); +} + +bool Binding::isValid_DataType() const +{ + Reference<XDataType> xDataType = getDataType(); + return !xDataType.is() + || xDataType->validate( maBindingExpression.getString() ); +} + +OUString Binding::explainInvalid_DataType() +{ + Reference<XDataType> xDataType = getDataType(); + return xDataType.is() + ? xDataType->explainInvalid( maBindingExpression.getString() ) + : OUString(); +} + +void Binding::clear() +{ + // remove MIPs contributed by this binding + Model* pModel = getModelImpl(); + if( pModel != nullptr ) + pModel->removeMIPs( this ); + + // remove all references + for (auto const& eventNode : maEventNodes) + lcl_removeListenerFromNode( eventNode, this ); + maEventNodes.clear(); + + // clear expressions + maBindingExpression.clear(); + maReadonly.clear(); + maRelevant.clear(); + maRequired.clear(); + maConstraint.clear(); + maCalculate.clear(); + + // TODO: what about our listeners? +} + + +static void lcl_removeOtherNamespaces( const css::uno::Reference<css::container::XNameContainer>& xFrom, + css::uno::Reference<css::container::XNameContainer> const & xTo ) +{ + OSL_ENSURE( xFrom.is(), "no source" ); + OSL_ENSURE( xTo.is(), "no target" ); + + // iterate over name in source + Sequence<OUString> aNames = xTo->getElementNames(); + sal_Int32 nNames = aNames.getLength(); + const OUString* pNames = aNames.getConstArray(); + for( sal_Int32 i = 0; i < nNames; i++ ) + { + const OUString& rName = pNames[i]; + + if( ! xFrom->hasByName( rName ) ) + xTo->removeByName( rName ); + } +} + +/** copy namespaces from one namespace container into another + * @param bOverwrite true: overwrite namespaces in target + * false: do not overwrite namespaces in target + * @param bMove true: move namespaces (i.e., delete in source) + * false: copy namespaces (do not modify source) + * @param bFromSource true: use elements from source + * false: use only elements from target + */ +static void 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; + + // and now... ACTION! + if( bCopy ) + { + if( bInTarget ) + xTo->replaceByName( rName, xFrom->getByName( rName ) ); + else + xTo->insertByName( rName, xFrom->getByName( rName ) ); + } + } +} + +// implement get*Namespaces() +// (identical for both variants) +css::uno::Reference<css::container::XNameContainer> Binding::_getNamespaces() const +{ + css::uno::Reference<css::container::XNameContainer> xNamespaces = new NameContainer<OUString>(); + lcl_copyNamespaces( mxNamespaces, xNamespaces, true ); + + // merge model's with binding's own namespaces + Model* pModel = getModelImpl(); + if( pModel != nullptr ) + lcl_copyNamespaces( pModel->getNamespaces(), xNamespaces, false ); + + return xNamespaces; +} + +// implement set*Namespaces() +// bBinding = true: setBindingNamespaces, otherwise: setModelNamespaces +void Binding::_setNamespaces( const css::uno::Reference<css::container::XNameContainer>& rNamespaces, + bool bBinding ) +{ + Model* pModel = getModelImpl(); + css::uno::Reference<css::container::XNameContainer> xModelNamespaces = ( pModel != nullptr ) + ? pModel->getNamespaces() + : nullptr; + OSL_ENSURE( ( pModel != nullptr ) == xModelNamespaces.is(), "no model nmsp?"); + + // remove deleted namespaces + lcl_removeOtherNamespaces( rNamespaces, mxNamespaces ); + if( !bBinding && xModelNamespaces.is() ) + lcl_removeOtherNamespaces( rNamespaces, xModelNamespaces ); + + // copy namespaces as appropriate + Sequence<OUString> aNames = rNamespaces->getElementNames(); + sal_Int32 nNames = aNames.getLength(); + const OUString* pNames = aNames.getConstArray(); + for( sal_Int32 i = 0; i < nNames; i++ ) + { + const OUString& rName = pNames[i]; + Any aValue = rNamespaces->getByName( rName ); + + // determine whether the namespace should go into model's or + // into binding's namespaces + bool bLocal = + ! xModelNamespaces.is() + || mxNamespaces->hasByName( rName ) + || ( bBinding + && xModelNamespaces.is() + && xModelNamespaces->hasByName( rName ) ); + + // write namespace into the appropriate namespace container + css::uno::Reference<css::container::XNameContainer>& rWhich = bLocal ? mxNamespaces : xModelNamespaces; + OSL_ENSURE( rWhich.is(), "whoops" ); + if( rWhich->hasByName( rName ) ) + rWhich->replaceByName( rName, aValue ); + else + rWhich->insertByName( rName, aValue ); + + // always 'promote' namespaces from binding to model, if equal + if( xModelNamespaces.is() + && xModelNamespaces->hasByName( rName ) + && mxNamespaces->hasByName( rName ) + && xModelNamespaces->getByName( rName ) == mxNamespaces->getByName( rName ) ) + { + mxNamespaces->removeByName( rName ); + } + } + + // ... done. But we modified the binding! + bindingModified(); +} + +void Binding::_checkBindingID() +{ + if( !getModel().is() ) + return; + + Reference<XNameAccess> xBindings( getModel()->getBindings(), UNO_QUERY_THROW ); + if( !msBindingID.isEmpty() ) + return; + + // no binding ID? then make one up! + OUString sIDPrefix = getResource( RID_STR_XFORMS_BINDING_UI_NAME ) + " "; + sal_Int32 nNumber = 0; + OUString sName; + do + { + nNumber++; + sName = sIDPrefix + OUString::number( nNumber ); + } + while( xBindings->hasByName( sName ) ); + setBindingID( sName ); +} + + +// XValueBinding + + +css::uno::Sequence<css::uno::Type> Binding::getSupportedValueTypes() +{ + return Convert::get().getTypes(); +} + +sal_Bool Binding::supportsType( const css::uno::Type& rType ) +{ + return Convert::get().hasType( rType ); +} + +css::uno::Any Binding::getValue( const css::uno::Type& rType ) +{ + // first, check for model + checkLive(); + + // second, check for type + if( ! supportsType( rType ) ) + throw IncompatibleTypesException("type unsupported", static_cast<XValueBinding*>(this)); + + // return string value (if present; else return empty Any) + css::uno::Any result; + if(maBindingExpression.hasValue()) { + OUString pathExpr(maBindingExpression.getString()); + Convert &rConvert = Convert::get(); + result = rConvert.toAny(pathExpr,rType); + } + + return result; +} + +void Binding::setValue( const css::uno::Any& aValue ) +{ + // first, check for model + checkLive(); + + // check for supported type + if( ! supportsType( aValue.getValueType() ) ) + throw IncompatibleTypesException("type unsupported", static_cast<XValueBinding*>(this)); + + if( !maBindingExpression.hasValue() ) + throw InvalidBindingStateException("no suitable node found", static_cast<XValueBinding*>(this)); + + css::uno::Reference<css::xml::dom::XNode> xNode = maBindingExpression.getNode(); + if( !xNode.is() ) + throw InvalidBindingStateException("no suitable node found", static_cast<XValueBinding*>(this)); + + OUString sValue = Convert::get().toXSD( aValue ); + bool bSuccess = getModelImpl()->setSimpleContent( xNode, sValue ); + if( ! bSuccess ) + throw InvalidBindingStateException("can't set value", static_cast<XValueBinding*>(this)); + + +} + + +// XListEntry Source + + +sal_Int32 Binding::getListEntryCount() +{ + // first, check for model + checkLive(); + + // return size of node list + return maBindingExpression.getNodeList().size(); +} + +static void lcl_getString( const Reference<XNode>& xNode, OUStringBuffer& rBuffer ) +{ + if( xNode->getNodeType() == NodeType_TEXT_NODE + || xNode->getNodeType() == NodeType_ATTRIBUTE_NODE ) + { + rBuffer.append( xNode->getNodeValue() ); + } + else + { + for( Reference<XNode> xChild = xNode->getFirstChild(); + xChild.is(); + xChild = xChild->getNextSibling() ) + { + lcl_getString( xChild, rBuffer ); + } + } +} + +static OUString lcl_getString( const Reference<XNode>& xNode ) +{ + OUStringBuffer aBuffer; + lcl_getString( xNode, aBuffer ); + return aBuffer.makeStringAndClear(); +} + +OUString Binding::getListEntry( sal_Int32 nPosition ) +{ + // first, check for model + checkLive(); + + // check bounds and return proper item + PathExpression::NodeVector_t aNodes = maBindingExpression.getNodeList(); + if( nPosition < 0 || o3tl::make_unsigned(nPosition) >= aNodes.size() ) + throw IndexOutOfBoundsException("", static_cast<XValueBinding*>(this)); + return lcl_getString( aNodes[ nPosition ] ); +} + +Sequence<OUString> Binding::getAllListEntries() +{ + // first, check for model + checkLive(); + + // create sequence of string values + PathExpression::NodeVector_t aNodes = maBindingExpression.getNodeList(); + Sequence<OUString> aSequence( aNodes.size() ); + OUString* pSequence = aSequence.getArray(); + for( sal_Int32 n = 0; n < aSequence.getLength(); n++ ) + { + pSequence[n] = lcl_getString( aNodes[n] ); + } + + return aSequence; +} + +void Binding::addListEntryListener( const css::uno::Reference<css::form::binding::XListEntryListener>& xListener ) +{ + OSL_ENSURE( xListener.is(), "need listener!" ); + if( ::std::find( maListEntryListeners.begin(), + maListEntryListeners.end(), + xListener) + == maListEntryListeners.end() ) + maListEntryListeners.push_back( xListener ); +} + +void Binding::removeListEntryListener( const css::uno::Reference<css::form::binding::XListEntryListener>& xListener ) +{ + XListEntryListeners_t::iterator aIter = + ::std::find( maListEntryListeners.begin(), maListEntryListeners.end(), + xListener ); + if( aIter != maListEntryListeners.end() ) + maListEntryListeners.erase( aIter ); +} + + +// XValidator + + +sal_Bool Binding::isValid( const css::uno::Any& ) +{ + // first, check for model + checkLive(); + + // ignore value; determine validate only on current data + return isValid(); +} + +OUString Binding::explainInvalid( + const css::uno::Any& /*Value*/ ) +{ + // first, check for model + checkLive(); + + // ignore value; determine explanation only on current data + return explainInvalid(); +} + +void Binding::addValidityConstraintListener( + const css::uno::Reference<css::form::validation::XValidityConstraintListener>& xListener ) +{ + OSL_ENSURE( xListener.is(), "need listener!" ); + if( ::std::find(maValidityListeners.begin(), maValidityListeners.end(), xListener) + == maValidityListeners.end() ) + maValidityListeners.push_back( xListener ); +} + +void Binding::removeValidityConstraintListener( + const css::uno::Reference<css::form::validation::XValidityConstraintListener>& xListener ) +{ + XValidityConstraintListeners_t::iterator aIter = + ::std::find( maValidityListeners.begin(), maValidityListeners.end(), + xListener ); + if( aIter != maValidityListeners.end() ) + maValidityListeners.erase( aIter ); +} + + +// xml::dom::event::XEventListener + + +void Binding::handleEvent( const css::uno::Reference<css::xml::dom::events::XEvent>& xEvent ) +{ + OUString sType(xEvent->getType()); + //OUString sEventMIPChanged("xforms-generic"); + //if(sType.equals(sEventMIPChanged)) { + if(sType == "xforms-generic") { + + // 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(); +} + + +// lang::XUnoTunnel + + +sal_Int64 Binding::getSomething( const css::uno::Sequence<sal_Int8>& xId ) +{ + return comphelper::getSomethingImpl(xId, this); +} + + +// XCloneable + + +css::uno::Reference<css::util::XCloneable> SAL_CALL Binding::createClone() +{ + Reference< XPropertySet > xClone; + + Model* pModel = getModelImpl(); + if ( pModel ) + xClone = pModel->cloneBinding( this ); + else + { + xClone = new Binding; + copy( this, xClone ); + } + return css::uno::Reference<css::util::XCloneable>( xClone, UNO_QUERY ); +} + + +// property set implementations + +void Binding::initializePropertySet() +{ + registerProperty( css::beans::Property("BindingID", HANDLE_BindingID, cppu::UnoType<OUString>::get(), css::beans::PropertyAttribute::BOUND ), + new DirectPropertyAccessor< Binding, OUString >(this, &Binding::setBindingID, &Binding::getBindingID)); + + registerProperty( css::beans::Property("BindingExpression", HANDLE_BindingExpression, cppu::UnoType<OUString>::get(), css::beans::PropertyAttribute::BOUND ), + new DirectPropertyAccessor< Binding, OUString >(this, &Binding::setBindingExpression, &Binding::getBindingExpression)); + + registerProperty( css::beans::Property("Model", HANDLE_Model, cppu::UnoType<css::uno::Reference<css::xforms::XModel>>::get(), css::beans::PropertyAttribute::BOUND | css::beans::PropertyAttribute::READONLY ), + new DirectPropertyAccessor< Binding, css::uno::Reference<css::xforms::XModel> >(this, nullptr, &Binding::getModel)); + + registerProperty( css::beans::Property("BindingNamespaces", HANDLE_BindingNamespaces, cppu::UnoType<css::uno::Reference<css::container::XNameContainer>>::get(), css::beans::PropertyAttribute::BOUND ), + new DirectPropertyAccessor< Binding, css::uno::Reference<css::container::XNameContainer> >(this, &Binding::setBindingNamespaces, &Binding::getBindingNamespaces)); + + registerProperty( css::beans::Property("ModelNamespaces", HANDLE_ModelNamespaces, cppu::UnoType<css::uno::Reference<css::container::XNameContainer>>::get(), css::beans::PropertyAttribute::BOUND ), + new DirectPropertyAccessor< Binding, css::uno::Reference<css::container::XNameContainer> >(this, &Binding::setModelNamespaces, &Binding::getModelNamespaces)); + + registerProperty( css::beans::Property("ModelID", HANDLE_ModelID, cppu::UnoType<OUString>::get(), css::beans::PropertyAttribute::BOUND | css::beans::PropertyAttribute::READONLY ), + new DirectPropertyAccessor< Binding, OUString >(this, nullptr, &Binding::getModelID)); + + registerProperty( css::beans::Property("ReadonlyExpression", HANDLE_ReadonlyExpression, cppu::UnoType<OUString>::get(), css::beans::PropertyAttribute::BOUND ), + new DirectPropertyAccessor< Binding, OUString >(this, &Binding::setReadonlyExpression, &Binding::getReadonlyExpression)); + + registerProperty( css::beans::Property("RelevantExpression", HANDLE_RelevantExpression, cppu::UnoType<OUString>::get(), css::beans::PropertyAttribute::BOUND ), + new DirectPropertyAccessor< Binding, OUString >(this, &Binding::setRelevantExpression, &Binding::getRelevantExpression)); + + registerProperty( css::beans::Property("RequiredExpression", HANDLE_RequiredExpression, cppu::UnoType<OUString>::get(), css::beans::PropertyAttribute::BOUND ), + new DirectPropertyAccessor< Binding, OUString >(this, &Binding::setRequiredExpression, &Binding::getRequiredExpression)); + + registerProperty( css::beans::Property("ConstraintExpression", HANDLE_ConstraintExpression, cppu::UnoType<OUString>::get(), css::beans::PropertyAttribute::BOUND ), + new DirectPropertyAccessor< Binding, OUString >(this, &Binding::setConstraintExpression, &Binding::getConstraintExpression)); + + registerProperty( css::beans::Property("CalculateExpression", HANDLE_CalculateExpression, cppu::UnoType<OUString>::get(), css::beans::PropertyAttribute::BOUND ), + new DirectPropertyAccessor< Binding, OUString >(this, &Binding::setCalculateExpression, &Binding::getCalculateExpression)); + + registerProperty( css::beans::Property("Type", HANDLE_Type, cppu::UnoType<OUString>::get(), css::beans::PropertyAttribute::BOUND ), + new DirectPropertyAccessor< Binding, OUString >(this, &Binding::setType, &Binding::getType)); + + registerProperty( css::beans::Property("ReadOnly", HANDLE_ReadOnly, cppu::UnoType<bool>::get(), css::beans::PropertyAttribute::BOUND | css::beans::PropertyAttribute::READONLY ), + new DirectPropertyAccessor< Binding, bool >(this, nullptr, &Binding::getReadOnly)); + + registerProperty( css::beans::Property("Relevant", HANDLE_Relevant, cppu::UnoType<bool>::get(), css::beans::PropertyAttribute::BOUND | css::beans::PropertyAttribute::READONLY ), + new DirectPropertyAccessor< Binding, bool >(this, nullptr, &Binding::getRelevant)); + + registerProperty( css::beans::Property("ExternalData", HANDLE_ExternalData, cppu::UnoType<sal_Bool>::get(), css::beans::PropertyAttribute::BOUND | css::beans::PropertyAttribute::READONLY ), + new BooleanPropertyAccessor< Binding >(this, nullptr, &Binding::getExternalData)); + + initializePropertyValueCache( HANDLE_ReadOnly ); + initializePropertyValueCache( HANDLE_Relevant ); + initializePropertyValueCache( HANDLE_ExternalData ); +} + +void Binding::addModifyListener( + const css::uno::Reference<css::util::XModifyListener>& xListener ) +{ + OSL_ENSURE( xListener.is(), "need listener!" ); + if( ::std::find( maModifyListeners.begin(), maModifyListeners.end(), xListener ) + == maModifyListeners.end() ) + maModifyListeners.push_back( xListener ); + + // HACK: currently, we have to 'push' some MIPs to the control + // (read-only, relevant, etc.) To enable this, we need to update + // the control at least once when it registers here. + valueModified(); +} + +void Binding::removeModifyListener( + const css::uno::Reference<css::util::XModifyListener>& xListener ) +{ + ModifyListeners_t::iterator aIter = + ::std::find( maModifyListeners.begin(), maModifyListeners.end(), xListener ); + if( aIter != maModifyListeners.end() ) + maModifyListeners.erase( aIter ); +} + + +OUString Binding::getName() +{ + return getBindingID(); +} + +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 ) ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/forms/source/xforms/binding.hxx b/forms/source/xforms/binding.hxx new file mode 100644 index 000000000..d7201a5b5 --- /dev/null +++ b/forms/source/xforms/binding.hxx @@ -0,0 +1,407 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/uno/Reference.hxx> +#include <cppuhelper/implbase.hxx> +#include "propertysetbase.hxx" +#include <com/sun/star/form/binding/XValueBinding.hpp> +#include <com/sun/star/form/binding/XListEntrySource.hpp> +#include <com/sun/star/form/validation/XValidator.hpp> +#include <com/sun/star/util/XModifyBroadcaster.hpp> +#include <com/sun/star/container/XNamed.hpp> +#include <com/sun/star/xml/dom/events/XEventListener.hpp> +#include <com/sun/star/lang/XUnoTunnel.hpp> +#include <com/sun/star/util/XCloneable.hpp> + +#include "pathexpression.hxx" +#include "boolexpression.hxx" +#include "mip.hxx" +#include <rtl/ustring.hxx> +#include <vector> + +// forward declaractions +namespace xforms +{ + class Model; + class EvaluationContext; +} +namespace com::sun::star { + namespace xml { + namespace xpath { class XXPathAPI; } + namespace dom + { + class XNode; + class XNodeList; + } + } + namespace container { class XNameContainer; } + namespace xforms { class XModel; } + namespace xsd { class XDataType; } +} + + +namespace xforms +{ + +/** An XForms Binding. Contains: + * # a connection to its model + * # an ID + * # a binding expression + * # model item properties + * # (NOT YET IMPLEMENTED) child bindings (sequence of) + * + * See http://www.w3.org/TR/xforms/ for more information. + */ + +typedef cppu::ImplInheritanceHelper< + PropertySetBase, + css::form::binding::XValueBinding, + css::form::binding::XListEntrySource, + css::form::validation::XValidator, + css::util::XModifyBroadcaster, + css::container::XNamed, + css::xml::dom::events::XEventListener, + css::lang::XUnoTunnel, + css::util::XCloneable +> Binding_t; + +class Binding : public Binding_t +{ +public: + typedef std::vector<css::uno::Reference<css::util::XModifyListener> > ModifyListeners_t; + typedef std::vector<css::uno::Reference<css::form::validation::XValidityConstraintListener> > XValidityConstraintListeners_t; + typedef std::vector<css::uno::Reference<css::form::binding::XListEntryListener> > XListEntryListeners_t; + + +private: + + /// the Model to which this Binding belongs; may be NULL + css::uno::Reference<css::xforms::XModel> mxModel; + + /// binding-ID. A document-wide unique ID for this binding element. + OUString msBindingID; + + /// an XPath-expression to be instantiated on the data instance + PathExpression maBindingExpression; + + /// an XPath-expression to determine read-only status + BoolExpression maReadonly; + + /// an XPath-expression to determine relevance + BoolExpression maRelevant; + + /// an XPath-expression to determine if item is required + BoolExpression maRequired; + + /// an XPath-expression to determine if item is valid + BoolExpression maConstraint; + + /// user-readable explanation of the constraint + OUString msExplainConstraint; + + /// an XPath-expression to calculate values + ComputedExpression maCalculate; + + /// the XML namespaces used for XML names/XPath-expressions in this binding + css::uno::Reference<css::container::XNameContainer> mxNamespaces; + + /// a type name + OUString msTypeName; + + /// modify listeners + ModifyListeners_t maModifyListeners; + + /// list entry listener + XListEntryListeners_t maListEntryListeners; + + /// validity listeners; + XValidityConstraintListeners_t maValidityListeners; + + /// nodes on which we are listening for events + std::vector<css::uno::Reference<css::xml::dom::XNode> > maEventNodes; + + /// the current MIP object for the first node we are bound to + MIP maMIP; + + /// flag to detect recursions in calculate + bool mbInCalculate; + + // flags to manage deferred notifications: + /// if >0, valueModified() and bindingModified() will only set flags + sal_Int32 mnDeferModifyNotifications; + bool mbValueModified; /// if true, valueModified needs to be called + bool mbBindingModified; /// if true, bindingModified needs to be called + + + void initializePropertySet(); + + +public: + Binding(); + virtual ~Binding() override; + + + // property methods: get/set value + + + css::uno::Reference<css::xforms::XModel> getModel() const { return mxModel;} /// get XForms model + void _setModel( const css::uno::Reference<css::xforms::XModel>& ); /// set XForms model (only called by Model) + + + OUString getModelID() const; /// get ID of XForms model + + OUString getBindingID() const { return msBindingID;} /// get ID for this binding + void setBindingID( const OUString& ); /// set ID for this binding + + OUString getBindingExpression() const; /// get binding expression + void setBindingExpression( const OUString& ); /// set binding exp. + + // MIPs (model item properties) + + OUString getReadonlyExpression() const; /// get read-only MIP + void setReadonlyExpression( const OUString& ); /// set read-only MIP + + OUString getRelevantExpression() const; /// get relevant MIP + void setRelevantExpression( const OUString& ); /// set relevant MIP + + OUString getRequiredExpression() const; /// get required MIP + void setRequiredExpression( const OUString& ); /// set required MIP + + OUString getConstraintExpression() const; /// get constraint MIP + void setConstraintExpression( const OUString& );/// set constraint MIP + + OUString getCalculateExpression() const; /// get calculate MIP + void setCalculateExpression( const OUString& ); /// set calculate MIP + + OUString getType() const { return msTypeName;} /// get type name MIP (static) + void setType( const OUString& ); /// set type name MIP (static) + + // a binding expression can only be interpreted with respect to + // suitable namespace declarations. We collect those in the model and in a binding. + + // access to a binding's namespace + // (set-method only changes local namespaces (but may add to model)) + css::uno::Reference<css::container::XNameContainer> getBindingNamespaces() const { return mxNamespaces; } + void setBindingNamespaces( const css::uno::Reference<css::container::XNameContainer>& ); /// get binding nmsp. + + // access to the model's namespaces + // (set-method changes model's namespaces (unless a local one is present)) + css::uno::Reference<css::container::XNameContainer> getModelNamespaces() const; /// set model namespaces + void setModelNamespaces( const css::uno::Reference<css::container::XNameContainer>& ); /// get model nmsp. + + + // read-only properties that map MIPs to control data source properties + bool getReadOnly() const; // MIP readonly + bool getRelevant() const; // MIP relevant + bool getExternalData() const; // mapped from model's ExternalData property + + + // missing binding properties: + // - type (static; default: xsd:string) + // - minOccurs/maxOccurs (computed XPath; default: 0/inf) + // - p3ptype (static; no default) + + + /// get this binding's context node + xforms::EvaluationContext getEvaluationContext() const; + + /// get evaluation contexts for this binding's MIPs + std::vector<xforms::EvaluationContext> getMIPEvaluationContexts(); + + /// get nodeset the bind is bound to + css::uno::Reference<css::xml::dom::XNodeList> getXNodeList(); + + /// heuristically determine whether this binding is simple binding + /// (here: simple binding == does not depend on other parts of the + /// instance, it's not a 'dynamic' binding) + bool isSimpleBinding() const; + + /// heuristically determine whether this binding's binding + /// expression is simple + bool isSimpleBindingExpression() const; + + /// update this binding (e.g. called by model for refresh ) + void update(); + + /// prevent change notifications being sent to controls + void deferNotifications( bool ); + + /// is this binding valid? (are constraint, type and required MIPs ok?) + bool isValid() const; + + /// determine whether this binding currently performs a useful + /// function, r whether is may be discarded + bool isUseful() const; + + /// explain why binding is invalid + OUString explainInvalid(); + + + // the ID for XUnoTunnel calls + static css::uno::Sequence<sal_Int8> getUnoTunnelId(); + + +private: + /// check whether object is live, and throw suitable exception if not + /// (to be used be API methods before acting on the object) + /// + /// @throws css::uno::RuntimeException + void checkLive(); + + /// determine whether object is live + /// live: has model, and model has been initialized + bool isLive() const; + + /// get the model implementation + xforms::Model* getModelImpl() const; + + /// get MIP evaluation contexts + /// (only valid if control has already been bound) + std::vector<xforms::EvaluationContext> _getMIPEvaluationContexts() const; + + /// bind this binding, and pre-compute the affected nodes + void bind( bool bForceRebind = false ); + + /// the binding value has been changed: + /// trigger a modified event on all modified listeners + void valueModified(); + + /// the binding itself has changed: + /// force rebind, then call valueModified() + void bindingModified(); + + + /// set MIPs defined by this binding on MIP item + MIP getLocalMIP() const; + + /// get the data type that applies to this binding + css::uno::Reference<css::xsd::XDataType> getDataType() const; + + /// determine whether binding is valid according to the given data type + bool isValid_DataType() const; + + /// explain validity of binding with respect to the given data type + OUString explainInvalid_DataType(); + + /// 'clear' this binding - remove all listeners, etc. + void clear(); + + /// distribute MIPs from current node recursively to children + void distributeMIP( const css::uno::Reference<css::xml::dom::XNode> &rxNode ); + + /// implement get*Namespaces() + css::uno::Reference<css::container::XNameContainer> _getNamespaces() const; + + /// implement set*Namespaces() + void _setNamespaces( const css::uno::Reference<css::container::XNameContainer>&, bool bBinding ); + + /// set a useful default binding ID (if none is set) + void _checkBindingID(); + +public: + + virtual css::uno::Sequence<css::uno::Type> SAL_CALL getSupportedValueTypes() override; + + virtual sal_Bool SAL_CALL supportsType( const css::uno::Type& aType ) override; + + virtual css::uno::Any SAL_CALL getValue( const css::uno::Type& aType ) override; + + virtual void SAL_CALL setValue( const css::uno::Any& aValue ) override; + + + // XListEntrySource + + + virtual sal_Int32 SAL_CALL getListEntryCount() override; + + virtual OUString SAL_CALL getListEntry( sal_Int32 nPosition ) override; + + virtual css::uno::Sequence<OUString> SAL_CALL getAllListEntries() override; + + virtual void SAL_CALL addListEntryListener( const css::uno::Reference<css::form::binding::XListEntryListener>& ) override; + + virtual void SAL_CALL removeListEntryListener( const css::uno::Reference<css::form::binding::XListEntryListener>&) override; + + + // XValidator: + + + virtual sal_Bool SAL_CALL isValid( + const css::uno::Any& ) override; + + virtual OUString SAL_CALL explainInvalid( + const css::uno::Any& ) override; + + virtual void SAL_CALL addValidityConstraintListener( + const css::uno::Reference<css::form::validation::XValidityConstraintListener>& xListener ) override; + + virtual void SAL_CALL removeValidityConstraintListener( + const css::uno::Reference<css::form::validation::XValidityConstraintListener>& xListener ) override; + + + // XModifyBroadcaster & friends: + // inform listeners about changes in our values + + +public: + + virtual void SAL_CALL addModifyListener( + const css::uno::Reference<css::util::XModifyListener>& xListener ) override; + + virtual void SAL_CALL removeModifyListener( + const css::uno::Reference<css::util::XModifyListener>& xListener ) override; + + + // XNamed: + // get/set name + + +public: + + virtual OUString SAL_CALL getName() override; + + virtual void SAL_CALL setName( const OUString& ) override; + + + // xml::dom::event::XEventListener + // receive an event if our node changed + + + virtual void SAL_CALL handleEvent( + const css::uno::Reference<css::xml::dom::events::XEvent>& xEvent ) override; + + + // XUnoTunnel + + + virtual sal_Int64 SAL_CALL getSomething( const css::uno::Sequence<sal_Int8>& ) override; + + + // XCloneable + + + virtual css::uno::Reference<css::util::XCloneable> SAL_CALL createClone() override; +}; + + +} // namespace xforms + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/forms/source/xforms/boolexpression.cxx b/forms/source/xforms/boolexpression.cxx new file mode 100644 index 000000000..af45c8c38 --- /dev/null +++ b/forms/source/xforms/boolexpression.cxx @@ -0,0 +1,42 @@ +/* -*- 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 . + */ + +#include "boolexpression.hxx" + +namespace xforms +{ +/** BoolExpression represents a computed XPath expression that returns + * a bool value and caches the results. + * + * As this class has no virtual methods, it should never be used + * polymorphically. */ + +BoolExpression::BoolExpression() {} + +BoolExpression::~BoolExpression() {} + +void BoolExpression::setExpression(const OUString& rExpression) +{ + ComputedExpression::setExpression(rExpression); + mbIsSimple = _checkExpression(" *(true)|(false) *\\( *\\) *"); +} + +} // namespace xforms + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/forms/source/xforms/boolexpression.hxx b/forms/source/xforms/boolexpression.hxx new file mode 100644 index 000000000..ddbc13ce7 --- /dev/null +++ b/forms/source/xforms/boolexpression.hxx @@ -0,0 +1,44 @@ +/* -*- 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 . + */ + +#pragma once + +#include "computedexpression.hxx" + +namespace xforms +{ +/** BoolExpression represents a computed XPath expression that returns + * a bool value and caches the results. + * + * As this class has no virtual methods, it should never be used + * polymorphically. */ +class BoolExpression : public ComputedExpression +{ +public: + BoolExpression(); + ~BoolExpression(); + + /// set the expression string + /// (overridden for new definition of a simple expression) + void setExpression(const OUString& rExpression); +}; + +} // namespace xforms + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/forms/source/xforms/collection.hxx b/forms/source/xforms/collection.hxx new file mode 100644 index 000000000..00c7bdc39 --- /dev/null +++ b/forms/source/xforms/collection.hxx @@ -0,0 +1,280 @@ +/* -*- 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 . + */ + +#pragma once + +#include "enumeration.hxx" + +#include <cppuhelper/implbase.hxx> +#include <osl/diagnose.h> +#include <com/sun/star/container/ElementExistException.hpp> +#include <com/sun/star/container/NoSuchElementException.hpp> +#include <com/sun/star/container/XEnumeration.hpp> +#include <com/sun/star/container/XIndexReplace.hpp> +#include <com/sun/star/container/XSet.hpp> +#include <com/sun/star/container/XContainer.hpp> +#include <com/sun/star/container/XContainerListener.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/uno/Type.hxx> +#include <vector> + + +typedef cppu::WeakImplHelper< + css::container::XIndexReplace, + css::container::XSet, + css::container::XContainer> +Collection_t; + +template<class ELEMENT_TYPE> +class Collection : public Collection_t +{ +public: + typedef ELEMENT_TYPE T; + typedef std::vector<css::uno::Reference<css::container::XContainerListener> > Listeners_t; + +protected: + std::vector<T> maItems; + Listeners_t maListeners; + +public: + + Collection() {} + + const T& getItem( sal_Int32 n ) const + { + OSL_ENSURE( isValidIndex(n), "invalid index" ); + OSL_ENSURE( isValid( maItems[n] ), "invalid item found" ); + return maItems[n]; + } + + void setItem( sal_Int32 n, const T& t) + { + OSL_ENSURE( isValidIndex(n), "invalid index" ); + OSL_ENSURE( isValid ( t ), "invalid item" ); + + T& aRef = maItems[ n ]; + _elementReplaced( n, t ); + _remove( aRef ); + aRef = t; + _insert( t ); + } + + bool hasItem( const T& t ) const + { + return maItems.end() != std::find( maItems.begin(), maItems.end(), t ); + } + + sal_Int32 addItem( const T& t ) + { + OSL_ENSURE( !hasItem( t ), "item to be added already present" ); + OSL_ENSURE( isValid( t ), "invalid item" ); + + maItems.push_back( t ); + _insert( t ); + _elementInserted( maItems.size() - 1 ); + return ( maItems.size() - 1 ); + } + + void removeItem( const T& t ) + { + OSL_ENSURE( hasItem( t ), "item to be removed not present" ); + OSL_ENSURE( isValid( t ), "an invalid item, funny that!" ); + + _elementRemoved( t ); + _remove( t ); + maItems.erase( std::find( maItems.begin(), maItems.end(), t ) ); + } + + bool hasItems() const + { + return maItems.size() != 0; + } + + sal_Int32 countItems() const + { + return static_cast<sal_Int32>( maItems.size() ); + } + + bool isValidIndex( sal_Int32 n ) const + { + return n >= 0 && n < static_cast<sal_Int32>( maItems.size() ); + } + + + // the following method may be overridden by sub-classes for + // customized behaviour + + /// called before insertion to determine whether item is valid + virtual bool isValid( const T& ) const { return true; } + + +protected: + + // the following methods may be overridden by sub-classes for + // customized behaviour + + /// called after item has been inserted into the collection + virtual void _insert( const T& ) { } + + /// called before item is removed from the collection + virtual void _remove( const T& ) { } + +public: + + // XElementAccess + virtual css::uno::Type SAL_CALL getElementType() override + { + return cppu::UnoType<T>::get(); + } + + virtual sal_Bool SAL_CALL hasElements() override + { + return hasItems(); + } + + // XIndexAccess : XElementAccess + virtual sal_Int32 SAL_CALL getCount() override + { + return countItems(); + } + + virtual css::uno::Any SAL_CALL getByIndex( sal_Int32 nIndex ) override + { + if( !isValidIndex( nIndex ) ) + throw css::lang::IndexOutOfBoundsException(); + return css::uno::Any( getItem( nIndex ) ); + } + + // XIndexReplace : XIndexAccess + virtual void SAL_CALL replaceByIndex( sal_Int32 nIndex, + const css::uno::Any& aElement ) override + { + T t; + if( !isValidIndex( nIndex) ) + throw css::lang::IndexOutOfBoundsException(); + if( !( aElement >>= t ) || !isValid( t ) ) + throw css::lang::IllegalArgumentException(); + setItem( nIndex, t ); + } + + // XEnumerationAccess : XElementAccess + virtual css::uno::Reference<css::container::XEnumeration> SAL_CALL createEnumeration() override + { + return new Enumeration( this ); + } + + + // XSet : XEnumerationAccess + virtual sal_Bool SAL_CALL has( const css::uno::Any& aElement ) override + { + T t; + return ( aElement >>= t ) && hasItem( t ); + } + + virtual void SAL_CALL insert( const css::uno::Any& aElement ) override + { + T t; + if( !( aElement >>= t ) || !isValid( t ) ) + throw css::lang::IllegalArgumentException(); + if( hasItem( t ) ) + throw css::container::ElementExistException(); + addItem( t ); + } + + virtual void SAL_CALL remove( const css::uno::Any& aElement ) override + { + T t; + if( !(aElement >>= t) ) + throw css::lang::IllegalArgumentException(); + if( !hasItem( t ) ) + throw css::container::NoSuchElementException(); + removeItem( t ); + } + + + // XContainer + virtual void SAL_CALL addContainerListener( + const css::uno::Reference<css::container::XContainerListener>& xListener ) override + { + OSL_ENSURE( xListener.is(), "need listener!" ); + if( std::find( maListeners.begin(), maListeners.end(), xListener) + == maListeners.end() ) + maListeners.push_back( xListener ); + } + + virtual void SAL_CALL removeContainerListener( + const css::uno::Reference<css::container::XContainerListener>& xListener ) override + { + OSL_ENSURE( xListener.is(), "need listener!" ); + Listeners_t::iterator aIter = + std::find( maListeners.begin(), maListeners.end(), xListener ); + if( aIter != maListeners.end() ) + maListeners.erase( aIter ); + } + +protected: + + // call listeners: + void _elementInserted( sal_Int32 nPos ) + { + OSL_ENSURE( isValidIndex(nPos), "invalid index" ); + css::container::ContainerEvent aEvent( + static_cast<css::container::XIndexReplace*>( this ), + css::uno::Any( nPos ), + css::uno::Any( getItem( nPos ) ), + css::uno::Any() ); + for (auto const& listener : maListeners) + { + listener->elementInserted( aEvent ); + } + } + + void _elementRemoved( const T& aOld ) + { + css::container::ContainerEvent aEvent( + static_cast<css::container::XIndexReplace*>( this ), + css::uno::Any(), + css::uno::Any( aOld ), + css::uno::Any() ); + for (auto const& listener : maListeners) + { + listener->elementRemoved( aEvent ); + } + } + + void _elementReplaced( const sal_Int32 nPos, const T& aNew ) + { + OSL_ENSURE( isValidIndex(nPos), "invalid index" ); + css::container::ContainerEvent aEvent( + static_cast<css::container::XIndexReplace*>( this ), + css::uno::Any( nPos ), + css::uno::Any( getItem( nPos ) ), + css::uno::Any( aNew ) ); + for (auto const& listener : maListeners) + { + listener->elementReplaced( aEvent ); + } + } + +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/forms/source/xforms/computedexpression.cxx b/forms/source/xforms/computedexpression.cxx new file mode 100644 index 000000000..add8d4350 --- /dev/null +++ b/forms/source/xforms/computedexpression.cxx @@ -0,0 +1,182 @@ +/* -*- 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 . + */ + + +#include "computedexpression.hxx" +#include "evaluationcontext.hxx" + +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/xml/xpath/XPathAPI.hpp> +#include <com/sun/star/xml/xpath/XPathExtension.hpp> +#include <com/sun/star/util/SearchAlgorithms2.hpp> + +#include <osl/diagnose.h> + +#include <i18nutil/searchopt.hxx> +#include <unotools/textsearch.hxx> +#include <comphelper/processfactory.hxx> + +using namespace com::sun::star::uno; +using com::sun::star::xml::xpath::XPathAPI; +using com::sun::star::xml::xpath::XXPathAPI; +using com::sun::star::xml::xpath::XPathExtension; +using com::sun::star::xml::xpath::XXPathExtension; +using com::sun::star::xml::xpath::XPathObjectType_XPATH_UNDEFINED; + + +namespace xforms +{ + +ComputedExpression::ComputedExpression() + : mbIsEmpty( true ), + mbIsSimple( true ) +{ +} + +ComputedExpression::~ComputedExpression() +{ +} + + +void ComputedExpression::setExpression( const OUString& rExpression ) +{ + // set new expression, and clear pre-computed results + msExpression = rExpression; + mbIsEmpty = _checkExpression( " *" ); + mbIsSimple = false; + mxResult.clear(); +} + + +bool ComputedExpression::_checkExpression( const char* pExpression ) const +{ + assert(pExpression && "no expression?"); + + // call RegExp engine + i18nutil::SearchOptions2 aSearchOptions; + aSearchOptions.AlgorithmType2 = css::util::SearchAlgorithms2::REGEXP; + aSearchOptions.searchString = OUString( pExpression, strlen(pExpression), RTL_TEXTENCODING_ASCII_US ); + utl::TextSearch aTextSearch( aSearchOptions ); + + sal_Int32 nLength = msExpression.getLength(); + sal_Int32 nStart = 0; + sal_Int32 nEnd = nLength; + bool bSearch = aTextSearch.SearchForward( msExpression, &nStart, &nEnd ); + + // our expression is static only if 1) we found our regexp, and 2) + // the regexp goes from beginning to end. + return ( nLength == 0 || bSearch ) + && ( nStart == 0 && nEnd == nLength ); +} + +bool ComputedExpression::isSimpleExpression() const +{ + // actual work is done by setExpression + return mbIsEmpty || mbIsSimple; +} + + +bool ComputedExpression::_evaluate( + const xforms::EvaluationContext& rContext, + const OUString& sExpression ) +{ + OSL_ENSURE( rContext.mxContextNode.is(), "no context node in context" ); + + // obtain value by evaluating XPath expression + mxResult.clear(); + try + { + mxResult = _getXPathAPI(rContext)->eval( rContext.mxContextNode, + sExpression ); + } + catch( const Exception& ) + { + ; // ignore exception -> mxResult will be empty + } + + return hasValue(); +} + +bool ComputedExpression::evaluate( const EvaluationContext& rContext ) +{ + // for simple expression we don't need to re-evaluate (if we have + // an older result); neither for empty expressions + if( mbIsEmpty || (mxResult.is() && mbIsSimple) ) + return true; + + return _evaluate( rContext, _getExpressionForEvaluation() ); +} + + +bool ComputedExpression::hasValue() const +{ + return mxResult.is() && + mxResult->getObjectType() != XPathObjectType_XPATH_UNDEFINED; +} + +void ComputedExpression::clear() +{ + mxResult.clear(); +} + + +OUString ComputedExpression::getString() const +{ + return mxResult.is() ? mxResult->getString() : OUString(); +} + +bool ComputedExpression::getBool( bool bDefault ) const +{ + return mxResult.is() ? mxResult->getBoolean() : bDefault; +} + + +Reference<XXPathAPI> ComputedExpression::_getXPathAPI(const xforms::EvaluationContext& aContext) +{ + // create XPath API, then register namespaces + Reference<XXPathAPI> xXPath( XPathAPI::create( comphelper::getProcessComponentContext() ) ); + + // register xforms extension# + Reference< XComponentContext > aComponentContext = comphelper::getProcessComponentContext(); + Reference< XXPathExtension > aExtension = XPathExtension::createWithModel(aComponentContext, aContext.mxModel, aContext.mxContextNode); + xXPath->registerExtensionInstance(aExtension); + + // register namespaces + if( aContext.mxNamespaces.is() ) + { + Sequence<OUString> aPrefixes =aContext.mxNamespaces->getElementNames(); + sal_Int32 nCount = aPrefixes.getLength(); + const OUString* pPrefixes = aPrefixes.getConstArray(); + for( sal_Int32 i = 0; i < nCount; i++ ) + { + const OUString* pNamePrefix = &pPrefixes[i]; + OUString sNameURL; + aContext.mxNamespaces->getByName( *pNamePrefix ) >>= sNameURL; + xXPath->registerNS( *pNamePrefix, sNameURL ); + } + } + + // done, so return xXPath-object + return xXPath; +} + + +} // namespace xforms + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/forms/source/xforms/computedexpression.hxx b/forms/source/xforms/computedexpression.hxx new file mode 100644 index 000000000..17f39cd21 --- /dev/null +++ b/forms/source/xforms/computedexpression.hxx @@ -0,0 +1,121 @@ +/* -*- 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 . + */ + +#pragma once + +#include <rtl/ustring.hxx> +#include <com/sun/star/uno/Reference.hxx> + +// forward declaractions +namespace com::sun::star +{ + namespace xml + { + namespace dom { class XNode; } + namespace dom { class XNodeset; } + namespace xpath { class XXPathAPI; } + namespace xpath { class XXPathObject; } + } + namespace container { class XNameContainer; } +} +namespace xforms { class EvaluationContext; } + + +namespace xforms +{ + +/** ComputedExpression represents an XPath Expression and caches results. + * + * As this class has no virtual methods, it should never be used + * polymorphically. */ +class ComputedExpression +{ + /// the expression string + OUString msExpression; + + /// is msExpression empty? + bool mbIsEmpty; + +protected: + /// is msExpression a simple expression? + bool mbIsSimple; + + /// the result from the last bind + css::uno::Reference<css::xml::xpath::XXPathObject> mxResult; + + + /// implementation of isSimpleExpression + bool _checkExpression( const char* pExpression ) const; + + /// allow manipulation of the expression before it is evaluated + // the default implementation is to do nothing... + const OUString& _getExpressionForEvaluation() const { return msExpression; } + + /// obtain a (suitable) XPathAPI implementation + static css::uno::Reference<css::xml::xpath::XXPathAPI> _getXPathAPI(const xforms::EvaluationContext& aContext); + + /// evaluate the expression relative to the content node. + bool _evaluate( const xforms::EvaluationContext& rContext, + const OUString& sExpression ); + + +public: + ComputedExpression(); + ~ComputedExpression(); + + + /// get the expression string + const OUString& getExpression() const { return msExpression;} + + /// set a new expression string + void setExpression( const OUString& rExpression ); + + /// do we have an actual expression? + bool isEmptyExpression() const { return mbIsEmpty;} + + /// heuristically determine whether this expression is 'simple', + /// i.e. whether its value will change depending on the values + /// of other nodes + bool isSimpleExpression() const; + + + /// evaluate the expression relative to the content node. + bool evaluate( const xforms::EvaluationContext& rContext ); + + + /// does this expression have a value? + bool hasValue() const; + + + /// remove value/evaluate results + void clear(); + + + // get the result of this expression as string/bool/... + // (Results will be based on the last call of evaluate(..). The caller + // must call evaluate to ensure current results.) + css::uno::Reference<css::xml::xpath::XXPathObject> const & getXPath() const { return mxResult;} + bool getBool( bool bDefault = false ) const; + OUString getString() const; + +}; + +} // namespace xforms + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/forms/source/xforms/convert.cxx b/forms/source/xforms/convert.cxx new file mode 100644 index 000000000..28d30885f --- /dev/null +++ b/forms/source/xforms/convert.cxx @@ -0,0 +1,335 @@ +/* -*- 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 . + */ + + +#include "convert.hxx" + +#include <sstream> +#include <rtl/math.hxx> +#include <rtl/ustrbuf.hxx> +#include <osl/diagnose.h> +#include <tools/date.hxx> +#include <com/sun/star/uno/Type.hxx> +#include <com/sun/star/util/Date.hpp> +#include <com/sun/star/util/DateTime.hpp> +#include <com/sun/star/util/Time.hpp> +#include <comphelper/sequence.hxx> +#include <unotools/datetime.hxx> + +using xforms::Convert; +using com::sun::star::uno::Any; +using namespace utl; + +Convert::Convert() +{ + init(); +} + +namespace +{ + + OUString lcl_toXSD_OUString( const Any& rAny ) + { OUString sStr; rAny >>= sStr; return sStr; } + + + Any lcl_toAny_OUString( const OUString& rStr ) + { return Any(rStr); } + + OUString lcl_toXSD_bool( const Any& rAny ) + { bool b = false; rAny >>= b; return b ? OUString("true") : OUString("false"); } + + Any lcl_toAny_bool( const OUString& rStr ) + { + bool b = ( rStr == "true" || rStr == "1" ); + return Any( b ); + } + + OUString lcl_toXSD_double( const Any& rAny ) + { + double f = 0.0; + rAny >>= f; + + return std::isfinite( f ) + ? rtl::math::doubleToUString( f, rtl_math_StringFormat_Automatic, + rtl_math_DecimalPlaces_Max, '.', + true ) + : OUString(); + } + + + Any lcl_toAny_double( const OUString& rString ) + { + rtl_math_ConversionStatus eStatus; + double f = rtl::math::stringToDouble( + rString, '.', ',', &eStatus ); + return ( eStatus == rtl_math_ConversionStatus_Ok ) ? Any( f ) : Any(); + } + + void lcl_appendInt32ToBuffer( const sal_Int32 _nValue, OUStringBuffer& _rBuffer, sal_Int16 _nMinDigits ) + { + if ( ( _nMinDigits >= 4 ) && ( _nValue < 1000 ) ) + _rBuffer.append( '0' ); + if ( ( _nMinDigits >= 3 ) && ( _nValue < 100 ) ) + _rBuffer.append( '0' ); + if ( ( _nMinDigits >= 2 ) && ( _nValue < 10 ) ) + _rBuffer.append( '0' ); + _rBuffer.append( _nValue ); + } + + + OUString lcl_toXSD_UNODate_typed( const css::util::Date& rDate ) + { + + OUStringBuffer sInfo; + lcl_appendInt32ToBuffer( rDate.Year, sInfo, 4 ); + sInfo.append( "-" ); + lcl_appendInt32ToBuffer( rDate.Month, sInfo, 2 ); + sInfo.append( "-" ); + lcl_appendInt32ToBuffer( rDate.Day, sInfo, 2 ); + + return sInfo.makeStringAndClear(); + } + + + OUString lcl_toXSD_UNODate( const Any& rAny ) + { + css::util::Date aDate; + OSL_VERIFY( rAny >>= aDate ); + return lcl_toXSD_UNODate_typed( aDate ); + } + + + css::util::Date lcl_toUNODate( std::u16string_view rString ) + { + css::util::Date aDate( 1, 1, 1900 ); + + bool bWellformed = ISO8601parseDate(rString, aDate); + + // sanity checks + if ( ( aDate.Year > 9999 ) || ( aDate.Month < 1 ) || ( aDate.Month > 12 ) || ( aDate.Day < 1 ) || ( aDate.Day > 31 ) ) + bWellformed = false; + else + { + ::Date aDateCheck( 1, aDate.Month, aDate.Year ); + if ( aDate.Day > aDateCheck.GetDaysInMonth() ) + bWellformed = false; + } + + // all okay? + if ( !bWellformed ) + return css::util::Date( 1, 1, 1900 ); + + return aDate; + } + + + Any lcl_toAny_UNODate( const OUString& rString ) + { + return Any( lcl_toUNODate( rString ) ); + } + + + OUString lcl_toXSD_UNOTime_typed( const css::util::Time& rTime ) + { + + OUStringBuffer sInfo; + lcl_appendInt32ToBuffer( rTime.Hours, sInfo, 2 ); + sInfo.append( ":" ); + lcl_appendInt32ToBuffer( rTime.Minutes, sInfo, 2 ); + sInfo.append( ":" ); + lcl_appendInt32ToBuffer( rTime.Seconds, sInfo, 2 ); + if ( rTime.NanoSeconds != 0 ) + { + OSL_ENSURE(rTime.NanoSeconds < 1000000000,"NanoSeconds cannot be more than 999 999 999"); + sInfo.append('.'); + std::ostringstream ostr; + ostr.fill('0'); + ostr.width(9); + ostr << rTime.NanoSeconds; + sInfo.append(OUString::createFromAscii(ostr.str().c_str())); + } + + return sInfo.makeStringAndClear(); + } + + + OUString lcl_toXSD_UNOTime( const Any& rAny ) + { + css::util::Time aTime; + OSL_VERIFY( rAny >>= aTime ); + return lcl_toXSD_UNOTime_typed( aTime ); + } + + + css::util::Time lcl_toUNOTime( std::u16string_view rString ) + { + css::util::Time aTime; + + bool bWellformed = ISO8601parseTime(rString, aTime); + + // sanity checks + // note that Seconds == 60 denotes leap seconds. Normally, they're not allowed everywhere, + // but we accept them all the time for simplicity reasons + if ( ( aTime.Hours > 24 ) + || ( aTime.Minutes > 59 ) + || ( aTime.Seconds > 60 ) + ) + bWellformed = false; + + if ( bWellformed + && ( aTime.Hours == 24 ) + && ( ( aTime.Minutes != 0 ) + || ( aTime.Seconds != 0 ) + || ( aTime.NanoSeconds != 0 ) + ) + ) + bWellformed = false; + + // all okay? + if ( !bWellformed ) + return css::util::Time(); + + return aTime; + } + + + Any lcl_toAny_UNOTime( const OUString& rString ) + { + return Any( lcl_toUNOTime( rString ) ); + } + + + OUString lcl_toXSD_UNODateTime( const Any& rAny ) + { + css::util::DateTime aDateTime; + OSL_VERIFY( rAny >>= aDateTime ); + + css::util::Date aDate( aDateTime.Day, aDateTime.Month, aDateTime.Year ); + OUString sDate = lcl_toXSD_UNODate_typed( aDate ); + + css::util::Time const aTime( aDateTime.NanoSeconds, aDateTime.Seconds, + aDateTime.Minutes, aDateTime.Hours, aDateTime.IsUTC); + OUString sTime = lcl_toXSD_UNOTime_typed( aTime ); + + OUString sRet = sDate + "T" + sTime; + return sRet; + } + + + Any lcl_toAny_UNODateTime( const OUString& rString ) + { + // separate the date from the time part + sal_Int32 nDateTimeSep = rString.indexOf( 'T' ); + if ( nDateTimeSep == -1 ) + nDateTimeSep = rString.indexOf( 't' ); + + css::util::Date aDate; + css::util::Time aTime; + if ( nDateTimeSep == -1 ) + { // no time part + aDate = lcl_toUNODate( rString ); + } + else + { + aDate = lcl_toUNODate( rString.subView( 0, nDateTimeSep ) ); + aTime = lcl_toUNOTime( rString.subView( nDateTimeSep + 1 ) ); + } + css::util::DateTime aDateTime( + aTime.NanoSeconds, aTime.Seconds, aTime.Minutes, aTime.Hours, + aDate.Day, aDate.Month, aDate.Year, aTime.IsUTC + ); + return Any( aDateTime ); + } +} + + +void Convert::init() +{ + maMap[ cppu::UnoType<OUString>::get() ] = Convert_t(&lcl_toXSD_OUString, &lcl_toAny_OUString); + maMap[ cppu::UnoType<bool>::get() ] = Convert_t(&lcl_toXSD_bool, &lcl_toAny_bool); + maMap[ cppu::UnoType<double>::get() ] = Convert_t(&lcl_toXSD_double, &lcl_toAny_double); + maMap[ cppu::UnoType<css::util::Date>::get() ] = Convert_t( &lcl_toXSD_UNODate, &lcl_toAny_UNODate ); + maMap[ cppu::UnoType<css::util::Time>::get() ] = Convert_t( &lcl_toXSD_UNOTime, &lcl_toAny_UNOTime ); + maMap[ cppu::UnoType<css::util::DateTime>::get() ] = Convert_t( &lcl_toXSD_UNODateTime, &lcl_toAny_UNODateTime ); +} + + +Convert& Convert::get() +{ + // create our Singleton instance on demand + static Convert aConvert; + return aConvert; +} + +bool Convert::hasType( const css::uno::Type& rType ) +{ + return maMap.find( rType ) != maMap.end(); +} + +css::uno::Sequence<css::uno::Type> Convert::getTypes() const +{ + return comphelper::mapKeysToSequence( maMap ); +} + +OUString Convert::toXSD( const css::uno::Any& rAny ) +{ + Map_t::iterator aIter = maMap.find( rAny.getValueType() ); + return aIter != maMap.end() ? aIter->second.first( rAny ) : OUString(); +} + +css::uno::Any Convert::toAny( const OUString& rValue, + const css::uno::Type& rType ) +{ + Map_t::iterator aIter = maMap.find( rType ); + return aIter != maMap.end() ? aIter->second.second( rValue ) : css::uno::Any(); +} + + +OUString Convert::collapseWhitespace( const OUString& _rString ) +{ + sal_Int32 nLength = _rString.getLength(); + OUStringBuffer aBuffer( nLength ); + const sal_Unicode* pStr = _rString.getStr(); + bool bStrip = true; + for( sal_Int32 i = 0; i < nLength; i++ ) + { + sal_Unicode c = pStr[i]; + if( c == u'\x0008' || + c == u'\x000A' || + c == u'\x000D' || + c == u' ' ) + { + if( ! bStrip ) + { + aBuffer.append( u' ' ); + bStrip = true; + } + } + else + { + bStrip = false; + aBuffer.append( c ); + } + } + if( aBuffer[ aBuffer.getLength() - 1 ] == u' ' ) + aBuffer.setLength( aBuffer.getLength() - 1 ); + return aBuffer.makeStringAndClear(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/forms/source/xforms/convert.hxx b/forms/source/xforms/convert.hxx new file mode 100644 index 000000000..c7073f7c3 --- /dev/null +++ b/forms/source/xforms/convert.hxx @@ -0,0 +1,78 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/uno/Sequence.hxx> +#include <map> + +namespace com::sun::star::uno +{ + class Any; + class Type; +} + +namespace xforms +{ + +struct TypeLess +{ + bool operator()( const css::uno::Type& rType1, + const css::uno::Type& rType2 ) const + { return rType1.getTypeName() < rType2.getTypeName(); } +}; + +class Convert +{ + // hold conversion objects + typedef OUString (*fn_toXSD)( const css::uno::Any& ); + typedef css::uno::Any (*fn_toAny)( const OUString& ); + typedef std::pair<fn_toXSD,fn_toAny> Convert_t; + typedef std::map<css::uno::Type, Convert_t, TypeLess> Map_t; + Map_t maMap; + + Convert(); + + void init(); + +public: + /** get/create Singleton class */ + static Convert& get(); + + /// can we convert this type? + bool hasType( const css::uno::Type& ); + + /// get list of convertible types + css::uno::Sequence<css::uno::Type> getTypes() const; + + /// convert any to XML representation + OUString toXSD( const css::uno::Any& rAny ); + + /// convert XML representation to Any of given type + css::uno::Any toAny( const OUString&, const css::uno::Type& ); + + /** replace all sequences of 0x08, 0x0A, 0x0D, 0x20 with a single 0x20. + also strip leading/trailing whitespace. + */ + static OUString collapseWhitespace( const OUString& _rString ); +}; + +} // namespace xforms + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/forms/source/xforms/datatyperepository.cxx b/forms/source/xforms/datatyperepository.cxx new file mode 100644 index 000000000..fc446a5a3 --- /dev/null +++ b/forms/source/xforms/datatyperepository.cxx @@ -0,0 +1,295 @@ +/* -*- 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 . + */ + + +#include "datatyperepository.hxx" +#include "datatypes.hxx" +#include <strings.hrc> +#include <frm_resource.hxx> +#include <frm_strings.hxx> +#include <property.hxx> + +#include <com/sun/star/container/ElementExistException.hpp> +#include <com/sun/star/util/VetoException.hpp> +#include <com/sun/star/xsd/DataTypeClass.hpp> +#include <comphelper/enumhelper.hxx> + +namespace xforms +{ + + using ::com::sun::star::uno::Reference; + using ::com::sun::star::uno::Any; + using ::com::sun::star::uno::Type; + using ::com::sun::star::uno::Sequence; + using ::com::sun::star::util::VetoException; + using ::com::sun::star::container::NoSuchElementException; + using ::com::sun::star::container::ElementExistException; + using ::com::sun::star::container::XEnumeration; + using ::com::sun::star::xsd::XDataType; + using namespace frm; + + ODataTypeRepository::ODataTypeRepository( ) + { + + // insert some basic types + OUString sName( ResourceManager::loadString(RID_STR_DATATYPE_STRING) ); + m_aRepository[ sName ] = new OStringType( sName, css::xsd::DataTypeClass::STRING ); + + sName = ResourceManager::loadString(RID_STR_DATATYPE_URL); + m_aRepository[ sName ] = new OStringType( sName, css::xsd::DataTypeClass::anyURI ); + + sName = ResourceManager::loadString(RID_STR_DATATYPE_BOOLEAN); + m_aRepository[ sName ] = new OBooleanType( sName ); + + sName = ResourceManager::loadString(RID_STR_DATATYPE_DECIMAL); + m_aRepository[ sName ] = new ODecimalType( sName, css::xsd::DataTypeClass::DECIMAL ); + + sName = ResourceManager::loadString(RID_STR_DATATYPE_FLOAT); + m_aRepository[ sName ] = new ODecimalType( sName, css::xsd::DataTypeClass::FLOAT ); + + sName = ResourceManager::loadString(RID_STR_DATATYPE_DOUBLE); + m_aRepository[ sName ] = new ODecimalType( sName, css::xsd::DataTypeClass::DOUBLE ); + + sName = ResourceManager::loadString(RID_STR_DATATYPE_DATE); + m_aRepository[ sName ] = new ODateType( sName ); + + sName = ResourceManager::loadString(RID_STR_DATATYPE_TIME); + m_aRepository[ sName ] = new OTimeType( sName ); + + sName = ResourceManager::loadString(RID_STR_DATATYPE_DATETIME); + m_aRepository[ sName ] = new ODateTimeType( sName ); + + sName = ResourceManager::loadString(RID_STR_DATATYPE_YEAR); + m_aRepository[ sName ] = new OShortIntegerType( sName, css::xsd::DataTypeClass::gYear ); + + sName = ResourceManager::loadString(RID_STR_DATATYPE_MONTH); + m_aRepository[ sName ] = new OShortIntegerType( sName, css::xsd::DataTypeClass::gMonth ); + + sName = ResourceManager::loadString(RID_STR_DATATYPE_DAY); + m_aRepository[ sName ] = new OShortIntegerType( sName, css::xsd::DataTypeClass::gDay ); + } + + + ODataTypeRepository::~ODataTypeRepository( ) + { + } + + + ODataTypeRepository::Repository::iterator ODataTypeRepository::implLocate( const OUString& _rName, bool _bAllowMiss ) + { + Repository::iterator aTypePos = m_aRepository.find( _rName ); + if ( aTypePos == m_aRepository.end() && !_bAllowMiss ) + throw NoSuchElementException( OUString(), *this ); + + return aTypePos; + } + + + Reference< XDataType > SAL_CALL ODataTypeRepository::getBasicDataType( sal_Int16 dataTypeClass ) + { + Reference< XDataType > xReturn; + + for ( Repository::const_iterator lookup = m_aRepository.begin(); + ( lookup != m_aRepository.end() ) && ! xReturn.is(); + ++lookup + ) + { + if ( lookup->second->getIsBasic() && ( lookup->second->getTypeClass() == dataTypeClass ) ) + xReturn = lookup->second.get(); + } + + if ( !xReturn.is() ) + throw NoSuchElementException( OUString(), *this ); + + return xReturn; + } + + + Reference< XDataType > SAL_CALL ODataTypeRepository::cloneDataType( const OUString& sourceName, const OUString& newName ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + + Repository::iterator aTypePos = implLocate( newName, true ); + if ( aTypePos != m_aRepository.end() ) + throw ElementExistException( OUString(), *this ); + + aTypePos = implLocate( sourceName ); + rtl::Reference<OXSDDataType> pClone = aTypePos->second->clone( newName ); + m_aRepository[ newName ] = pClone; + + return pClone; + } + + + void SAL_CALL ODataTypeRepository::revokeDataType( const OUString& typeName ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + + Repository::iterator aTypePos = implLocate( typeName ); + if ( aTypePos->second->getIsBasic() ) + // "This is a built-in type and cannot be removed." + throw VetoException(ResourceManager::loadString(RID_STR_XFORMS_CANT_REMOVE_TYPE), *this ); + + m_aRepository.erase( aTypePos ); + } + + + Reference< XDataType > SAL_CALL ODataTypeRepository::getDataType( const OUString& typeName ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + return implLocate( typeName )->second; + } + + + Reference< XEnumeration > SAL_CALL ODataTypeRepository::createEnumeration( ) + { + return new ::comphelper::OEnumerationByName( this ); + } + + + Any SAL_CALL ODataTypeRepository::getByName( const OUString& aName ) + { + return Any( getDataType( aName ) ); + } + + + Sequence< OUString > SAL_CALL ODataTypeRepository::getElementNames( ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + + return comphelper::mapKeysToSequence( m_aRepository ); + } + + + sal_Bool SAL_CALL ODataTypeRepository::hasByName( const OUString& aName ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + return m_aRepository.find( aName ) != m_aRepository.end(); + } + + + Type SAL_CALL ODataTypeRepository::getElementType( ) + { + return cppu::UnoType<XDataType>::get(); + } + + + sal_Bool SAL_CALL ODataTypeRepository::hasElements( ) + { + return !m_aRepository.empty(); + } + + + // type specific implementation of registerProperties, using explicit + // template instantiations + + template<> + void OValueLimitedType<css::util::Date>::registerProperties() + { + OValueLimitedType_Base::registerProperties(); + + registerMayBeVoidProperty( PROPERTY_XSD_MAX_INCLUSIVE_DATE, PROPERTY_ID_XSD_MAX_INCLUSIVE_DATE, css::beans::PropertyAttribute::BOUND | css::beans::PropertyAttribute::MAYBEVOID, + &m_aMaxInclusive, cppu::UnoType<ValueType>::get() ); + + registerMayBeVoidProperty( PROPERTY_XSD_MAX_EXCLUSIVE_DATE, PROPERTY_ID_XSD_MAX_EXCLUSIVE_DATE, css::beans::PropertyAttribute::BOUND | css::beans::PropertyAttribute::MAYBEVOID, + &m_aMaxExclusive, cppu::UnoType<ValueType>::get() ); + + registerMayBeVoidProperty( PROPERTY_XSD_MIN_INCLUSIVE_DATE, PROPERTY_ID_XSD_MIN_INCLUSIVE_DATE, css::beans::PropertyAttribute::BOUND | css::beans::PropertyAttribute::MAYBEVOID, + &m_aMinInclusive, cppu::UnoType<ValueType>::get() ); + + registerMayBeVoidProperty( PROPERTY_XSD_MIN_EXCLUSIVE_DATE, PROPERTY_ID_XSD_MIN_EXCLUSIVE_DATE, css::beans::PropertyAttribute::BOUND | css::beans::PropertyAttribute::MAYBEVOID, + &m_aMinExclusive, cppu::UnoType<ValueType>::get() ); + } + + template<> + void OValueLimitedType<css::util::Time>::registerProperties() + { + OValueLimitedType_Base::registerProperties(); + + registerMayBeVoidProperty( PROPERTY_XSD_MAX_INCLUSIVE_TIME, PROPERTY_ID_XSD_MAX_INCLUSIVE_TIME, css::beans::PropertyAttribute::BOUND | css::beans::PropertyAttribute::MAYBEVOID, + &m_aMaxInclusive, cppu::UnoType<ValueType>::get() ); + + registerMayBeVoidProperty( PROPERTY_XSD_MAX_EXCLUSIVE_TIME, PROPERTY_ID_XSD_MAX_EXCLUSIVE_TIME, css::beans::PropertyAttribute::BOUND | css::beans::PropertyAttribute::MAYBEVOID, + &m_aMaxExclusive, cppu::UnoType<ValueType>::get() ); + + registerMayBeVoidProperty( PROPERTY_XSD_MIN_INCLUSIVE_TIME, PROPERTY_ID_XSD_MIN_INCLUSIVE_TIME, css::beans::PropertyAttribute::BOUND | css::beans::PropertyAttribute::MAYBEVOID, + &m_aMinInclusive, cppu::UnoType<ValueType>::get() ); + + registerMayBeVoidProperty( PROPERTY_XSD_MIN_EXCLUSIVE_TIME, PROPERTY_ID_XSD_MIN_EXCLUSIVE_TIME, css::beans::PropertyAttribute::BOUND | css::beans::PropertyAttribute::MAYBEVOID, + &m_aMinExclusive, cppu::UnoType<ValueType>::get() ); + } + + template<> + void OValueLimitedType<css::util::DateTime>::registerProperties() + { + OValueLimitedType_Base::registerProperties(); + + registerMayBeVoidProperty( PROPERTY_XSD_MAX_INCLUSIVE_DATE_TIME, PROPERTY_ID_XSD_MAX_INCLUSIVE_DATE_TIME, css::beans::PropertyAttribute::BOUND | css::beans::PropertyAttribute::MAYBEVOID, + &m_aMaxInclusive, cppu::UnoType<ValueType>::get() ); + + registerMayBeVoidProperty( PROPERTY_XSD_MAX_EXCLUSIVE_DATE_TIME, PROPERTY_ID_XSD_MAX_EXCLUSIVE_DATE_TIME, css::beans::PropertyAttribute::BOUND | css::beans::PropertyAttribute::MAYBEVOID, + &m_aMaxExclusive, cppu::UnoType<ValueType>::get() ); + + registerMayBeVoidProperty( PROPERTY_XSD_MIN_INCLUSIVE_DATE_TIME, PROPERTY_ID_XSD_MIN_INCLUSIVE_DATE_TIME, css::beans::PropertyAttribute::BOUND | css::beans::PropertyAttribute::MAYBEVOID, + &m_aMinInclusive, cppu::UnoType<ValueType>::get() ); + + registerMayBeVoidProperty( PROPERTY_XSD_MIN_EXCLUSIVE_DATE_TIME, PROPERTY_ID_XSD_MIN_EXCLUSIVE_DATE_TIME, css::beans::PropertyAttribute::BOUND | css::beans::PropertyAttribute::MAYBEVOID, + &m_aMinExclusive, cppu::UnoType<ValueType>::get() ); + } + + template<> + void OValueLimitedType<double>::registerProperties() + { + OValueLimitedType_Base::registerProperties(); + + registerMayBeVoidProperty( PROPERTY_XSD_MAX_INCLUSIVE_DOUBLE, PROPERTY_ID_XSD_MAX_INCLUSIVE_DOUBLE, css::beans::PropertyAttribute::BOUND | css::beans::PropertyAttribute::MAYBEVOID, + &m_aMaxInclusive, cppu::UnoType<ValueType>::get() ); + + registerMayBeVoidProperty( PROPERTY_XSD_MAX_EXCLUSIVE_DOUBLE, PROPERTY_ID_XSD_MAX_EXCLUSIVE_DOUBLE, css::beans::PropertyAttribute::BOUND | css::beans::PropertyAttribute::MAYBEVOID, + &m_aMaxExclusive, cppu::UnoType<ValueType>::get() ); + + registerMayBeVoidProperty( PROPERTY_XSD_MIN_INCLUSIVE_DOUBLE, PROPERTY_ID_XSD_MIN_INCLUSIVE_DOUBLE, css::beans::PropertyAttribute::BOUND | css::beans::PropertyAttribute::MAYBEVOID, + &m_aMinInclusive, cppu::UnoType<ValueType>::get() ); + + registerMayBeVoidProperty( PROPERTY_XSD_MIN_EXCLUSIVE_DOUBLE, PROPERTY_ID_XSD_MIN_EXCLUSIVE_DOUBLE, css::beans::PropertyAttribute::BOUND | css::beans::PropertyAttribute::MAYBEVOID, + &m_aMinExclusive, cppu::UnoType<ValueType>::get() ); + } + + template<> + void OValueLimitedType<sal_Int16>::registerProperties() + { + OValueLimitedType_Base::registerProperties(); + + registerMayBeVoidProperty( PROPERTY_XSD_MAX_INCLUSIVE_INT, PROPERTY_ID_XSD_MAX_INCLUSIVE_INT, css::beans::PropertyAttribute::BOUND | css::beans::PropertyAttribute::MAYBEVOID, + &m_aMaxInclusive, cppu::UnoType<ValueType>::get() ); + + registerMayBeVoidProperty( PROPERTY_XSD_MAX_EXCLUSIVE_INT, PROPERTY_ID_XSD_MAX_EXCLUSIVE_INT, css::beans::PropertyAttribute::BOUND | css::beans::PropertyAttribute::MAYBEVOID, + &m_aMaxExclusive, cppu::UnoType<ValueType>::get() ); + + registerMayBeVoidProperty( PROPERTY_XSD_MIN_INCLUSIVE_INT, PROPERTY_ID_XSD_MIN_INCLUSIVE_INT, css::beans::PropertyAttribute::BOUND | css::beans::PropertyAttribute::MAYBEVOID, + &m_aMinInclusive, cppu::UnoType<ValueType>::get() ); + + registerMayBeVoidProperty( PROPERTY_XSD_MIN_EXCLUSIVE_INT, PROPERTY_ID_XSD_MIN_EXCLUSIVE_INT, css::beans::PropertyAttribute::BOUND | css::beans::PropertyAttribute::MAYBEVOID, + &m_aMinExclusive, cppu::UnoType<ValueType>::get() ); + } + +} // namespace xforms + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/forms/source/xforms/datatyperepository.hxx b/forms/source/xforms/datatyperepository.hxx new file mode 100644 index 000000000..cd5ea944d --- /dev/null +++ b/forms/source/xforms/datatyperepository.hxx @@ -0,0 +1,83 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/xforms/XDataTypeRepository.hpp> +#include <cppuhelper/implbase.hxx> +#include <rtl/ref.hxx> + +#include <map> + + +namespace xforms +{ + + class OXSDDataType; + + typedef ::cppu::WeakImplHelper < css::xforms::XDataTypeRepository + > ODataTypeRepository_Base; + class ODataTypeRepository : public ODataTypeRepository_Base + { + private: + typedef ::rtl::Reference< OXSDDataType > DataType; + typedef ::std::map< OUString, DataType > Repository; + + ::osl::Mutex m_aMutex; + Repository m_aRepository; + + public: + ODataTypeRepository( ); + + protected: + virtual ~ODataTypeRepository( ) override; + + // XDataTypeRepository + virtual css::uno::Reference< css::xsd::XDataType > SAL_CALL getBasicDataType( sal_Int16 dataTypeClass ) override; + virtual css::uno::Reference< css::xsd::XDataType > SAL_CALL cloneDataType( const OUString& sourceName, const OUString& newName ) override; + virtual void SAL_CALL revokeDataType( const OUString& typeName ) override; + virtual css::uno::Reference< css::xsd::XDataType > SAL_CALL getDataType( const OUString& typeName ) override; + + // XEnumerationAccess (base of XDataTypeRepository) + virtual css::uno::Reference< css::container::XEnumeration > SAL_CALL createEnumeration( ) override; + + // XNameAccess (base of XDataTypeRepository) + virtual css::uno::Any SAL_CALL getByName( const OUString& aName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getElementNames( ) override; + virtual sal_Bool SAL_CALL hasByName( const OUString& aName ) override; + + // XElementAccess (base of XEnumerationAccess and XNameAccess) + virtual css::uno::Type SAL_CALL getElementType( ) override; + virtual sal_Bool SAL_CALL hasElements( ) override; + + private: + ODataTypeRepository( const ODataTypeRepository& ) = delete; + ODataTypeRepository& operator=( const ODataTypeRepository& ) = delete; + + private: + /** locates the type with the given name in our repository, or throws an exception if there is no such type + */ + Repository::iterator implLocate( const OUString& _rName, bool _bAllowMiss = false ); + }; + + +} // namespace xforms + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/forms/source/xforms/datatypes.cxx b/forms/source/xforms/datatypes.cxx new file mode 100644 index 000000000..d4578013c --- /dev/null +++ b/forms/source/xforms/datatypes.cxx @@ -0,0 +1,976 @@ +/* -*- 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 . + */ + +#include <memory> +#include "datatypes.hxx" +#include "resourcehelper.hxx" +#include <frm_strings.hxx> +#include <property.hxx> +#include <strings.hrc> +#include "convert.hxx" + +#include <com/sun/star/xsd/DataTypeClass.hpp> +#include <com/sun/star/xsd/WhiteSpaceTreatment.hpp> +#include <tools/datetime.hxx> +#include <rtl/math.hxx> +#include <sal/log.hxx> + + +namespace xforms +{ + + + using ::com::sun::star::uno::Reference; + using ::com::sun::star::uno::Any; + using ::com::sun::star::util::Date; + using ::com::sun::star::util::Time; + using ::com::sun::star::util::DateTime; + using ::com::sun::star::lang::IllegalArgumentException; + using ::com::sun::star::beans::XPropertyChangeListener; + using ::com::sun::star::beans::XVetoableChangeListener; + + using ::com::sun::star::beans::PropertyAttribute::BOUND; + using ::com::sun::star::beans::PropertyAttribute::READONLY; + + using namespace ::com::sun::star::xsd; + using namespace ::frm; + U_NAMESPACE_USE + + OXSDDataType::OXSDDataType( const OUString& _rName, sal_Int16 _nTypeClass ) + :OXSDDataType_PBase( m_aBHelper ) + ,m_bIsBasic( true ) + ,m_nTypeClass( _nTypeClass ) + ,m_sName( _rName ) + ,m_nWST( WhiteSpaceTreatment::Preserve ) + ,m_bPatternMatcherDirty( true ) + { + } + + + OXSDDataType::~OXSDDataType() + { + } + + + void OXSDDataType::registerProperties() + { + registerProperty( PROPERTY_NAME, PROPERTY_ID_NAME, BOUND, &m_sName, cppu::UnoType<decltype(m_sName)>::get() ); + registerProperty( PROPERTY_XSD_WHITESPACE, PROPERTY_ID_XSD_WHITESPACE, BOUND, &m_nWST, cppu::UnoType<cppu::UnoUnsignedShortType>::get() ); + registerProperty( PROPERTY_XSD_PATTERN, PROPERTY_ID_XSD_PATTERN, BOUND, &m_sPattern, cppu::UnoType<decltype(m_sPattern)>::get() ); + + registerProperty( PROPERTY_XSD_IS_BASIC, PROPERTY_ID_XSD_IS_BASIC, READONLY, &m_bIsBasic, cppu::UnoType<decltype(m_bIsBasic)>::get() ); + registerProperty( PROPERTY_XSD_TYPE_CLASS, PROPERTY_ID_XSD_TYPE_CLASS, READONLY, &m_nTypeClass, cppu::UnoType<decltype(m_nTypeClass)>::get() ); + } + + + void OXSDDataType::initializeClone( const OXSDDataType& _rCloneSource ) + { + m_bIsBasic = false; + m_nTypeClass = _rCloneSource.m_nTypeClass; + m_sPattern = _rCloneSource.m_sPattern; + m_nWST = _rCloneSource.m_nWST; + } + + + rtl::Reference<OXSDDataType> OXSDDataType::clone( const OUString& _rNewName ) const + { + rtl::Reference<OXSDDataType> pClone = createClone( _rNewName ); + pClone->initializeClone( *this ); + return pClone; + } + + + IMPLEMENT_FORWARD_XINTERFACE2( OXSDDataType, OXSDDataType_Base, ::comphelper::OPropertyContainer ) + + + IMPLEMENT_FORWARD_XTYPEPROVIDER2( OXSDDataType, OXSDDataType_Base, ::comphelper::OPropertyContainer ) + + OUString SAL_CALL OXSDDataType::getName( ) + { + return m_sName; + } + + + void SAL_CALL OXSDDataType::setName( const OUString& aName ) + { + // TODO: check the name for conflicts in the repository + setFastPropertyValue( PROPERTY_ID_NAME, Any(aName) ); + SAL_WARN_IF( m_sName != aName, "forms.misc", "OXSDDataType::setName: inconsistency!" ); + } + + + OUString SAL_CALL OXSDDataType::getPattern() + { + return m_sPattern; + } + + + void SAL_CALL OXSDDataType::setPattern( const OUString& _pattern ) + { + setFastPropertyValue( PROPERTY_ID_XSD_PATTERN, Any(_pattern) ); + SAL_WARN_IF( m_sPattern != _pattern, "forms.misc", "OXSDDataType::setPattern: inconsistency!" ); + } + + + sal_Int16 SAL_CALL OXSDDataType::getWhiteSpaceTreatment() + { + return m_nWST; + } + + + void SAL_CALL OXSDDataType::setWhiteSpaceTreatment( sal_Int16 _whitespacetreatment ) + { + setFastPropertyValue( PROPERTY_ID_XSD_WHITESPACE, Any(_whitespacetreatment) ); + SAL_WARN_IF( m_nWST != _whitespacetreatment, "forms.misc", "OXSDDataType::setWhiteSpaceTreatment: inconsistency!" ); + } + + + sal_Bool SAL_CALL OXSDDataType::getIsBasic() + { + return m_bIsBasic; + } + + + sal_Int16 SAL_CALL OXSDDataType::getTypeClass() + { + return m_nTypeClass; + } + + + sal_Bool OXSDDataType::validate( const OUString& sValue ) + { + return bool(_validate( sValue )); + } + + + OUString OXSDDataType::explainInvalid( const OUString& sValue ) + { + // get reason + TranslateId pReason = _validate( sValue ); + + // get resource and return localized string + return (!pReason) + ? OUString() + : getResource( pReason, sValue, + _explainInvalid( pReason ) ); + } + + OUString OXSDDataType::_explainInvalid(TranslateId rReason) + { + if ( RID_STR_XFORMS_PATTERN_DOESNT_MATCH == rReason ) + { + OSL_ENSURE( !m_sPattern.isEmpty(), "OXSDDataType::_explainInvalid: how can this error occur without a regular expression?" ); + return m_sPattern; + } + return OUString(); + } + + namespace + { + void lcl_initializePatternMatcher( ::std::unique_ptr< RegexMatcher >& _rpMatcher, const OUString& _rPattern ) + { + UErrorCode nMatchStatus = U_ZERO_ERROR; + UnicodeString aIcuPattern( reinterpret_cast<const UChar *>(_rPattern.getStr()), _rPattern.getLength() ); + _rpMatcher.reset( new RegexMatcher( aIcuPattern, 0, nMatchStatus ) ); + OSL_ENSURE( U_SUCCESS( nMatchStatus ), "lcl_initializePatternMatcher: invalid pattern property!" ); + // if asserts, then something changed our pattern without going to convertFastPropertyValue/checkPropertySanity + } + + bool lcl_matchString( RegexMatcher& _rMatcher, const OUString& _rText ) + { + UErrorCode nMatchStatus = U_ZERO_ERROR; + UnicodeString aInput( reinterpret_cast<const UChar *>(_rText.getStr()), _rText.getLength() ); + _rMatcher.reset( aInput ); + if ( _rMatcher.matches( nMatchStatus ) ) + { + int32_t nStart = _rMatcher.start( nMatchStatus ); + int32_t nEnd = _rMatcher.end ( nMatchStatus ); + if ( ( nStart == 0 ) && ( nEnd == _rText.getLength() ) ) + return true; + } + + return false; + } + } + + TranslateId OXSDDataType::_validate( const OUString& _rValue ) + { + // care for the regular expression + if ( !m_sPattern.isEmpty() ) + { + // ensure our pattern matcher is up to date + if ( m_bPatternMatcherDirty ) + { + lcl_initializePatternMatcher( m_pPatternMatcher, m_sPattern ); + m_bPatternMatcherDirty = false; + } + + // let it match the string + if (!lcl_matchString(*m_pPatternMatcher, _rValue)) + return RID_STR_XFORMS_PATTERN_DOESNT_MATCH; + } + + return {}; + } + + + sal_Bool OXSDDataType::convertFastPropertyValue( Any& _rConvertedValue, Any& _rOldValue, sal_Int32 _nHandle, const Any& _rValue ) + { + // let the base class do the conversion + if ( !OXSDDataType_PBase::convertFastPropertyValue( _rConvertedValue, _rOldValue, _nHandle, _rValue ) ) + return false; + + // sanity checks + OUString sErrorMessage; + if ( !checkPropertySanity( _nHandle, _rConvertedValue, sErrorMessage ) ) + { + IllegalArgumentException aException; + aException.Message = sErrorMessage; + aException.Context = *this; + throw aException; + } + + return true; + } + + + void SAL_CALL OXSDDataType::setFastPropertyValue_NoBroadcast( sal_Int32 _nHandle, const Any& _rValue ) + { + OXSDDataType_PBase::setFastPropertyValue_NoBroadcast( _nHandle, _rValue ); + if ( _nHandle == PROPERTY_ID_XSD_PATTERN ) + m_bPatternMatcherDirty = true; + } + + + bool OXSDDataType::checkPropertySanity( sal_Int32 _nHandle, const css::uno::Any& _rNewValue, OUString& _rErrorMessage ) + { + if ( _nHandle == PROPERTY_ID_XSD_PATTERN ) + { + OUString sPattern; + OSL_VERIFY( _rNewValue >>= sPattern ); + + UnicodeString aIcuPattern( reinterpret_cast<const UChar *>(sPattern.getStr()), sPattern.getLength() ); + UErrorCode nMatchStatus = U_ZERO_ERROR; + RegexMatcher aMatcher( aIcuPattern, 0, nMatchStatus ); + if ( U_FAILURE( nMatchStatus ) ) + { + _rErrorMessage = "This is no valid pattern."; + return false; + } + } + return true; + } + + + void SAL_CALL OXSDDataType::setPropertyValue( const OUString& aPropertyName, const Any& aValue ) + { + OXSDDataType_PBase::setPropertyValue( aPropertyName, aValue ); + } + + + Any SAL_CALL OXSDDataType::getPropertyValue( const OUString& PropertyName ) + { + return OXSDDataType_PBase::getPropertyValue( PropertyName ); + } + + + void SAL_CALL OXSDDataType::addPropertyChangeListener( const OUString& aPropertyName, const Reference< XPropertyChangeListener >& xListener ) + { + OXSDDataType_PBase::addPropertyChangeListener( aPropertyName, xListener ); + } + + + void SAL_CALL OXSDDataType::removePropertyChangeListener( const OUString& aPropertyName, const Reference< XPropertyChangeListener >& aListener ) + { + OXSDDataType_PBase::removePropertyChangeListener( aPropertyName, aListener ); + } + + + void SAL_CALL OXSDDataType::addVetoableChangeListener( const OUString& PropertyName, const Reference< XVetoableChangeListener >& aListener ) + { + OXSDDataType_PBase::addVetoableChangeListener( PropertyName, aListener ); + } + + + void SAL_CALL OXSDDataType::removeVetoableChangeListener( const OUString& PropertyName, const Reference< XVetoableChangeListener >& aListener ) + { + OXSDDataType_PBase::removeVetoableChangeListener( PropertyName, aListener ); + } + + OValueLimitedType_Base::OValueLimitedType_Base( const OUString& _rName, sal_Int16 _nTypeClass ) + :OXSDDataType( _rName, _nTypeClass ) + ,m_fCachedMaxInclusive( 0 ) + ,m_fCachedMaxExclusive( 0 ) + ,m_fCachedMinInclusive( 0 ) + ,m_fCachedMinExclusive( 0 ) + { + } + + + void OValueLimitedType_Base::initializeClone( const OXSDDataType& _rCloneSource ) + { + OXSDDataType::initializeClone( _rCloneSource ); + initializeTypedClone( static_cast< const OValueLimitedType_Base& >( _rCloneSource ) ); + } + + + void OValueLimitedType_Base::initializeTypedClone( const OValueLimitedType_Base& _rCloneSource ) + { + m_aMaxInclusive = _rCloneSource.m_aMaxInclusive; + m_aMaxExclusive = _rCloneSource.m_aMaxExclusive; + m_aMinInclusive = _rCloneSource.m_aMinInclusive; + m_aMinExclusive = _rCloneSource.m_aMinExclusive; + m_fCachedMaxInclusive = _rCloneSource.m_fCachedMaxInclusive; + m_fCachedMaxExclusive = _rCloneSource.m_fCachedMaxExclusive; + m_fCachedMinInclusive = _rCloneSource.m_fCachedMinInclusive; + m_fCachedMinExclusive = _rCloneSource.m_fCachedMinExclusive; + } + + + void SAL_CALL OValueLimitedType_Base::setFastPropertyValue_NoBroadcast( + sal_Int32 _nHandle, const css::uno::Any& _rValue ) + { + OXSDDataType::setFastPropertyValue_NoBroadcast( _nHandle, _rValue ); + + // if one of our limit properties has been set, translate it into a double + // value, for later efficient validation + switch ( _nHandle ) + { + case PROPERTY_ID_XSD_MAX_INCLUSIVE_INT: + case PROPERTY_ID_XSD_MAX_INCLUSIVE_DOUBLE: + case PROPERTY_ID_XSD_MAX_INCLUSIVE_DATE: + case PROPERTY_ID_XSD_MAX_INCLUSIVE_TIME: + case PROPERTY_ID_XSD_MAX_INCLUSIVE_DATE_TIME: + if ( m_aMaxInclusive.hasValue() ) + normalizeValue( m_aMaxInclusive, m_fCachedMaxInclusive ); + else + m_fCachedMaxInclusive = 0; + break; + case PROPERTY_ID_XSD_MAX_EXCLUSIVE_INT: + case PROPERTY_ID_XSD_MAX_EXCLUSIVE_DOUBLE: + case PROPERTY_ID_XSD_MAX_EXCLUSIVE_DATE: + case PROPERTY_ID_XSD_MAX_EXCLUSIVE_TIME: + case PROPERTY_ID_XSD_MAX_EXCLUSIVE_DATE_TIME: + if ( m_aMaxExclusive.hasValue() ) + normalizeValue( m_aMaxExclusive, m_fCachedMaxExclusive ); + else + m_fCachedMaxExclusive = 0; + break; + case PROPERTY_ID_XSD_MIN_INCLUSIVE_INT: + case PROPERTY_ID_XSD_MIN_INCLUSIVE_DOUBLE: + case PROPERTY_ID_XSD_MIN_INCLUSIVE_DATE: + case PROPERTY_ID_XSD_MIN_INCLUSIVE_TIME: + case PROPERTY_ID_XSD_MIN_INCLUSIVE_DATE_TIME: + if ( m_aMinInclusive.hasValue() ) + normalizeValue( m_aMinInclusive, m_fCachedMinInclusive ); + else + m_fCachedMinInclusive = 0; + break; + case PROPERTY_ID_XSD_MIN_EXCLUSIVE_INT: + case PROPERTY_ID_XSD_MIN_EXCLUSIVE_DOUBLE: + case PROPERTY_ID_XSD_MIN_EXCLUSIVE_DATE: + case PROPERTY_ID_XSD_MIN_EXCLUSIVE_TIME: + case PROPERTY_ID_XSD_MIN_EXCLUSIVE_DATE_TIME: + if ( m_aMinExclusive.hasValue() ) + normalizeValue( m_aMinExclusive, m_fCachedMinExclusive ); + else + m_fCachedMinExclusive = 0; + break; + } + } + + + bool OValueLimitedType_Base::_getValue( const OUString& rValue, double& fValue ) + { + // convert to double + rtl_math_ConversionStatus eStatus; + sal_Int32 nEnd; + double f = ::rtl::math::stringToDouble( + rValue, '.', u'\0', &eStatus, &nEnd ); + + // error checking... + bool bReturn = false; + if( eStatus == rtl_math_ConversionStatus_Ok + && nEnd == rValue.getLength() ) + { + bReturn = true; + fValue = f; + } + return bReturn; + } + + TranslateId OValueLimitedType_Base::_validate( const OUString& rValue ) + { + TranslateId pReason = OXSDDataType::_validate( rValue ); + if (!pReason) + { + + // convert value and check format + double f; + if( ! _getValue( rValue, f ) ) + pReason = RID_STR_XFORMS_VALUE_IS_NOT_A; + + // check range + else if( ( m_aMaxInclusive.hasValue() ) && f > m_fCachedMaxInclusive ) + pReason = RID_STR_XFORMS_VALUE_MAX_INCL; + else if( ( m_aMaxExclusive.hasValue() ) && f >= m_fCachedMaxExclusive ) + pReason = RID_STR_XFORMS_VALUE_MAX_EXCL; + else if( ( m_aMinInclusive.hasValue() ) && f < m_fCachedMinInclusive ) + pReason = RID_STR_XFORMS_VALUE_MIN_INCL; + else if( ( m_aMinExclusive.hasValue() ) && f <= m_fCachedMinExclusive ) + pReason = RID_STR_XFORMS_VALUE_MIN_EXCL; + } + return pReason; + } + + OUString OValueLimitedType_Base::_explainInvalid(TranslateId rReason) + { + OUStringBuffer sInfo; + if (rReason == RID_STR_XFORMS_VALUE_IS_NOT_A) + sInfo.append( getName() ); + else if (rReason == RID_STR_XFORMS_VALUE_MAX_INCL) + sInfo.append( typedValueAsHumanReadableString( m_aMaxInclusive ) ); + else if (rReason == RID_STR_XFORMS_VALUE_MAX_EXCL) + sInfo.append( typedValueAsHumanReadableString( m_aMaxExclusive ) ); + else if (rReason == RID_STR_XFORMS_VALUE_MIN_INCL) + sInfo.append( typedValueAsHumanReadableString( m_aMinInclusive ) ); + else if (rReason == RID_STR_XFORMS_VALUE_MIN_EXCL) + sInfo.append( typedValueAsHumanReadableString( m_aMinExclusive ) ); + return sInfo.makeStringAndClear(); + } + + OStringType::OStringType( const OUString& _rName, sal_Int16 _nTypeClass ) + :OStringType_Base( _rName, _nTypeClass ) + { + } + + + void OStringType::registerProperties() + { + OStringType_Base::registerProperties(); + + registerMayBeVoidProperty( PROPERTY_XSD_LENGTH, PROPERTY_ID_XSD_LENGTH, css::beans::PropertyAttribute::BOUND | css::beans::PropertyAttribute::MAYBEVOID, + &m_aLength, cppu::UnoType<sal_Int32>::get() ); + + registerMayBeVoidProperty( PROPERTY_XSD_MIN_LENGTH, PROPERTY_ID_XSD_MIN_LENGTH, css::beans::PropertyAttribute::BOUND | css::beans::PropertyAttribute::MAYBEVOID, + &m_aMinLength, cppu::UnoType<sal_Int32>::get() ); + + registerMayBeVoidProperty( PROPERTY_XSD_MAX_LENGTH, PROPERTY_ID_XSD_MAX_LENGTH, css::beans::PropertyAttribute::BOUND | css::beans::PropertyAttribute::MAYBEVOID, + &m_aMaxLength, cppu::UnoType<sal_Int32>::get() ); + } + + + rtl::Reference<OXSDDataType> OStringType::createClone( const OUString& _rName ) const + { + return new OStringType( _rName, getTypeClass() ); + } + void OStringType::initializeClone( const OXSDDataType& _rCloneSource ) + { + OStringType_Base::initializeClone( _rCloneSource ); + initializeTypedClone( static_cast< const OStringType& >( _rCloneSource ) ); + } + + + + void OStringType::initializeTypedClone( const OStringType& _rCloneSource ) + { + m_aLength = _rCloneSource.m_aLength; + m_aMinLength = _rCloneSource.m_aMinLength; + m_aMaxLength = _rCloneSource.m_aMaxLength; + } + + + bool OStringType::checkPropertySanity( sal_Int32 _nHandle, const Any& _rNewValue, OUString& _rErrorMessage ) + { + // let the base class do the conversion + if ( !OStringType_Base::checkPropertySanity( _nHandle, _rNewValue, _rErrorMessage ) ) + return false; + + _rErrorMessage.clear(); + switch ( _nHandle ) + { + case PROPERTY_ID_XSD_LENGTH: + case PROPERTY_ID_XSD_MIN_LENGTH: + case PROPERTY_ID_XSD_MAX_LENGTH: + { + sal_Int32 nValue( 0 ); + OSL_VERIFY( _rNewValue >>= nValue ); + if ( nValue <= 0 ) + _rErrorMessage = "Length limits must denote positive integer values."; + // TODO/eforms: localize the error message + } + break; + } + + return _rErrorMessage.isEmpty(); + } + + + TranslateId OStringType::_validate( const OUString& rValue ) + { + // check regexp, whitespace etc. in parent class + TranslateId pReason = OStringType_Base::_validate( rValue ); + + if (!pReason) + { + // check string constraints + sal_Int32 nLength = rValue.getLength(); + sal_Int32 nLimit = 0; + if ( m_aLength >>= nLimit ) + { + if ( nLimit != nLength ) + pReason = RID_STR_XFORMS_VALUE_LENGTH; + } + else + { + if ( ( m_aMaxLength >>= nLimit ) && ( nLength > nLimit ) ) + pReason = RID_STR_XFORMS_VALUE_MAX_LENGTH; + else if ( ( m_aMinLength >>= nLimit ) && ( nLength < nLimit ) ) + pReason = RID_STR_XFORMS_VALUE_MIN_LENGTH; + } + } + return pReason; + } + + OUString OStringType::_explainInvalid(TranslateId rReason) + { + sal_Int32 nValue = 0; + OUStringBuffer sInfo; + if (rReason == RID_STR_XFORMS_VALUE_LENGTH) + { + if( m_aLength >>= nValue ) + sInfo.append( nValue ); + } + else if (rReason == RID_STR_XFORMS_VALUE_MAX_LENGTH) + { + if( m_aMaxLength >>= nValue ) + sInfo.append( nValue ); + } + else if (rReason == RID_STR_XFORMS_VALUE_MIN_LENGTH) + { + if( m_aMinLength >>= nValue ) + sInfo.append( nValue ); + } + else if (rReason) + { + sInfo.append(OStringType_Base::_explainInvalid(rReason)); + } + return sInfo.makeStringAndClear(); + } + + OBooleanType::OBooleanType( const OUString& _rName ) + :OBooleanType_Base( _rName, DataTypeClass::BOOLEAN ) + { + } + + rtl::Reference<OXSDDataType> OBooleanType::createClone( const OUString& _rName ) const + { + return new OBooleanType( _rName ); + } + + void OBooleanType::initializeClone( const OXSDDataType& _rCloneSource ) + { + OBooleanType_Base::initializeClone( _rCloneSource ); + } + + TranslateId OBooleanType::_validate( const OUString& sValue ) + { + TranslateId pInvalidityReason = OBooleanType_Base::_validate( sValue ); + if ( pInvalidityReason ) + return pInvalidityReason; + + bool bValid = sValue == "0" || sValue == "1" || sValue == "true" || sValue == "false"; + return bValid ? TranslateId() : RID_STR_XFORMS_INVALID_VALUE; + } + + OUString OBooleanType::_explainInvalid(TranslateId rReason) + { + return !rReason ? OUString() : getName(); + } + + ODecimalType::ODecimalType( const OUString& _rName, sal_Int16 _nTypeClass ) + :ODecimalType_Base( _rName, _nTypeClass ) + { + } + + rtl::Reference<OXSDDataType> ODecimalType::createClone( const OUString& _rName ) const + { + return new ODecimalType( _rName, getTypeClass() ); + } + void ODecimalType::initializeClone( const OXSDDataType& _rCloneSource ) + { + ODecimalType_Base::initializeClone( _rCloneSource ); + initializeTypedClone( static_cast< const ODecimalType& >( _rCloneSource ) ); + } + + void ODecimalType::initializeTypedClone( const ODecimalType& _rCloneSource ) + { + m_aTotalDigits = _rCloneSource.m_aTotalDigits; + m_aFractionDigits = _rCloneSource.m_aFractionDigits; + } + + + void ODecimalType::registerProperties() + { + ODecimalType_Base::registerProperties(); + + registerMayBeVoidProperty( PROPERTY_XSD_TOTAL_DIGITS, PROPERTY_ID_XSD_TOTAL_DIGITS, css::beans::PropertyAttribute::BOUND | css::beans::PropertyAttribute::MAYBEVOID, + &m_aTotalDigits, cppu::UnoType<sal_Int32>::get() ); + + registerMayBeVoidProperty( PROPERTY_XSD_FRACTION_DIGITS, PROPERTY_ID_XSD_FRACTION_DIGITS, css::beans::PropertyAttribute::BOUND | css::beans::PropertyAttribute::MAYBEVOID, + &m_aFractionDigits, cppu::UnoType<sal_Int32>::get() ); + } + + + // validate decimals and return code for which facets failed + // to be used by: ODecimalType::validate and ODecimalType::explainInvalid + TranslateId ODecimalType::_validate( const OUString& rValue ) + { + TranslateId pReason = ODecimalType_Base::_validate( rValue ); + + // check digits (if no other cause is available so far) + if (!pReason) + { + sal_Int32 nLength = rValue.getLength(); + sal_Int32 n = 0; + sal_Int32 nTotalDigits = 0; + sal_Int32 nFractionDigits = 0; + const sal_Unicode* pValue = rValue.getStr(); + for( ; n < nLength && pValue[n] != '.'; n++ ) + if( pValue[n] >= '0' + && pValue[n] <= '9') + nTotalDigits++; + for( ; n < nLength; n++ ) + if( pValue[n] >= '0' + && pValue[n] <= '9') + nFractionDigits++; + nTotalDigits += nFractionDigits; + + sal_Int32 nValue = 0; + if( ( m_aTotalDigits >>= nValue ) && nTotalDigits > nValue ) + pReason = RID_STR_XFORMS_VALUE_TOTAL_DIGITS; + else if( ( m_aFractionDigits >>= nValue ) && + ( nFractionDigits > nValue ) ) + pReason = RID_STR_XFORMS_VALUE_FRACTION_DIGITS; + } + + return pReason; + } + + OUString ODecimalType::_explainInvalid(TranslateId rReason) + { + sal_Int32 nValue = 0; + OUStringBuffer sInfo; + if (rReason == RID_STR_XFORMS_VALUE_TOTAL_DIGITS) + { + if( m_aTotalDigits >>= nValue ) + sInfo.append( nValue ); + } + else if (rReason == RID_STR_XFORMS_VALUE_FRACTION_DIGITS) + { + if( m_aFractionDigits >>= nValue ) + sInfo.append( nValue ); + } + else + { + sInfo.append(ODecimalType_Base::_explainInvalid(rReason)); + } + return sInfo.makeStringAndClear(); + } + + OUString ODecimalType::typedValueAsHumanReadableString( const Any& _rValue ) const + { + double fValue( 0 ); + normalizeValue( _rValue, fValue ); + return OUString::number( fValue ); + } + + + void ODecimalType::normalizeValue( const Any& _rValue, double& _rDoubleValue ) const + { + OSL_VERIFY( _rValue >>= _rDoubleValue ); + } + + + ODateType::ODateType(const OUString& _rName) + :ODateType_Base(_rName, DataTypeClass::DATE) + { + } + rtl::Reference<OXSDDataType> ODateType::createClone(const OUString& _rName) const + { + return new ODateType(_rName); + } + void ODateType::initializeClone( const OXSDDataType& _rCloneSource ) + { + ODateType_Base::initializeClone(_rCloneSource); + initializeTypedClone(static_cast< const ODateType& >(_rCloneSource)); + } + + TranslateId ODateType::_validate( const OUString& _rValue ) + { + return ODateType_Base::_validate( _rValue ); + } + + bool ODateType::_getValue( const OUString& value, double& fValue ) + { + Any aTypeValue = Convert::get().toAny( value, getCppuType() ); + + Date aValue; + if ( !( aTypeValue >>= aValue ) ) + return false; + + ::Date aToolsDate( aValue.Day, aValue.Month, aValue.Year ); + fValue = aToolsDate.GetDate(); + return true; + } + + + OUString ODateType::typedValueAsHumanReadableString( const Any& _rValue ) const + { + OSL_PRECOND( _rValue.getValueType().equals( getCppuType() ), "ODateType::typedValueAsHumanReadableString: unexpected type" ); + return Convert::get().toXSD( _rValue ); + } + + + void ODateType::normalizeValue( const Any& _rValue, double& _rDoubleValue ) const + { + Date aValue; + OSL_VERIFY( _rValue >>= aValue ); + ::Date aToolsDate( aValue.Day, aValue.Month, aValue.Year ); + _rDoubleValue = aToolsDate.GetDate(); + } + + + OTimeType::OTimeType(const OUString& _rName) + :OTimeType_Base(_rName, DataTypeClass::TIME) + { + } + rtl::Reference<OXSDDataType> OTimeType::createClone(const OUString& _rName) const + { + return new OTimeType(_rName); + } + void OTimeType::initializeClone( const OXSDDataType& _rCloneSource ) + { + OTimeType_Base::initializeClone(_rCloneSource); + initializeTypedClone(static_cast< const OTimeType& >(_rCloneSource)); + } + + TranslateId OTimeType::_validate( const OUString& _rValue ) + { + return OTimeType_Base::_validate( _rValue ); + } + + bool OTimeType::_getValue( const OUString& value, double& fValue ) + { + Any aTypedValue = Convert::get().toAny( value, getCppuType() ); + + css::util::Time aValue; + if ( !( aTypedValue >>= aValue ) ) + return false; + + ::tools::Time aToolsTime( aValue ); + // no loss/rounding; IEEE 754 double-precision floating-point + // has a mantissa of 53 bits; we need at the very most 50 bits: + // format of aToolsTime.GetTime() is (in decimal) hhmmssnnnnnnnnn + // and 999999999999999 = 0x38D7EA4C67FFF + // in reality I doubt we need (much) more than + // 240000000000000 = 0x0DA475ABF0000 + // that is 48 bits + fValue = aToolsTime.GetTime(); + return true; + } + + + OUString OTimeType::typedValueAsHumanReadableString( const Any& _rValue ) const + { + OSL_PRECOND( _rValue.getValueType().equals( getCppuType() ), "OTimeType::typedValueAsHumanReadableString: unexpected type" ); + return Convert::get().toXSD( _rValue ); + } + + + void OTimeType::normalizeValue( const Any& _rValue, double& _rDoubleValue ) const + { + css::util::Time aValue; + OSL_VERIFY( _rValue >>= aValue ); + ::tools::Time aToolsTime( aValue ); + _rDoubleValue = aToolsTime.GetTime(); + } + + + ODateTimeType::ODateTimeType(const OUString& _rName) + :ODateTimeType_Base(_rName, DataTypeClass::DATETIME) + { + } + rtl::Reference<OXSDDataType> ODateTimeType::createClone(const OUString& _rName) const + { + return new ODateTimeType(_rName); + } + void ODateTimeType::initializeClone( const OXSDDataType& _rCloneSource ) + { + ODateTimeType_Base::initializeClone(_rCloneSource); + initializeTypedClone(static_cast< const ODateTimeType& >(_rCloneSource)); + } + + TranslateId ODateTimeType::_validate( const OUString& _rValue ) + { + return ODateTimeType_Base::_validate( _rValue ); + } + + namespace + { + double lcl_normalizeDateTime( const DateTime& _rValue ) + { + ::DateTime aToolsValue(_rValue); + + double fValue = 0; + // days since 1.1.1900 (which is relatively arbitrary but fixed date) + fValue += ::Date( aToolsValue ) - ::Date( 1, 1, 1900 ); + // time + fValue += aToolsValue.GetTimeInDays(); + return fValue; + } + } + + + bool ODateTimeType::_getValue( const OUString& value, double& fValue ) + { + Any aTypedValue = Convert::get().toAny( value, getCppuType() ); + + DateTime aValue; + if ( !( aTypedValue >>= aValue ) ) + return false; + + fValue = lcl_normalizeDateTime( aValue ); + return true; + } + + + OUString ODateTimeType::typedValueAsHumanReadableString( const Any& _rValue ) const + { + OSL_PRECOND( _rValue.getValueType().equals( getCppuType() ), "OTimeType::typedValueAsHumanReadableString: unexpected type" ); + OUString sString = Convert::get().toXSD( _rValue ); + + // ISO 8601 notation has a "T" to separate between date and time. Our only concession + // to the "human readable" in the method name is to replace this T with a whitespace. + OSL_ENSURE( sString.indexOf( 'T' ) != -1, "ODateTimeType::typedValueAsHumanReadableString: hmm - no ISO notation?" ); + return sString.replace( 'T', ' ' ); + } + + + void ODateTimeType::normalizeValue( const Any& _rValue, double& _rDoubleValue ) const + { + DateTime aValue; + OSL_VERIFY( _rValue >>= aValue ); + _rDoubleValue = lcl_normalizeDateTime( aValue ); + } + + OShortIntegerType::OShortIntegerType( const OUString& _rName, sal_Int16 _nTypeClass ) + :OShortIntegerType_Base( _rName, _nTypeClass ) + { + } + + rtl::Reference<OXSDDataType> OShortIntegerType::createClone( const OUString& _rName ) const + { + return new OShortIntegerType( _rName, getTypeClass() ); + } + void OShortIntegerType::initializeClone( const OXSDDataType& _rCloneSource ) + { + OShortIntegerType_Base::initializeClone( _rCloneSource ); + initializeTypedClone( static_cast< const OShortIntegerType& >( _rCloneSource ) ); + } + + bool OShortIntegerType::_getValue( const OUString& value, double& fValue ) + { + fValue = static_cast<double>(static_cast<sal_Int16>(value.toInt32())); + // TODO/eforms + // this does not care for values which do not fit into a sal_Int16, but simply + // cuts them down. A better implementation here should probably return <FALSE/> + // for those values. + // Else, we may have a situation where the UI claims an input to be valid + // (say "12345678"), while internally, and at submission time, this is cut to + // some smaller value. + + // Additionally, this of course does not care for strings which are no numbers... + return true; + } + + + OUString OShortIntegerType::typedValueAsHumanReadableString( const Any& _rValue ) const + { + sal_Int16 nValue( 0 ); + OSL_VERIFY( _rValue >>= nValue ); + return OUString::number( nValue ); + } + + + void OShortIntegerType::normalizeValue( const Any& _rValue, double& _rDoubleValue ) const + { + sal_Int16 nValue( 0 ); + OSL_VERIFY( _rValue >>= nValue ); + _rDoubleValue = nValue; + } + + +template< typename CONCRETE_DATA_TYPE_IMPL, typename SUPERCLASS > +ODerivedDataType< CONCRETE_DATA_TYPE_IMPL, SUPERCLASS >::ODerivedDataType( const OUString& _rName, sal_Int16 _nTypeClass ) + :SUPERCLASS( _rName, _nTypeClass ) + ,m_bPropertiesRegistered( false ) +{ +} + + +template< typename CONCRETE_DATA_TYPE_IMPL, typename SUPERCLASS > +::cppu::IPropertyArrayHelper* ODerivedDataType< CONCRETE_DATA_TYPE_IMPL, SUPERCLASS >::createArrayHelper( ) const +{ + css::uno::Sequence< css::beans::Property > aProps; + ODerivedDataType< CONCRETE_DATA_TYPE_IMPL, SUPERCLASS >::describeProperties( aProps ); + return new ::cppu::OPropertyArrayHelper( aProps ); +} + + +template< typename CONCRETE_DATA_TYPE_IMPL, typename SUPERCLASS > +css::uno::Reference< css::beans::XPropertySetInfo > SAL_CALL ODerivedDataType< CONCRETE_DATA_TYPE_IMPL, SUPERCLASS >::getPropertySetInfo() +{ + return ::cppu::OPropertySetHelper::createPropertySetInfo( getInfoHelper() ); +} + + +template< typename CONCRETE_DATA_TYPE_IMPL, typename SUPERCLASS > +::cppu::IPropertyArrayHelper& SAL_CALL ODerivedDataType< CONCRETE_DATA_TYPE_IMPL, SUPERCLASS >::getInfoHelper() +{ + if ( !m_bPropertiesRegistered ) + { + this->registerProperties(); + m_bPropertiesRegistered = true; + } + + return *ODerivedDataType< CONCRETE_DATA_TYPE_IMPL, SUPERCLASS >::getArrayHelper(); +} + + +template< typename VALUE_TYPE > +OValueLimitedType< VALUE_TYPE >::OValueLimitedType( const OUString& _rName, sal_Int16 _nTypeClass ) + :OValueLimitedType_Base( _rName, _nTypeClass ) +{ +} + +} // namespace xforms + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/forms/source/xforms/datatypes.hxx b/forms/source/xforms/datatypes.hxx new file mode 100644 index 000000000..bed7f2d3a --- /dev/null +++ b/forms/source/xforms/datatypes.hxx @@ -0,0 +1,393 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/util/Date.hpp> +#include <com/sun/star/util/Time.hpp> +#include <com/sun/star/util/DateTime.hpp> +#include <com/sun/star/xsd/XDataType.hpp> +#include <cppuhelper/implbase.hxx> +#include <comphelper/uno3.hxx> +#include <comphelper/propertycontainer.hxx> +#include <comphelper/proparrhlp.hxx> +#include <comphelper/broadcasthelper.hxx> +#include <rtl/ref.hxx> +#include <unotools/resmgr.hxx> + +#include <unicode/regex.h> + +#include <memory> + + +namespace xforms +{ + + + //= OXSDDataType + + typedef ::cppu::WeakImplHelper < css::xsd::XDataType + > OXSDDataType_Base; + typedef ::comphelper::OMutexAndBroadcastHelper OXSDDataType_BBase; + typedef ::comphelper::OPropertyContainer OXSDDataType_PBase; + + class OXSDDataType :public OXSDDataType_Base + ,public OXSDDataType_BBase // order matters: OMutexAndBroadcastHelper before + ,public OXSDDataType_PBase // OPropertyContainer + { + private: + // <properties> + bool m_bIsBasic; + sal_Int16 m_nTypeClass; + OUString m_sName; + OUString m_sPattern; + sal_uInt16 m_nWST; + // </properties> + + ::std::unique_ptr< U_NAMESPACE_QUALIFIER RegexMatcher > + m_pPatternMatcher; + bool m_bPatternMatcherDirty; + + protected: + sal_Int16 getTypeClass() const { return m_nTypeClass; } + + private: + OXSDDataType( const OXSDDataType& ) = delete; + OXSDDataType& operator=( const OXSDDataType& ) = delete; + + protected: + // create basic data type + OXSDDataType( const OUString& _rName, sal_Int16 _nTypeClass ); + virtual ~OXSDDataType() override; + + public: + DECLARE_XINTERFACE() + DECLARE_XTYPEPROVIDER() + + virtual OUString SAL_CALL getName( ) override; + virtual void SAL_CALL setName( const OUString& aName ) override; + virtual OUString SAL_CALL getPattern() override; + virtual void SAL_CALL setPattern( const OUString& _pattern ) override; + virtual sal_Int16 SAL_CALL getWhiteSpaceTreatment() override; + virtual void SAL_CALL setWhiteSpaceTreatment( sal_Int16 _whitespacetreatment ) override; + virtual sal_Bool SAL_CALL getIsBasic() override; + virtual sal_Int16 SAL_CALL getTypeClass() override; + + virtual sal_Bool SAL_CALL validate( const OUString& value ) override; + virtual OUString SAL_CALL explainInvalid( const OUString& value ) override; + + // XPropertySet - is a base of XDataType and needs to be disambiguated + virtual void SAL_CALL setPropertyValue( const OUString& aPropertyName, const css::uno::Any& aValue ) override; + virtual css::uno::Any SAL_CALL getPropertyValue( const OUString& PropertyName ) override; + virtual void SAL_CALL addPropertyChangeListener( const OUString& aPropertyName, const css::uno::Reference< css::beans::XPropertyChangeListener >& xListener ) override; + virtual void SAL_CALL removePropertyChangeListener( const OUString& aPropertyName, const css::uno::Reference< css::beans::XPropertyChangeListener >& aListener ) override; + virtual void SAL_CALL addVetoableChangeListener( const OUString& PropertyName, const css::uno::Reference< css::beans::XVetoableChangeListener >& aListener ) override; + virtual void SAL_CALL removeVetoableChangeListener( const OUString& PropertyName, const css::uno::Reference< css::beans::XVetoableChangeListener >& aListener ) override; + + public: + rtl::Reference<OXSDDataType> clone( const OUString& _rNewName ) const; + + protected: + // XPropertySet and friends + virtual sal_Bool SAL_CALL convertFastPropertyValue( css::uno::Any& _rConvertedValue, css::uno::Any& _rOldValue, sal_Int32 _nHandle, const css::uno::Any& _rValue ) override; + virtual void SAL_CALL setFastPropertyValue_NoBroadcast( + sal_Int32 nHandle, + const css::uno::Any& rValue + ) override; + + // --- own overridables --- + // helper for implementing cloning of data types + virtual rtl::Reference<OXSDDataType> createClone( const OUString& _rName ) const = 0; + virtual void initializeClone( const OXSDDataType& _rCloneSource ); + + // helper method for validate and explainInvalid + virtual TranslateId _validate( const OUString& value ); + virtual OUString _explainInvalid( TranslateId rReason ); + + // helper method for checking properties values which are to be set + virtual bool checkPropertySanity( sal_Int32 _nHandle, const css::uno::Any& _rNewValue, OUString& _rErrorMessage ); + + // register properties implemented by this instance - call the base class when overriding + virtual void registerProperties(); + }; + + + //= helper for deriving from OXSDDataType + + class OValueLimitedType_Base : public OXSDDataType + { + protected: + css::uno::Any m_aMaxInclusive; + css::uno::Any m_aMaxExclusive; + css::uno::Any m_aMinInclusive; + css::uno::Any m_aMinExclusive; + + double m_fCachedMaxInclusive; + double m_fCachedMaxExclusive; + double m_fCachedMinInclusive; + double m_fCachedMinExclusive; + + protected: + OValueLimitedType_Base( const OUString& _rName, sal_Int16 _nTypeClass ); + + virtual void initializeClone( const OXSDDataType& _rCloneSource ) override; + void initializeTypedClone( const OValueLimitedType_Base& _rCloneSource ); + + // XPropertySet and friends + virtual void SAL_CALL setFastPropertyValue_NoBroadcast( + sal_Int32 nHandle, + const css::uno::Any& rValue + ) override; + + // OXSDDataType overridables + virtual bool _getValue( const OUString& value, double& fValue ); + virtual TranslateId _validate( const OUString& value ) override; + virtual OUString _explainInvalid( TranslateId rReason ) override; + + // own overridables + /** translate a given value into a human-readable string + + The value is guaranteed to be not <NULL/>, and is of type <member>ValueType</member> + */ + virtual OUString typedValueAsHumanReadableString( const css::uno::Any& _rValue ) const = 0; + + /** translates a <member>ValueType</member> value into a double value + + The normalization must respect the "<" and "==" relations on the value + space. That is, if two values are equal, their normalizations must be equal, too. + Similarly, if <code>foo</code> is less than <code>bar</code>, the same + must hold for their normalizations. + + @param _rValue + the value to translate. Guaranteed to be not <NULL/>, and of type <member>ValueType</member> + @param _rDoubleValue + output parameter to hold the resulting double value + */ + virtual void normalizeValue( const css::uno::Any& _rValue, double& _rDoubleValue ) const = 0; + }; + + template < typename VALUE_TYPE > + class OValueLimitedType : public OValueLimitedType_Base + { + protected: + typedef VALUE_TYPE ValueType; + const css::uno::Type& + getCppuType() const { return cppu::UnoType<ValueType>::get(); } + + protected: + OValueLimitedType( const OUString& _rName, sal_Int16 _nTypeClass ); + + // OXSDDataType overridables + virtual void registerProperties() override; + }; + + /** helper class for implementing interfaces derived from XDataType + */ + template< typename CONCRETE_DATA_TYPE_IMPL, typename SUPERCLASS = OXSDDataType > + class ODerivedDataType :public SUPERCLASS + ,public ::comphelper::OPropertyArrayUsageHelper< CONCRETE_DATA_TYPE_IMPL > + { + private: + bool m_bPropertiesRegistered; + + protected: + ODerivedDataType( const OUString& _rName, sal_Int16 _nTypeClass ); + + protected: + // OPropertyArrayUsageHelper + virtual ::cppu::IPropertyArrayHelper* createArrayHelper() const override; + + // XPropertySet + virtual css::uno::Reference<css::beans::XPropertySetInfo> SAL_CALL getPropertySetInfo() override; + virtual ::cppu::IPropertyArrayHelper& SAL_CALL getInfoHelper() override; + }; + + class OBooleanType; + typedef ODerivedDataType< OBooleanType > OBooleanType_Base; + class OBooleanType : public OBooleanType_Base + { + public: + explicit OBooleanType( const OUString& _rName ); + + protected: + virtual rtl::Reference<OXSDDataType> createClone(const OUString& _rName) const override; + virtual void initializeClone(const OXSDDataType& _rCloneSource) override; + + // OXSDDataType overridables + virtual TranslateId _validate( const OUString& value ) override; + virtual OUString _explainInvalid( TranslateId rReason ) override; + }; + + class OStringType; + typedef ODerivedDataType< OStringType > OStringType_Base; + class OStringType :public OStringType_Base + { + // <properties> + css::uno::Any m_aLength; + css::uno::Any m_aMinLength; + css::uno::Any m_aMaxLength; + // </properties> + + public: + OStringType( const OUString& _rName, sal_Int16 _nTypeClass /* = css::xsd::DataTypeClass::STRING */ ); + + protected: + virtual rtl::Reference<OXSDDataType> createClone(const OUString& _rName) const override; + virtual void initializeClone(const OXSDDataType& _rCloneSource) override; + void initializeTypedClone( const OStringType& _rCloneSource ); + + // OXSDDataType overridables + virtual TranslateId _validate( const OUString& value ) override; + virtual OUString _explainInvalid( TranslateId rReason ) override; + virtual bool checkPropertySanity( sal_Int32 _nHandle, const css::uno::Any& _rNewValue, OUString& _rErrorMessage ) override; + virtual void registerProperties() override; + }; + + class ODecimalType; + typedef ODerivedDataType< ODecimalType, OValueLimitedType< double > > ODecimalType_Base; + class ODecimalType : public ODecimalType_Base + { + css::uno::Any m_aTotalDigits; + css::uno::Any m_aFractionDigits; + + public: + ODecimalType( const OUString& _rName, sal_Int16 _nTypeClass /* = css::xsd::DataTypeClass::DECIMAL */ ); + + protected: + virtual rtl::Reference<OXSDDataType> createClone(const OUString& _rName) const override; + virtual void initializeClone(const OXSDDataType& _rCloneSource) override; + void initializeTypedClone( const ODecimalType& _rCloneSource ); + + // OXSDDataType overridables + virtual TranslateId _validate( const OUString& value ) override; + virtual OUString _explainInvalid( TranslateId rReason ) override; + virtual void registerProperties() override; + + // OValueLimitedType overridables + virtual OUString typedValueAsHumanReadableString( const css::uno::Any& _rValue ) const override; + virtual void normalizeValue( const css::uno::Any& _rValue, double& _rDoubleValue ) const override; + + private: + using ODecimalType_Base::initializeTypedClone; + }; + + + //= + + class ODateType; + typedef ODerivedDataType< ODateType, OValueLimitedType< css::util::Date > > ODateType_Base; + class ODateType : public ODateType_Base + { + public: + explicit ODateType( const OUString& _rName ); + + protected: + virtual rtl::Reference<OXSDDataType> createClone(const OUString& _rName) const override; + virtual void initializeClone(const OXSDDataType& _rCloneSource) override; + + /* OXSDDataType overridables */ + virtual TranslateId _validate( const OUString& value ) override; + virtual bool _getValue( const OUString& value, double& fValue ) override; + + /* OValueLimitedType overridables */ + virtual OUString typedValueAsHumanReadableString( const css::uno::Any& _rValue ) const override; + virtual void normalizeValue( const css::uno::Any& _rValue, double& _rDoubleValue ) const override; + + private: + using ODateType_Base::initializeTypedClone; + }; + + + class OTimeType; + typedef ODerivedDataType< OTimeType, OValueLimitedType< css::util::Time > > OTimeType_Base; + class OTimeType : public OTimeType_Base + { + public: + explicit OTimeType( const OUString& _rName ); + + protected: + virtual rtl::Reference<OXSDDataType> createClone(const OUString& _rName) const override; + virtual void initializeClone(const OXSDDataType& _rCloneSource) override; + + /* OXSDDataType overridables */ + virtual TranslateId _validate( const OUString& value ) override; + virtual bool _getValue( const OUString& value, double& fValue ) override; + + /* OValueLimitedType overridables */ + virtual OUString typedValueAsHumanReadableString( const css::uno::Any& _rValue ) const override; + virtual void normalizeValue( const css::uno::Any& _rValue, double& _rDoubleValue ) const override; + + private: + using OTimeType_Base::initializeTypedClone; + }; + + + + class ODateTimeType; + typedef ODerivedDataType< OTimeType, OValueLimitedType< css::util::DateTime > > ODateTimeType_Base; + class ODateTimeType : public ODateTimeType_Base + { + public: + explicit ODateTimeType( const OUString& _rName ); + + protected: + virtual rtl::Reference<OXSDDataType> createClone(const OUString& _rName) const override; + virtual void initializeClone(const OXSDDataType& _rCloneSource) override; + + /* OXSDDataType overridables */ + virtual TranslateId _validate( const OUString& value ) override; + virtual bool _getValue( const OUString& value, double& fValue ) override; + + /* OValueLimitedType overridables */ + virtual OUString typedValueAsHumanReadableString( const css::uno::Any& _rValue ) const override; + virtual void normalizeValue( const css::uno::Any& _rValue, double& _rDoubleValue ) const override; + + private: + using ODateTimeType_Base::initializeTypedClone; + }; + + + class OShortIntegerType; + typedef ODerivedDataType< OShortIntegerType, OValueLimitedType< sal_Int16 > > OShortIntegerType_Base; + class OShortIntegerType : public OShortIntegerType_Base + { + public: + OShortIntegerType( const OUString& _rName, sal_Int16 _nTypeClass ); + + protected: + virtual rtl::Reference<OXSDDataType> createClone(const OUString& _rName) const override; + virtual void initializeClone(const OXSDDataType& _rCloneSource) override; + + // OXSDDataType overridables + virtual bool _getValue( const OUString& value, double& fValue ) override; + + // OValueLimitedType overridables + virtual OUString typedValueAsHumanReadableString( const css::uno::Any& _rValue ) const override; + virtual void normalizeValue( const css::uno::Any& _rValue, double& _rDoubleValue ) const override; + + private: + using OShortIntegerType_Base::initializeTypedClone; + }; + + +} // namespace xforms + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/forms/source/xforms/enumeration.cxx b/forms/source/xforms/enumeration.cxx new file mode 100644 index 000000000..e02881fbb --- /dev/null +++ b/forms/source/xforms/enumeration.cxx @@ -0,0 +1,59 @@ +/* -*- 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 . + */ + +#include "enumeration.hxx" + +#include <osl/diagnose.h> + +#include <com/sun/star/container/NoSuchElementException.hpp> +#include <com/sun/star/container/XIndexAccess.hpp> +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/RuntimeException.hpp> + +using com::sun::star::container::NoSuchElementException; +using com::sun::star::container::XIndexAccess; +using com::sun::star::uno::Any; +using com::sun::star::uno::RuntimeException; + +Enumeration::Enumeration(XIndexAccess* pContainer) + : mxContainer(pContainer) + , mnIndex(0) +{ + OSL_ENSURE(mxContainer.is(), "no container?"); +} + +sal_Bool Enumeration::hasMoreElements() +{ + if (!mxContainer.is()) + throw RuntimeException(); + + return mnIndex < mxContainer->getCount(); +} + +Any Enumeration::nextElement() +{ + if (!mxContainer.is()) + throw RuntimeException(); + if (mnIndex >= mxContainer->getCount()) + throw NoSuchElementException(); + + return mxContainer->getByIndex(mnIndex++); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/forms/source/xforms/enumeration.hxx b/forms/source/xforms/enumeration.hxx new file mode 100644 index 000000000..6374e0c8e --- /dev/null +++ b/forms/source/xforms/enumeration.hxx @@ -0,0 +1,50 @@ +/* -*- 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 . + */ + +#pragma once + + +#include <cppuhelper/implbase.hxx> +#include <com/sun/star/container/XEnumeration.hpp> +#include <com/sun/star/uno/Reference.hxx> + +namespace com::sun::star { + namespace container { class XIndexAccess; } + namespace uno { class Any; } + namespace container { class NoSuchElementException; } + namespace lang { class WrappedTargetException; } + namespace uno { class RuntimeException; } +} + +/** implement XEnumeration based on container::XIndexAccess */ +class Enumeration + : public cppu::WeakImplHelper<css::container::XEnumeration> +{ + css::uno::Reference<css::container::XIndexAccess> mxContainer; + sal_Int32 mnIndex; + +public: + explicit Enumeration( css::container::XIndexAccess* ); + + virtual sal_Bool SAL_CALL hasMoreElements() override; + + virtual css::uno::Any SAL_CALL nextElement() override; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/forms/source/xforms/evaluationcontext.hxx b/forms/source/xforms/evaluationcontext.hxx new file mode 100644 index 000000000..145a1f2d2 --- /dev/null +++ b/forms/source/xforms/evaluationcontext.hxx @@ -0,0 +1,57 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/xml/dom/XNode.hpp> +#include <com/sun/star/container/XNameContainer.hpp> +#include <com/sun/star/xforms/XModel.hpp> + +namespace xforms +{ + + +/** define the context for the evaluation of an XPath expression */ +class EvaluationContext +{ +public: + EvaluationContext() + : mxContextNode(), + mxModel(), + mxNamespaces() + { } + + EvaluationContext( + const css::uno::Reference<css::xml::dom::XNode>& xContextNode, + const css::uno::Reference<css::xforms::XModel>& xModel, + const css::uno::Reference<css::container::XNameContainer>& xNamespaces ) + : mxContextNode( xContextNode ), + mxModel( xModel ), + mxNamespaces( xNamespaces ) + { } + + css::uno::Reference<css::xml::dom::XNode> mxContextNode; + css::uno::Reference<css::xforms::XModel> mxModel; + css::uno::Reference<css::container::XNameContainer> mxNamespaces; +}; + + +} // namespace xforms + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/forms/source/xforms/mip.cxx b/forms/source/xforms/mip.cxx new file mode 100644 index 000000000..b1759ecbe --- /dev/null +++ b/forms/source/xforms/mip.cxx @@ -0,0 +1,98 @@ +/* -*- 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 . + */ + + +#include "mip.hxx" + +namespace xforms +{ + +MIP::MIP() +{ + resetReadonly(); + resetRequired(); + resetRelevant(); + resetConstraint(); + mbHasCalculate = false; + resetTypeName(); +} + +void MIP::inherit( const MIP& rMip ) +{ + if( ! mbHasReadonly ) + { + mbHasReadonly = rMip.mbHasReadonly; + mbReadonly = rMip.isReadonly(); + } + if( ! mbHasRequired ) + { + mbHasRequired = rMip.mbHasRequired; + mbRequired = rMip.isRequired(); + } + if( ! mbHasRelevant ) + { + mbHasRelevant = rMip.mbHasRelevant; + mbRelevant = rMip.isRelevant(); + } + if( ! mbHasConstraint ) + { + mbHasConstraint = rMip.mbHasConstraint; + mbConstraint = rMip.isConstraint(); + msConstraintExplanation = rMip.getConstraintExplanation(); + } + if( ! mbHasCalculate ) + { + mbHasCalculate = rMip.mbHasCalculate; + } + if( ! mbHasTypeName ) + { + mbHasTypeName = rMip.mbHasTypeName; + msTypeName = rMip.getTypeName(); + } +} + +void MIP::join( const MIP& rMip ) +{ + // TODO: inherit only inheritable MIPs... + inherit( rMip ); +} + +bool MIP::isReadonly() const { return mbHasReadonly ? mbReadonly : mbHasCalculate; } +void MIP::setReadonly( bool b ) { mbHasReadonly = true; mbReadonly = b; } +void MIP::resetReadonly() { mbHasReadonly = false; mbReadonly = false; } + +void MIP::setRequired( bool b ) { mbHasRequired = true; mbRequired = b; } +void MIP::resetRequired() { mbHasRequired = false; mbRequired = false; } + +void MIP::setRelevant( bool b ) { mbHasRelevant = true; mbRelevant = b; } +void MIP::resetRelevant() { mbHasRelevant = false; mbRelevant = true; } + +void MIP::setConstraint( bool b ) { mbHasConstraint = true; mbConstraint = b; msConstraintExplanation.clear(); } +void MIP::resetConstraint() { mbHasConstraint = false; mbConstraint = true; msConstraintExplanation.clear(); } + +void MIP::setConstraintExplanation( const OUString& s ) { msConstraintExplanation = s; } + +void MIP::setHasCalculate( bool b ) { mbHasCalculate = b; } + +void MIP::setTypeName( const OUString& s ) { msTypeName = s; mbHasTypeName = true; } +void MIP::resetTypeName() { msTypeName.clear(); mbHasTypeName = false; } + +} // namespace xforms + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/forms/source/xforms/mip.hxx b/forms/source/xforms/mip.hxx new file mode 100644 index 000000000..97b98d04e --- /dev/null +++ b/forms/source/xforms/mip.hxx @@ -0,0 +1,102 @@ +/* -*- 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 . + */ + +#pragma once + +#include <rtl/ustring.hxx> + +namespace xforms +{ +/** represents the XForms *m*odel *i*tem *p*roperties (MIPs) for a + * given XNode in the instance data at a given point in time. The + * values will not be updated; for updated values new MIP objects have + * to be created/queried. */ +class MIP +{ + bool mbHasReadonly; + bool mbReadonly; + + bool mbHasRequired; + bool mbRequired; + + bool mbHasRelevant; + bool mbRelevant; + + bool mbHasConstraint; + bool mbConstraint; + + bool mbHasCalculate; + + bool mbHasTypeName; + OUString msTypeName; + + OUString msConstraintExplanation; + +public: + MIP(); + + /// inherit from upper-level MIPs + void inherit(const MIP&); + + /// join with same-level MIPs + void join(const MIP&); + + // - type (static; default: xsd:string) + // (currently default implemented as empty string) + const OUString& getTypeName() const { return msTypeName; } + void setTypeName(const OUString&); + void resetTypeName(); + + // - readonly (computed XPath; default: false; true if calculate exists) + bool isReadonly() const; + void setReadonly(bool); + void resetReadonly(); + + // - required (computed XPath; default: false) + bool isRequired() const { return mbRequired; } + void setRequired(bool); + void resetRequired(); + + // - relevant (computed XPath; default: true) + bool isRelevant() const { return mbRelevant; } + void setRelevant(bool); + void resetRelevant(); + + // - constraint (computed XPath; default: true) + bool isConstraint() const { return mbConstraint; } + void setConstraint(bool); + void resetConstraint(); + + // explain _why_ a constraint failed + void setConstraintExplanation(const OUString&); + const OUString& getConstraintExplanation() const { return msConstraintExplanation; } + + // - calculate (computed XPath; default: has none (false)) + // (for calculate, we only store whether a calculate MIP is present; + // the actual calculate value is handled my changing the instance + // directly) + void setHasCalculate(bool); + + // - minOccurs/maxOccurs (computed XPath; default: 0/inf) + // - p3ptype (static; no default) +}; + +} // namespace xforms + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/forms/source/xforms/model.cxx b/forms/source/xforms/model.cxx new file mode 100644 index 000000000..7001f4aaa --- /dev/null +++ b/forms/source/xforms/model.cxx @@ -0,0 +1,619 @@ +/* -*- 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 . + */ + + +#include "model.hxx" + +#include "model_helper.hxx" +#include "unohelper.hxx" +#include "binding.hxx" +#include "submission.hxx" +#include "mip.hxx" +#include "evaluationcontext.hxx" +#include "xmlhelper.hxx" +#include "datatyperepository.hxx" +#include "NameContainer.hxx" + +#include <rtl/ustring.hxx> +#include <rtl/ustrbuf.hxx> +#include <tools/debug.hxx> + +#include <comphelper/processfactory.hxx> +#include <comphelper/servicehelper.hxx> +#include <cppuhelper/supportsservice.hxx> + +#include <algorithm> + +// UNO classes +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <com/sun/star/xml/dom/XDocument.hpp> +#include <com/sun/star/xml/dom/XCharacterData.hpp> +#include <com/sun/star/xml/dom/NodeType.hpp> +#include <com/sun/star/xml/dom/XDocumentBuilder.hpp> +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/ucb/SimpleFileAccess.hpp> +#include <com/sun/star/io/XInputStream.hpp> + + +using com::sun::star::lang::XUnoTunnel; +using com::sun::star::beans::XPropertySet; +using com::sun::star::beans::PropertyValue; +using com::sun::star::ucb::SimpleFileAccess; +using com::sun::star::io::XInputStream; + +using namespace com::sun::star::uno; +using namespace com::sun::star::xml::dom; +using namespace xforms; + + +#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG +#define DBG_INVARIANT_TYPE(TYPE) class DBG_##TYPE { const TYPE* mpT; void check() { mpT->dbg_assertInvariant(); } public: DBG_##TYPE(const TYPE* pT) : mpT(pT) { check(); } ~DBG_##TYPE() { check(); } } _DBG_##TYPE(this); + +#define DBG_INVARIANT() DBG_INVARIANT_TYPE(Model) +#else +#define DBG_INVARIANT() +#endif + + +// The Model + + +void Model::ensureAtLeastOneInstance() +{ + if( ! mxInstances->hasItems() ) + { + // create a default instance + newInstance( OUString(), OUString(), true ); + } +} + + +/** Model default constructor; create empty model */ +Model::Model() : + mxInstances( new InstanceCollection ), + mxNamespaces( new NameContainer<OUString>() ), + mbInitialized( false ), + mbExternalData( true ) +{ + initializePropertySet(); + + // initialize bindings collections + // (not in initializer list to avoid use of incomplete 'this') + mxBindings = new BindingCollection( this ); + mxSubmissions = new SubmissionCollection( this ); + + // invariant only holds after construction + DBG_INVARIANT(); +} + +Model::~Model() noexcept +{ +} + +EvaluationContext Model::getEvaluationContext() +{ + // the default context is the top-level element node. A default + // node (instanceData' is inserted when there is no default node + Reference<XDocument> xInstance = getDefaultInstance(); + Reference<XNode> xElement = xInstance->getDocumentElement(); + + // no element found? Then insert default element 'instanceData' + if( ! xElement.is() ) + { + xElement.set( xInstance->createElement( "instanceData" ), UNO_QUERY_THROW ); + xInstance->appendChild( xElement ); + } + + OSL_ENSURE( xElement.is() && + xElement->getNodeType() == NodeType_ELEMENT_NODE, + "no element in evaluation context" ); + + return EvaluationContext( xElement, this, mxNamespaces ); +} + + +css::uno::Sequence<sal_Int8> Model::getUnoTunnelId() +{ + static const comphelper::UnoIdInit aImplementationId; + return aImplementationId.getSeq(); +} + + +void Model::setForeignSchema( const css::uno::Reference<css::xml::dom::XDocument>& rDocument ) +{ + mxForeignSchema = rDocument; +} + + +void Model::setSchemaRef( const OUString& rSchemaRef ) +{ + msSchemaRef = rSchemaRef; +} + + +void Model::setNamespaces( const css::uno::Reference<css::container::XNameContainer>& rNamespaces ) +{ + if( rNamespaces.is() ) + mxNamespaces = rNamespaces; +} + + +void Model::setExternalData( bool _bData ) +{ + mbExternalData = _bData; +} + +#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG +void Model::dbg_assertInvariant() const +{ + assert(mxInstances && "no instances found"); + assert(mxBindings && "no bindings element"); + assert(mxSubmissions && "no submissions element"); +} +#endif + + +// MIP management +void Model::addMIP( void* pTag, const XNode_t& xNode, const MIP& rMIP ) +{ + OSL_ENSURE( pTag != nullptr, "empty tag?" ); + OSL_ENSURE( xNode.is(), "no node" ); + + MIPs_t::value_type aValue( xNode, ::std::pair<void*,MIP>( pTag, rMIP ) ); + maMIPs.insert( aValue ); +} + +void Model::removeMIPs( void const * pTag ) +{ + OSL_ENSURE( pTag != nullptr, "empty tag?" ); + + for( MIPs_t::iterator aIter = maMIPs.begin(); + aIter != maMIPs.end(); ) + { + if( aIter->second.first == pTag ) + { + aIter = maMIPs.erase( aIter ); + } + else + ++aIter; + } +} + +MIP Model::queryMIP( const XNode_t& xNode ) const +{ + // travel up inheritance chain and inherit MIPs + MIP aRet; + for( XNode_t xCurrent = xNode; + xCurrent.is(); + xCurrent = xCurrent->getParentNode() ) + { + // iterate over all MIPs for this node, and join MIPs + MIP aMIP; + MIPs_t::const_iterator aEnd = maMIPs.upper_bound( xCurrent ); + MIPs_t::const_iterator aIter = maMIPs.lower_bound( xCurrent ); + for( ; aIter != aEnd; ++aIter ) + aMIP.join( aIter->second.second ); + + // inherit from current node (or set if we are at the start node) + if( xCurrent == xNode ) + aRet = aMIP; + else + aRet.inherit( aMIP ); + } + + return aRet; +} + + +void Model::rebind() +{ + OSL_ENSURE( mxBindings, "bindings?" ); + + // iterate over all bindings and call update + sal_Int32 nCount = mxBindings->countItems(); + for( sal_Int32 i = 0; i < nCount; i++ ) + { + Binding* pBind = comphelper::getFromUnoTunnel<Binding>( mxBindings->Collection<XPropertySet_t>::getItem( i ) ); + OSL_ENSURE( pBind != nullptr, "binding?" ); + pBind->update(); + } +} + + +void Model::deferNotifications( bool bDefer ) +{ + // iterate over all bindings and defer notifications + sal_Int32 nCount = mxBindings->countItems(); + for( sal_Int32 i = 0; i < nCount; i++ ) + { + Binding* pBind = comphelper::getFromUnoTunnel<Binding>( mxBindings->Collection<XPropertySet_t>::getItem( i ) ); + OSL_ENSURE( pBind != nullptr, "binding?" ); + pBind->deferNotifications( bDefer ); + } +} + + +bool Model::setSimpleContent( const XNode_t& xConstNode, + const OUString& sValue ) +{ + OSL_ENSURE( xConstNode.is(), "need node to set data" ); + + bool bRet = false; + if( xConstNode.is() ) + { + // non-const node reference so we can assign children (if necessary) + XNode_t xNode( xConstNode ); + + switch( xNode->getNodeType() ) + { + case NodeType_ELEMENT_NODE: + { + // find first text node child + Reference<XNode> xChild; + for( xChild = xNode->getFirstChild(); + xChild.is() && xChild->getNodeType() != NodeType_TEXT_NODE; + xChild = xChild->getNextSibling() ) + ; // empty loop; only find first text node child + + // create text node, if none is found + if( ! xChild.is() ) + { + xChild.set( + xNode->getOwnerDocument()->createTextNode( OUString() ), + UNO_QUERY_THROW ); + xNode->appendChild( xChild ); + } + xNode = xChild; + + OSL_ENSURE( xNode.is() && + xNode->getNodeType() == NodeType_TEXT_NODE, + "text node creation failed?" ); + [[fallthrough]]; // continue as with text node: + } + + case NodeType_TEXT_NODE: + case NodeType_ATTRIBUTE_NODE: + { + // set the node value (defer notifications) + if( xNode->getNodeValue() != sValue ) + { + deferNotifications( true ); + xNode->setNodeValue( sValue ); + deferNotifications( false ); + } + bRet = true; + } + break; + + default: + { + OSL_FAIL( "bound to unknown node type?" ); + } + break; + + } + } + return bRet; +} + +void Model::loadInstance( sal_Int32 nInstance ) +{ + Sequence<PropertyValue> aSequence = mxInstances->getItem( nInstance ); + + // find URL from instance + OUString sURL; + bool bOnce = false; + getInstanceData( aSequence, nullptr, nullptr, &sURL, &bOnce ); + + // if we have a URL, load the document and set it into the instance + if( sURL.isEmpty() ) + return; + + try + { + Reference<XInputStream> xInput = + SimpleFileAccess::create( ::comphelper::getProcessComponentContext() )->openFileRead( sURL ); + if( xInput.is() ) + { + Reference<XDocument> xInstance = + getDocumentBuilder()->parse( xInput ); + if( xInstance.is() ) + { + OUString sEmpty; + setInstanceData( aSequence, nullptr, &xInstance, + bOnce ? &sEmpty : &sURL, nullptr); + mxInstances->setItem( nInstance, aSequence ); + } + } + } + catch( const Exception& ) + { + // couldn't load the instance -> ignore! + } +} + +void Model::loadInstances() +{ + // iterate over instance array to get PropertyValue-Sequence + const sal_Int32 nInstances = mxInstances->countItems(); + for( sal_Int32 nInstance = 0; nInstance < nInstances; nInstance++ ) + { + loadInstance( nInstance ); + } +} + + +bool Model::isValid() const +{ + bool bValid = true; + sal_Int32 nCount = mxBindings->countItems(); + for( sal_Int32 i = 0; bValid && i < nCount; i++ ) + { + Binding* pBind = comphelper::getFromUnoTunnel<Binding>( mxBindings->Collection<XPropertySet_t>::getItem( i ) ); + OSL_ENSURE( pBind != nullptr, "binding?" ); + bValid = pBind->isValid(); + } + return bValid; +} + + +// implement xforms::XModel + + +OUString Model::getID() +{ + DBG_INVARIANT(); + return msID; +} + +void Model::setID( const OUString& sID ) +{ + DBG_INVARIANT(); + msID = sID; +} + +void Model::initialize() +{ + DBG_ASSERT( ! mbInitialized, "model already initialized" ); + + // load instances + loadInstances(); + + // let's pretend we're initialized and rebind all bindings + mbInitialized = true; + rebind(); +} + +void Model::rebuild() +{ + if( ! mbInitialized ) + initialize(); + else + rebind(); +} + +void Model::recalculate() +{ + rebind(); +} + +void Model::revalidate() +{ + // do nothing. We don't validate anyways! +} + +void Model::refresh() +{ + rebind(); +} + + +void SAL_CALL Model::submitWithInteraction( + const OUString& sID, + const css::uno::Reference<css::task::XInteractionHandler>& _rxHandler ) +{ + DBG_INVARIANT(); + + if( mxSubmissions->hasItem( sID ) ) + { + Submission* pSubmission = + comphelper::getFromUnoTunnel<Submission>( mxSubmissions->getItem( sID ) ); + OSL_ENSURE( pSubmission != nullptr, "no submission?" ); + OSL_ENSURE( pSubmission->getModel() == Reference<XModel>( this ), + "wrong model" ); + + // submit. All exceptions are allowed to leave. + pSubmission->submitWithInteraction( _rxHandler ); + } +} + +void Model::submit( const OUString& sID ) +{ + submitWithInteraction( sID, nullptr ); +} + +css::uno::Reference<css::xforms::XDataTypeRepository> SAL_CALL Model::getDataTypeRepository( ) +{ + if ( !mxDataTypes.is() ) + mxDataTypes = new ODataTypeRepository; + + return mxDataTypes; +} + + +// instance management + + +css::uno::Reference<css::container::XSet> Model::getInstances() +{ + return mxInstances; +} + +css::uno::Reference<css::xml::dom::XDocument> Model::getInstanceDocument( const OUString& rName ) +{ + ensureAtLeastOneInstance(); + Reference<XDocument> aInstance; + sal_Int32 nInstance = lcl_findInstance( mxInstances.get(), rName ); + if( nInstance != -1 ) + getInstanceData( mxInstances->getItem( nInstance ), + nullptr, &aInstance, nullptr, nullptr ); + return aInstance; +} + +css::uno::Reference<css::xml::dom::XDocument> SAL_CALL Model::getDefaultInstance() +{ + ensureAtLeastOneInstance(); + DBG_ASSERT( mxInstances->countItems() > 0, "no instance?" ); + Reference<XDocument> aInstance; + getInstanceData( mxInstances->getItem( 0 ), nullptr, &aInstance, nullptr, nullptr ); + return aInstance; +} + + +// bindings management + + +css::uno::Reference<css::beans::XPropertySet> SAL_CALL Model::createBinding() +{ + DBG_INVARIANT(); + return new Binding(); +} + +css::uno::Reference<css::beans::XPropertySet> Model::cloneBinding( const css::uno::Reference<css::beans::XPropertySet>& xBinding ) +{ + DBG_INVARIANT(); + XPropertySet_t xNewBinding = createBinding(); + copy( xBinding, xNewBinding ); + return xNewBinding; +} + +css::uno::Reference<css::beans::XPropertySet> Model::getBinding( const OUString& sId ) +{ + DBG_INVARIANT(); + return mxBindings->hasItem( sId ) ? mxBindings->getItem( sId ) : nullptr; +} + +css::uno::Reference<css::container::XSet> Model::getBindings() +{ + DBG_INVARIANT(); + return mxBindings; +} + + +// submission management + + +css::uno::Reference<css::xforms::XSubmission> Model::createSubmission() +{ + DBG_INVARIANT(); + return new Submission(); +} + +css::uno::Reference<css::xforms::XSubmission> Model::cloneSubmission(const css::uno::Reference<css::beans::XPropertySet>& xSubmission) +{ + DBG_INVARIANT(); + css::uno::Reference<css::xforms::XSubmission> xNewSubmission = createSubmission(); + XPropertySet_t xAsPropertySet( xNewSubmission ); + copy( xSubmission, xAsPropertySet ); + return xNewSubmission; +} + +css::uno::Reference<css::xforms::XSubmission> Model::getSubmission( const OUString& sId ) +{ + DBG_INVARIANT(); + css::uno::Reference<css::xforms::XSubmission> xSubmission; + if ( mxSubmissions->hasItem( sId ) ) + xSubmission.set(mxSubmissions->getItem( sId ), css::uno::UNO_QUERY); + return xSubmission; +} + +css::uno::Reference<css::container::XSet> Model::getSubmissions() +{ + DBG_INVARIANT(); + return mxSubmissions; +} + + +// implement XPropertySet & friends + + +#define HANDLE_ID 0 +#define HANDLE_ForeignSchema 3 +#define HANDLE_SchemaRef 4 +#define HANDLE_Namespaces 5 +#define HANDLE_ExternalData 6 + +void Model::initializePropertySet() +{ + registerProperty( css::beans::Property("ID", HANDLE_ID, cppu::UnoType<OUString>::get(), css::beans::PropertyAttribute::BOUND ), + new APIPropertyAccessor< Model, OUString >(this, &Model::setID, &Model::getID) ); + registerProperty( css::beans::Property("ForeignSchema", HANDLE_ForeignSchema, cppu::UnoType<css::uno::Reference<css::xml::dom::XDocument>>::get(), css::beans::PropertyAttribute::BOUND ), + new DirectPropertyAccessor< Model, css::uno::Reference<css::xml::dom::XDocument> >( this, &Model::setForeignSchema, &Model::getForeignSchema) ); + + registerProperty( css::beans::Property("SchemaRef", HANDLE_SchemaRef, cppu::UnoType<OUString>::get(), css::beans::PropertyAttribute::BOUND ), + new DirectPropertyAccessor< Model, OUString >( this, &Model::setSchemaRef, &Model::getSchemaRef) ); + + registerProperty( css::beans::Property("Namespaces", HANDLE_Namespaces, cppu::UnoType<css::uno::Reference<css::container::XNameContainer>>::get(), css::beans::PropertyAttribute::BOUND ), + new DirectPropertyAccessor< Model, css::uno::Reference<css::container::XNameContainer> >( this, &Model::setNamespaces, &Model::getNamespaces) ); + + registerProperty( css::beans::Property("ExternalData", HANDLE_ExternalData, cppu::UnoType<sal_Bool>::get(), css::beans::PropertyAttribute::BOUND ), + new BooleanPropertyAccessor< Model >( this, &Model::setExternalData, &Model::getExternalData ) ); +} + +void Model::update() +{ + rebuild(); +} + + +sal_Int64 Model::getSomething( const css::uno::Sequence<sal_Int8>& xId ) +{ + return comphelper::getSomethingImpl(xId, this); +} + +Sequence<sal_Int8> Model::getImplementationId() +{ + return css::uno::Sequence<sal_Int8>(); +} + +OUString Model::getImplementationName() +{ + return "com.sun.star.form.Model"; +} + +sal_Bool Model::supportsService(OUString const & ServiceName) +{ + return cppu::supportsService(this, ServiceName); +} + +css::uno::Sequence<OUString> Model::getSupportedServiceNames() +{ + return {"com.sun.star.xforms.Model"}; +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +com_sun_star_form_Model_get_implementation(css::uno::XComponentContext*, + css::uno::Sequence<css::uno::Any> const &) +{ + return cppu::acquire(new xforms::Model()); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/forms/source/xforms/model.hxx b/forms/source/xforms/model.hxx new file mode 100644 index 000000000..9deb8a834 --- /dev/null +++ b/forms/source/xforms/model.hxx @@ -0,0 +1,372 @@ +/* -*- 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 . + */ + +#pragma once + +#include <cppuhelper/implbase.hxx> +#include "propertysetbase.hxx" +#include <com/sun/star/xforms/XModel2.hpp> +#include <com/sun/star/xforms/XFormsUIHelper1.hpp> +#include <com/sun/star/util/XUpdatable.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/lang/XUnoTunnel.hpp> +#include <com/sun/star/uno/Reference.hxx> +#include <rtl/ref.hxx> +#include "mip.hxx" +#include <map> + + +// forward declaractions +namespace com::sun::star +{ + namespace xml::dom { class XDocument; } + namespace xml::dom { class XNode; } + namespace uno { template<typename T> class Sequence; } + namespace lang { class IndexOutOfBoundsException; } + namespace lang { class IllegalArgumentException; } + namespace beans { class XPropertySet; } + namespace container { class XSet; } + namespace container { class XNameContainer; } + namespace frame { class XModel; } +} +namespace xforms +{ + class BindingCollection; + class SubmissionCollection; + class InstanceCollection; + class EvaluationContext; +} + + +namespace xforms +{ + +/** An XForms Model. Contains: + * # (set of) instance data (XML DOM tree) + * # (set of) bindings + * # (set of) submissions + * # (NOT YET IMPLEMENTED) actions (set of) + * + * See http://www.w3.org/TR/xforms/ for more information. + */ +typedef cppu::ImplInheritanceHelper< + PropertySetBase, + css::xforms::XModel2, + css::xforms::XFormsUIHelper1, + css::util::XUpdatable, + css::lang::XUnoTunnel, + css::lang::XServiceInfo +> Model_t; +class Model : public Model_t +{ + // a number of local typedefs, to make the remaining header readable + typedef css::uno::Reference<css::xml::dom::XNode> XNode_t; + typedef css::uno::Reference<css::beans::XPropertySet> XPropertySet_t; + + typedef std::multimap<XNode_t,std::pair<void*,MIP> > MIPs_t; + + +private: + + OUString msID; /// the model ID + rtl::Reference<BindingCollection> mxBindings; /// the bindings + rtl::Reference<SubmissionCollection> mxSubmissions; /// the submissions + rtl::Reference<InstanceCollection> mxInstances; /// the instance(s) + + css::uno::Reference<css::xforms::XDataTypeRepository> mxDataTypes; /// the XSD data-types used + css::uno::Reference<css::xml::dom::XDocument> mxForeignSchema; /// the XSD-schema part we cannot + /// map onto data types + OUString msSchemaRef; /// xforms:model/@schema attribute + + css::uno::Reference<css::container::XNameContainer> mxNamespaces; /// namespaces for entire model + + MIPs_t maMIPs; /// map nodes to their MIPs + + bool mbInitialized; /// has model been initialized ? + bool mbExternalData; /// is the data of this model to be considered an integral part of the document? + + void initializePropertySet(); + + void ensureAtLeastOneInstance(); + + +public: + + /// create a new model with an empty, default instance + Model(); + virtual ~Model() noexcept override; + + xforms::EvaluationContext getEvaluationContext(); + + + static css::uno::Sequence<sal_Int8> getUnoTunnelId(); + + + // get/set that part of the schema, that we can't interpret as data types + css::uno::Reference<css::xml::dom::XDocument> getForeignSchema() const { return mxForeignSchema;} + void setForeignSchema( const css::uno::Reference<css::xml::dom::XDocument>& ); + + // get/set the xforms:model/@schema attribute + OUString getSchemaRef() const { return msSchemaRef;} + void setSchemaRef( const OUString& ); + + // get/set namespaces for entire model + css::uno::Reference<css::container::XNameContainer> getNamespaces() const { return mxNamespaces;} + void setNamespaces( const css::uno::Reference<css::container::XNameContainer>& ); + + // get/set the ExternalData property + bool getExternalData() const { return mbExternalData;} + void setExternalData( bool _bData ); + + +#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG + void dbg_assertInvariant() const; +#endif + + + // MIP (model item property) management + + + // register MIPs which apply to a given node; only to be called by bindings + // (The pTag parameter serves only to be able to remove the MIPs + // that were added using the same tag. No functions will be + // performed on it; hence the void* type.) + void addMIP( void* pTag, const XNode_t&, const MIP& ); + void removeMIPs( void const * pTag ); + + /// query which MIPs apply to the given node + MIP queryMIP( const XNode_t& xNode ) const; + + /// re-bind all bindings + void rebind(); + + /// call defer notifications on all bindings + void deferNotifications( bool ); + + /// set a data value in the instance + /// (also defers notifications) + bool setSimpleContent( const XNode_t&, const OUString& ); + + /// load instance data + void loadInstance( sal_Int32 nInstance ); + void loadInstances(); + + /// has model been initialized? + bool isInitialized() const { return mbInitialized;} + + /// is model currently valid (for submission)? + bool isValid() const; + + + // XModel + // implement the xforms::XModel implementation + + + virtual OUString SAL_CALL getID() override; + + virtual void SAL_CALL setID( const OUString& sID ) override; + + virtual void SAL_CALL initialize() override; + + virtual void SAL_CALL rebuild() override; + + virtual void SAL_CALL recalculate() override; + + virtual void SAL_CALL revalidate() override; + + virtual void SAL_CALL refresh() override; + + virtual void SAL_CALL submit( const OUString& sID ) override; + + virtual void SAL_CALL submitWithInteraction( const OUString& id, const css::uno::Reference<css::task::XInteractionHandler>& _rxHandler ) override; + + virtual css::uno::Reference<css::xforms::XDataTypeRepository> SAL_CALL getDataTypeRepository( ) override; + + + // XModel: instance management + + virtual css::uno::Reference<css::container::XSet> SAL_CALL getInstances() override; + + virtual css::uno::Reference<css::xml::dom::XDocument> SAL_CALL getInstanceDocument( const OUString& ) override; + + virtual css::uno::Reference<css::xml::dom::XDocument> SAL_CALL getDefaultInstance() override; + + + // XModel: binding management + + virtual css::uno::Reference<css::beans::XPropertySet> SAL_CALL createBinding() override; + + virtual css::uno::Reference<css::beans::XPropertySet> SAL_CALL cloneBinding( const css::uno::Reference<css::beans::XPropertySet>& ) override; + + virtual css::uno::Reference<css::beans::XPropertySet> SAL_CALL getBinding( const OUString& ) override; + + virtual css::uno::Reference<css::container::XSet> SAL_CALL getBindings() override; + + + // XModel: submission management + + virtual css::uno::Reference<css::xforms::XSubmission> SAL_CALL createSubmission() override; + + virtual css::uno::Reference<css::xforms::XSubmission> SAL_CALL cloneSubmission( const css::uno::Reference<css::beans::XPropertySet>& ) override; + + virtual css::uno::Reference<css::xforms::XSubmission> SAL_CALL getSubmission( const OUString& ) override; + + virtual css::uno::Reference<css::container::XSet> SAL_CALL getSubmissions() override; + + // XPropertySet + + virtual css::uno::Any SAL_CALL getPropertyValue(const OUString& p) override + { return PropertySetBase::getPropertyValue(p); } + + virtual void SAL_CALL addPropertyChangeListener(const OUString& p1, const css::uno::Reference<css::beans::XPropertyChangeListener>& p2) override + { PropertySetBase::addPropertyChangeListener(p1, p2); } + + virtual void SAL_CALL removePropertyChangeListener(const OUString& p1, const css::uno::Reference<css::beans::XPropertyChangeListener>& p2) override + { PropertySetBase::removePropertyChangeListener(p1, p2); } + + virtual void SAL_CALL addVetoableChangeListener(const OUString& p1, const css::uno::Reference<css::beans::XVetoableChangeListener>& p2) override + { PropertySetBase::addVetoableChangeListener(p1, p2); } + + virtual void SAL_CALL removeVetoableChangeListener(const OUString& p1, const css::uno::Reference<css::beans::XVetoableChangeListener>& p2) override + { PropertySetBase::removeVetoableChangeListener(p1, p2); } + + virtual css::uno::Reference<css::beans::XPropertySetInfo> SAL_CALL getPropertySetInfo() override + { return PropertySetBase::getPropertySetInfo(); } + + virtual void SAL_CALL setPropertyValue(const OUString& p1, const css::uno::Any& p2) override + { PropertySetBase::setPropertyValue(p1, p2); } + + + // XFormsUIHelper1 & friends: + // (implementation in model_ui.cxx) + + + /// determine a reasonable control service for a given node + /// (based on data type MIP assigned to the node) + virtual OUString SAL_CALL getDefaultServiceNameForNode( const css::uno::Reference<css::xml::dom::XNode>& xNode ) override; + + /// call getDefaultBindingExpressionForNode with default evaluation context + virtual OUString SAL_CALL getDefaultBindingExpressionForNode( const css::uno::Reference<css::xml::dom::XNode>& xNode ) override; + + /// determine a reasonable default binding expression for a given node + /// and a given evaluation context + /// @returns expression, or empty string if no expression could be derived + OUString getDefaultBindingExpressionForNode( + const XNode_t&, + const EvaluationContext& ); + + virtual OUString SAL_CALL getNodeDisplayName( const css::uno::Reference<css::xml::dom::XNode>&, + sal_Bool bDetail ) override; + + virtual OUString SAL_CALL getNodeName( const css::uno::Reference<css::xml::dom::XNode>& ) override; + + virtual OUString SAL_CALL getBindingName( const css::uno::Reference< ::css::beans::XPropertySet >&, + sal_Bool bDetail ) override; + + virtual OUString SAL_CALL getSubmissionName( const css::uno::Reference< ::css::beans::XPropertySet >&, + sal_Bool bDetail ) override; + + virtual css::uno::Reference< ::css::beans::XPropertySet > SAL_CALL cloneBindingAsGhost( const css::uno::Reference< ::css::beans::XPropertySet >& ) override; + + virtual void SAL_CALL removeBindingIfUseless( const css::uno::Reference< ::css::beans::XPropertySet >& ) override; + + virtual css::uno::Reference<css::xml::dom::XDocument> SAL_CALL newInstance( const OUString& sName, + const OUString& sURL, + sal_Bool bURLOnce ) override; + + virtual void SAL_CALL renameInstance( const OUString& sFrom, + const OUString& sTo, + const OUString& sURL, + sal_Bool bURLOnce ) override; + + virtual void SAL_CALL removeInstance( const OUString& sName ) override; + + + virtual css::uno::Reference<css::xforms::XModel> SAL_CALL newModel( const css::uno::Reference<css::frame::XModel>& xComponent, + const OUString& sName ) override; + virtual void SAL_CALL renameModel( const css::uno::Reference<css::frame::XModel>& xComponent, + const OUString& sFrom, + const OUString& sTo ) override; + + virtual void SAL_CALL removeModel( const css::uno::Reference<css::frame::XModel>& xComponent, + const OUString& sName ) override; + + + virtual css::uno::Reference< css::xml::dom::XNode > SAL_CALL createElement( + const css::uno::Reference< ::css::xml::dom::XNode >& xParent, + const OUString& sName ) override; + + virtual css::uno::Reference< css::xml::dom::XNode > SAL_CALL createAttribute( + const css::uno::Reference< ::css::xml::dom::XNode >& xParent, + const OUString& sName ) override; + + virtual css::uno::Reference< css::xml::dom::XNode > SAL_CALL renameNode( + const css::uno::Reference< ::css::xml::dom::XNode >& xNode, + const OUString& sName ) override; + + virtual css::uno::Reference< css::beans::XPropertySet > SAL_CALL getBindingForNode( const + css::uno::Reference<css::xml::dom::XNode>&, + sal_Bool bCreate ) override; + + virtual void SAL_CALL removeBindingForNode( const css::uno::Reference< ::css::xml::dom::XNode >& ) override; + + virtual OUString SAL_CALL getResultForExpression( + const css::uno::Reference< css::beans::XPropertySet >& xBinding, + sal_Bool bIsBindingExpression, + const OUString& sExpression ) override; + + virtual sal_Bool SAL_CALL isValidXMLName( const OUString& sName ) override; + + virtual sal_Bool SAL_CALL isValidPrefixName( const OUString& sName ) override; + + virtual void SAL_CALL setNodeValue( + const css::uno::Reference< ::css::xml::dom::XNode >& xNode, + const OUString& sValue ) override; + + + // XUpdatable + + +public: + virtual void SAL_CALL update() override; + + + // XUnoTunnel + + +public: + virtual sal_Int64 SAL_CALL getSomething( const css::uno::Sequence<sal_Int8>& ) override; + + + // XTypeProvider::getImplementationId + + +public: + virtual css::uno::Sequence<sal_Int8> SAL_CALL getImplementationId() override; + + OUString SAL_CALL getImplementationName() override; + + sal_Bool SAL_CALL supportsService(OUString const & ServiceName) override; + + css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override; +}; + +} // namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/forms/source/xforms/model_helper.hxx b/forms/source/xforms/model_helper.hxx new file mode 100644 index 000000000..d8e98c98e --- /dev/null +++ b/forms/source/xforms/model_helper.hxx @@ -0,0 +1,146 @@ +/* -*- 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 . + */ +#pragma once + + +// some helper definitions that must be available for model.cxx and +// model_ui.cxx + + +#include "namedcollection.hxx" +#include "binding.hxx" +#include "submission.hxx" +#include "unohelper.hxx" + +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/lang/XUnoTunnel.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <comphelper/servicehelper.hxx> + +namespace xforms +{ + class Model; +} + + +// BindingCollection + + +namespace xforms +{ + +class BindingCollection : public NamedCollection<css::uno::Reference<css::beans::XPropertySet> > +{ + Model* mpModel; + +public: + explicit BindingCollection( Model* pModel ) : mpModel( pModel ) {} + + virtual bool isValid( const T& t ) const override + { + return comphelper::getFromUnoTunnel<Binding>( t ) != nullptr; + } + +protected: + virtual void _insert( const T& t ) override + { + auto pBinding = comphelper::getFromUnoTunnel<Binding>( t ); + OSL_ENSURE( pBinding != nullptr, "invalid item?" ); + pBinding->_setModel( css::uno::Reference<css::xforms::XModel>( mpModel ) ); + } + + virtual void _remove( const T& t ) override + { + auto pBinding = comphelper::getFromUnoTunnel<Binding>( t ); + OSL_ENSURE( pBinding != nullptr, "invalid item?" ); + pBinding->_setModel( css::uno::Reference<css::xforms::XModel>() ); + } +}; + +class SubmissionCollection : public NamedCollection<css::uno::Reference<css::beans::XPropertySet> > +{ + Model* mpModel; + +public: + explicit SubmissionCollection( Model* pModel ) : mpModel( pModel ) {} + + virtual bool isValid( const T& t ) const override + { + return comphelper::getFromUnoTunnel<Submission>( t ) != nullptr; + } + +protected: + virtual void _insert( const T& t ) override + { + auto pSubmission = comphelper::getFromUnoTunnel<Submission>( t ); + OSL_ENSURE( pSubmission != nullptr, "invalid item?" ); + pSubmission->setModel( css::uno::Reference<css::xforms::XModel>( mpModel ) ); + } + + virtual void _remove( const T& t ) override + { + auto pSubmission = comphelper::getFromUnoTunnel<Submission>( t ); + OSL_ENSURE( pSubmission != nullptr, "invalid item?" ); + pSubmission->setModel( css::uno::Reference<css::xforms::XModel>( ) ); + } +}; + +class InstanceCollection : public Collection<css::uno::Sequence<css::beans::PropertyValue> > +{ +public: + virtual bool isValid( const T& t ) const override + { + for( const css::beans::PropertyValue& rProp : t ) + { + if (rProp.Name == "Instance" ) + return true; + } + return false; + } +}; + + +// helper functions + + +sal_Int32 lcl_findInstance( const InstanceCollection*, + std::u16string_view ); + + +// get values from Sequence<PropertyValue> describing an Instance +void getInstanceData( + const css::uno::Sequence<css::beans::PropertyValue>&, + OUString* pID, + css::uno::Reference<css::xml::dom::XDocument>*, + OUString* pURL, + bool* pURLOnce ); + +// set values on Sequence<PropertyValue> for an Instance +void setInstanceData( + css::uno::Sequence<css::beans::PropertyValue>&, + const OUString* pID, + const css::uno::Reference<css::xml::dom::XDocument>*, + const OUString* pURL, + const bool* pURLOnce ); + +} // namespace xforms + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/forms/source/xforms/model_ui.cxx b/forms/source/xforms/model_ui.cxx new file mode 100644 index 000000000..616ea8863 --- /dev/null +++ b/forms/source/xforms/model_ui.cxx @@ -0,0 +1,1007 @@ +/* -*- 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 . + */ + + +#include "model.hxx" +#include "model_helper.hxx" +#include "mip.hxx" +#include "evaluationcontext.hxx" +#include "unohelper.hxx" +#include "submission/serialization_app_xml.hxx" +#include "resourcehelper.hxx" +#include "xmlhelper.hxx" +#include "convert.hxx" +#include <strings.hrc> + +#include <rtl/ustring.hxx> +#include <rtl/ustrbuf.hxx> +#include <tools/debug.hxx> +#include <comphelper/processfactory.hxx> + +// UNO classes +#include <com/sun/star/xml/dom/XNode.hpp> +#include <com/sun/star/xml/dom/XDocumentBuilder.hpp> +#include <com/sun/star/xml/dom/XDocumentFragment.hpp> +#include <com/sun/star/xml/dom/XNamedNodeMap.hpp> +#include <com/sun/star/xml/xpath/XXPathObject.hpp> +#include <com/sun/star/xml/xpath/XPathObjectType.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/io/TextInputStream.hpp> +#include <com/sun/star/container/XEnumeration.hpp> +#include <com/sun/star/container/XNameContainer.hpp> +#include <com/sun/star/frame/XModel.hpp> +#include <com/sun/star/xforms/XFormsSupplier.hpp> +#include <com/sun/star/xforms/XDataTypeRepository.hpp> +#include <com/sun/star/xsd/XDataType.hpp> +#include <com/sun/star/xsd/DataTypeClass.hpp> + + +using com::sun::star::beans::PropertyValue; +using com::sun::star::io::TextInputStream; +using com::sun::star::io::XTextInputStream2; +using com::sun::star::container::XEnumeration; +using com::sun::star::container::XNameContainer; +using com::sun::star::xforms::XFormsSupplier; + +using namespace xforms; +using namespace com::sun::star::uno; +using namespace com::sun::star::xml::dom; +using namespace com::sun::star::xml::xpath; + + +// implement XFormsUIHelper1 + + +OUString Model::getDefaultServiceNameForNode( const css::uno::Reference<css::xml::dom::XNode>& xNode ) +{ + // determine service for control. string/text field is default. + OUString sService = "com.sun.star.form.component.TextField"; + + // query repository for suitable type + OSL_ENSURE( mxDataTypes.is(), "no type repository?" ); + OUString sTypeName = queryMIP( xNode ).getTypeName(); + if( mxDataTypes->hasByName( sTypeName ) ) + { + OSL_ENSURE( mxDataTypes->getDataType( sTypeName ).is(), + "has or has not?" ); + + switch( mxDataTypes->getDataType( sTypeName )->getTypeClass() ) + { + case css::xsd::DataTypeClass::BOOLEAN: + sService = "com.sun.star.form.component.CheckBox"; + break; + case css::xsd::DataTypeClass::DOUBLE: + case css::xsd::DataTypeClass::DECIMAL: + case css::xsd::DataTypeClass::FLOAT: + sService = "com.sun.star.form.component.NumericField"; + break; + + case css::xsd::DataTypeClass::STRING: + case css::xsd::DataTypeClass::DURATION: + case css::xsd::DataTypeClass::DATETIME: + case css::xsd::DataTypeClass::TIME: + case css::xsd::DataTypeClass::DATE: + case css::xsd::DataTypeClass::gYearMonth: + case css::xsd::DataTypeClass::gYear: + case css::xsd::DataTypeClass::gMonthDay: + case css::xsd::DataTypeClass::gDay: + case css::xsd::DataTypeClass::gMonth: + case css::xsd::DataTypeClass::hexBinary: + case css::xsd::DataTypeClass::base64Binary: + case css::xsd::DataTypeClass::anyURI: + case css::xsd::DataTypeClass::QName: + case css::xsd::DataTypeClass::NOTATION: + default: + // keep default + break; + } + } + + return sService; +} + + +static void lcl_OutPosition( OUStringBuffer& rBuffer, + const Reference<XNode>& xNode ) +{ + OSL_ENSURE( xNode->getParentNode().is(), "need parent" ); + + // count # of occurrences of this node + sal_Int32 nFound = 0; + sal_Int32 nPosition = -1; + if( xNode->getParentNode().is() ) + { + for( Reference<XNode> xIter = xNode->getParentNode()->getFirstChild(); + xIter != nullptr; + xIter = xIter->getNextSibling() ) + { + if( xIter->getNodeType() == xNode->getNodeType() && + xIter->getNodeName() == xNode->getNodeName() && + xIter->getNamespaceURI() == xNode->getNamespaceURI() ) + { + nFound++; + if( xIter == xNode ) + nPosition = nFound; + } + } + } + OSL_ENSURE( nFound > 0 && nPosition > 0, "node not found???" ); + + // output position (if necessary) + if( nFound > 1 ) + { + rBuffer.insert( 0, ']' ); + rBuffer.insert( 0, nPosition ); + rBuffer.insert( 0, '[' ); + } +} + +static void lcl_OutName( OUStringBuffer& rBuffer, + const Reference<XNode>& xNode ) +{ + rBuffer.insert( 0, xNode->getNodeName() ); + OUString sPrefix = xNode->getPrefix(); + if( !sPrefix.isEmpty() ) + { + rBuffer.insert( 0, ':' ); + rBuffer.insert( 0, sPrefix ); + } +} + +static void lcl_OutInstance( OUStringBuffer& rBuffer, + const Reference<XNode>& xNode, + Model* pModel ) +{ + Reference<XDocument> xDoc = xNode->getOwnerDocument(); + + if( xDoc == pModel->getDefaultInstance() ) + return; + + rBuffer.insert( 0, "')" ); + + // iterate over instances, and find the right one + OUString sInstanceName; + Reference<XEnumeration> xEnum = + pModel->getInstances()->createEnumeration(); + while( sInstanceName.isEmpty() && xEnum->hasMoreElements() ) + { + Sequence<PropertyValue> aValues; + xEnum->nextElement() >>= aValues; + + // get ID and instance + OUString sId; + Reference<XDocument> xInstance; + getInstanceData( aValues, &sId, &xInstance, nullptr, nullptr ); + + // now check whether this was our instance: + if( xInstance == xDoc ) + sInstanceName = sId; + } + + rBuffer.insert( 0, sInstanceName ); + rBuffer.insert( 0, "instance('" ); +} + +OUString Model::getDefaultBindingExpressionForNode( + const XNode_t& xNode, + const EvaluationContext& rContext) +{ + OSL_ENSURE( xNode.is(), "need node" ); + + // iterate upwards and put sections into the expression buffer. + // Stop iteration either at context node (relative expression) or + // at document root, whichever occurs first. + OUStringBuffer aBuffer; + for( Reference<XNode> xCurrent = xNode; + xCurrent.is() && xCurrent != rContext.mxContextNode; + xCurrent = xCurrent->getParentNode() ) + { + // insert a '/' for every step except the first + if( !aBuffer.isEmpty() ) + aBuffer.insert( 0, '/' ); + + switch( xCurrent->getNodeType() ) + { + case NodeType_ELEMENT_NODE: + lcl_OutPosition( aBuffer, xCurrent ); + lcl_OutName( aBuffer, xCurrent ); + break; + + case NodeType_TEXT_NODE: + lcl_OutPosition( aBuffer, xCurrent ); + aBuffer.insert( 0, "text()" ); + break; + + case NodeType_ATTRIBUTE_NODE: + lcl_OutName( aBuffer, xCurrent ); + aBuffer.insert( 0, '@' ); + break; + + case NodeType_DOCUMENT_NODE: + // check for which instance we have + lcl_OutInstance( aBuffer, xCurrent, this ); + break; + + default: + // unknown type? fail! + OSL_FAIL( "unknown node type!" ); + return OUString(); + } + } + + return aBuffer.makeStringAndClear(); +} + + +OUString Model::getDefaultBindingExpressionForNode( const css::uno::Reference<css::xml::dom::XNode>& xNode ) +{ + return getDefaultBindingExpressionForNode( xNode, getEvaluationContext() ); +} + +static bool lcl_isWhitespace( const OUString& rString ) +{ + sal_Int32 nLength = rString.getLength(); + const sal_Unicode* pStr = rString.getStr(); + + bool bWhitespace = true; + for( sal_Int32 i = 0; bWhitespace && ( i < nLength ); i++ ) + { + sal_Unicode c = pStr[i]; + bWhitespace = ( c == u'\x0009' || + c == u'\x000A' || + c == u'\x000D' || + c == u' ' ); + } + return bWhitespace; +} + +OUString Model::getNodeDisplayName( const css::uno::Reference<css::xml::dom::XNode>& xNode, + sal_Bool bDetail ) +{ + OUStringBuffer aBuffer; + + switch( xNode->getNodeType() ) + { + case NodeType_ELEMENT_NODE: + lcl_OutName( aBuffer, xNode ); + break; + + case NodeType_TEXT_NODE: + { + OUString sContent = xNode->getNodeValue(); + if( bDetail || ! lcl_isWhitespace( sContent ) ) + { + aBuffer.append("\"" + Convert::collapseWhitespace( sContent ) + "\""); + } + } + break; + + case NodeType_ATTRIBUTE_NODE: + lcl_OutName( aBuffer, xNode ); + aBuffer.insert( 0, '@' ); + break; + + case NodeType_DOCUMENT_NODE: + if( xNode == getDefaultInstance() ) + aBuffer.append( '/' ); + else + lcl_OutInstance( aBuffer, xNode, this ); + break; + + default: + // unknown type? fail! + OSL_FAIL( "unknown node type!" ); + break; + } + + return aBuffer.makeStringAndClear(); +} + +OUString Model::getNodeName( const css::uno::Reference<css::xml::dom::XNode>& xNode ) +{ + OUStringBuffer aBuffer; + + switch( xNode->getNodeType() ) + { + case NodeType_ELEMENT_NODE: + case NodeType_ATTRIBUTE_NODE: + lcl_OutName( aBuffer, xNode ); + break; + + case NodeType_TEXT_NODE: + case NodeType_DOCUMENT_NODE: + default: + // unknown type? fail! + OSL_FAIL( "no name for this node type!" ); + break; + } + + return aBuffer.makeStringAndClear(); +} + +OUString Model::getBindingName( const css::uno::Reference< ::css::beans::XPropertySet >& xBinding, + sal_Bool /*bDetail*/ ) +{ + OUString sID; + xBinding->getPropertyValue( "BindingID" ) >>= sID; + OUString sExpression; + xBinding->getPropertyValue( "BindingExpression" ) >>= sExpression; + + OUString sRet; + if( !sID.isEmpty() ) + { + sRet = sID + " (" + sExpression + ") "; + } + else + sRet = sExpression; + + return sRet; +} + +OUString Model::getSubmissionName( const css::uno::Reference< ::css::beans::XPropertySet >& xSubmission, + sal_Bool /*bDetail*/ ) +{ + OUString sID; + xSubmission->getPropertyValue( "ID" ) >>= sID; + return sID; +} + +css::uno::Reference< ::css::beans::XPropertySet > Model::cloneBindingAsGhost( const css::uno::Reference< ::css::beans::XPropertySet > &xBinding ) +{ + // Create a new binding instance first... + rtl::Reference<Binding> pBinding = new Binding(); + + // ...and bump up the "deferred notification counter" + // to prevent this binding from contributing to the + // MIPs table... + pBinding->deferNotifications(true); + + // Copy the propertyset and return result... + XPropertySet_t xNewBinding(pBinding); + copy( xBinding, xNewBinding ); + return xNewBinding; +} + +void Model::removeBindingIfUseless( const css::uno::Reference< ::css::beans::XPropertySet >& xBinding ) +{ + Binding* pBinding = comphelper::getFromUnoTunnel<Binding>( xBinding ); + if( pBinding != nullptr ) + { + if( ! pBinding->isUseful() ) + mxBindings->removeItem( pBinding ); + } +} + +css::uno::Reference<css::xml::dom::XDocument> Model::newInstance( const OUString& sName, + const OUString& sURL, + sal_Bool bURLOnce ) +{ + // create a default instance with <instanceData> element + css::uno::Reference<css::xml::dom::XDocument> xInstance = getDocumentBuilder()->newDocument(); + DBG_ASSERT( xInstance.is(), "failed to create DOM instance" ); + + Reference<XNode>( xInstance, UNO_QUERY_THROW )->appendChild( + Reference<XNode>( xInstance->createElement( "instanceData" ), + UNO_QUERY_THROW ) ); + + Sequence<PropertyValue> aSequence; + bool bOnce = bURLOnce; // bool, so we can take address in setInstanceData + setInstanceData( aSequence, &sName, &xInstance, &sURL, &bOnce ); + sal_Int32 nInstance = mxInstances->addItem( aSequence ); + loadInstance( nInstance ); + + return xInstance; +} + +static sal_Int32 lcl_findProp( const PropertyValue* pValues, + sal_Int32 nLength, + std::u16string_view rName ) +{ + bool bFound = false; + sal_Int32 n = 0; + for( ; !bFound && n < nLength; n++ ) + { + bFound = ( pValues[n].Name == rName ); + } + return bFound ? ( n - 1) : -1; +} + +sal_Int32 xforms::lcl_findInstance( const InstanceCollection* pInstances, + std::u16string_view rName ) +{ + sal_Int32 nLength = pInstances->countItems(); + sal_Int32 n = 0; + bool bFound = false; + for( ; !bFound && n < nLength; n++ ) + { + OUString sName; + getInstanceData( pInstances->getItem( n ), &sName, nullptr, nullptr, nullptr ); + bFound = ( sName == rName ); + } + return bFound ? ( n - 1 ) : -1; +} + +void Model::renameInstance( const OUString& sFrom, + const OUString& sTo, + const OUString& sURL, + sal_Bool bURLOnce ) +{ + sal_Int32 nPos = lcl_findInstance( mxInstances.get(), sFrom ); + if( nPos == -1 ) + return; + + Sequence<PropertyValue> aSeq = mxInstances->getItem( nPos ); + PropertyValue* pSeq = aSeq.getArray(); + sal_Int32 nLength = aSeq.getLength(); + + sal_Int32 nProp = lcl_findProp( pSeq, nLength, u"ID" ); + if( nProp == -1 ) + { + // add name property + aSeq.realloc( nLength + 1 ); + pSeq = aSeq.getArray(); + pSeq[ nLength ].Name = "ID"; + nProp = nLength; + } + + // change name + pSeq[ nProp ].Value <<= sTo; + + // change url + nProp = lcl_findProp( pSeq, nLength, u"URL" ); + if(nProp != -1) + pSeq[ nProp ].Value <<= sURL; + + // change urlonce + nProp = lcl_findProp( pSeq, nLength, u"URLOnce" ); + if(nProp != -1) + pSeq[ nProp ].Value <<= bURLOnce; + + // set instance + mxInstances->setItem( nPos, aSeq ); +} + +void Model::removeInstance( const OUString& sName ) +{ + sal_Int32 nPos = lcl_findInstance( mxInstances.get(), sName ); + if( nPos != -1 ) + mxInstances->removeItem( mxInstances->getItem( nPos ) ); +} + +static Reference<XNameContainer> lcl_getModels( + const Reference<css::frame::XModel>& xComponent ) +{ + Reference<XNameContainer> xRet; + Reference<XFormsSupplier> xSupplier( xComponent, UNO_QUERY ); + if( xSupplier.is() ) + { + xRet = xSupplier->getXForms(); + } + return xRet; +} + +css::uno::Reference<css::xforms::XModel> Model::newModel( const Reference<css::frame::XModel>& xCmp, + const OUString& sName ) +{ + css::uno::Reference<css::xforms::XModel> xModel; + Reference<XNameContainer> xModels = lcl_getModels( xCmp ); + if( xModels.is() + && ! xModels->hasByName( sName ) ) + { + rtl::Reference<Model> pModel = new Model(); + xModel.set( pModel ); + + pModel->setID( sName ); + pModel->newInstance( OUString(), OUString(), false ); + pModel->initialize(); + xModels->insertByName( sName, Any( xModel ) ); + } + + return xModel; +} + +void Model::renameModel( const Reference<css::frame::XModel>& xCmp, + const OUString& sFrom, + const OUString& sTo ) +{ + Reference<XNameContainer> xModels = lcl_getModels( xCmp ); + if( xModels.is() + && xModels->hasByName( sFrom ) + && ! xModels->hasByName( sTo ) ) + { + Reference<XModel> xModel( xModels->getByName( sFrom ), UNO_QUERY ); + xModel->setID( sTo ); + xModels->insertByName( sTo, Any( xModel ) ); + xModels->removeByName( sFrom ); + } +} + +void Model::removeModel( const Reference<css::frame::XModel>& xCmp, + const OUString& sName ) +{ + Reference<XNameContainer> xModels = lcl_getModels( xCmp ); + if( xModels.is() + && xModels->hasByName( sName ) ) + { + xModels->removeByName( sName ); + } +} + +css::uno::Reference<css::xml::dom::XNode> Model::createElement( const css::uno::Reference<css::xml::dom::XNode>& xParent, + const OUString& sName ) +{ + Reference<XNode> xNode; + if( xParent.is() + && isValidXMLName( sName ) ) + { + // TODO: implement proper namespace handling + xNode = xParent->getOwnerDocument()->createElement( sName ); + } + return xNode; +} + +css::uno::Reference<css::xml::dom::XNode> Model::createAttribute( const css::uno::Reference<css::xml::dom::XNode>& xParent, + const OUString& sName ) +{ + Reference<XNode> xNode; + Reference<XElement> xElement( xParent, UNO_QUERY ); + if( xParent.is() + && xElement.is() + && isValidXMLName( sName ) ) + { + // handle case where attribute already exists + sal_Int32 nCount = 0; + OUString sUniqueName = sName; + while( xElement->hasAttribute( sUniqueName ) ) + { + nCount++; + sUniqueName = sName + OUString::number( nCount ); + } + + // TODO: implement proper namespace handling + xNode = xParent->getOwnerDocument()->createAttribute( sUniqueName ); + } + return xNode; +} + +css::uno::Reference<css::xml::dom::XNode> Model::renameNode( const css::uno::Reference<css::xml::dom::XNode>& xNode, + const OUString& sName ) +{ + // early out if we don't have to change the name + if( xNode->getNodeName() == sName ) + return xNode; + + // refuse to change name if it's an attribute, and the name is already used + if( xNode->getNodeType() == NodeType_ATTRIBUTE_NODE + && xNode->getParentNode().is() + && Reference<XElement>(xNode->getParentNode(), UNO_QUERY_THROW)->hasAttribute( sName ) ) + return xNode; + + // note old binding expression so we can adjust bindings below + OUString sOldDefaultBindingExpression = + getDefaultBindingExpressionForNode( xNode ); + + Reference<XDocument> xDoc = xNode->getOwnerDocument(); + Reference<XNode> xNew; + if( xNode->getNodeType() == NodeType_ELEMENT_NODE ) + { + Reference<XElement> xElem = xDoc->createElement( sName ); + xNew = xElem; + + // iterate over all attributes and append them to the new element + Reference<XElement> xOldElem( xNode, UNO_QUERY ); + OSL_ENSURE( xNode.is(), "no element?" ); + + Reference<XNamedNodeMap> xMap = xNode->getAttributes(); + sal_Int32 nLength = xMap.is() ? xMap->getLength() : 0; + // looping until nLength is suspicious wrt removeAttributeNode + // presumably shrinking XNamedNodeMap::getLength by 1 + for( sal_Int32 n = 0; n < nLength; n++ ) + { + Reference<XAttr> xAttr( xMap->item(n), UNO_QUERY ); + xElem->setAttributeNode( xOldElem->removeAttributeNode( xAttr ) ); + } + + // iterate over all children and append them to the new element + for( Reference<XNode> xCurrent = xNode->getFirstChild(); + xCurrent.is(); + xCurrent = xNode->getFirstChild() ) + { + xNew->appendChild( xNode->removeChild( xCurrent ) ); + } + + xNode->getParentNode()->replaceChild( xNew, xNode ); + } + else if( xNode->getNodeType() == NodeType_ATTRIBUTE_NODE ) + { + // create new attribute + Reference<XAttr> xAttr = xDoc->createAttribute( sName ); + xAttr->setValue( xNode->getNodeValue() ); + + // replace node + Reference<XNode> xParent = xNode->getParentNode(); + xParent->removeChild( xNode ); + xNew = xParent->appendChild( xAttr ); + } + else + { + OSL_FAIL( "can't rename this node type" ); + } + + // adjust bindings (if necessary): + if( xNew.is() ) + { + // iterate over bindings and replace default expressions + OUString sNewDefaultBindingExpression = + getDefaultBindingExpressionForNode( xNew ); + for( sal_Int32 n = 0; n < mxBindings->countItems(); n++ ) + { + Binding* pBinding = comphelper::getFromUnoTunnel<Binding>( + mxBindings->Collection<XPropertySet_t>::getItem( n ) ); + + if( pBinding->getBindingExpression() + == sOldDefaultBindingExpression ) + pBinding->setBindingExpression( sNewDefaultBindingExpression ); + } + } + + // return node; return old node if renaming failed + return xNew.is() ? xNew : xNode; +} + +css::uno::Reference< ::css::beans::XPropertySet > Model::getBindingForNode( const css::uno::Reference<css::xml::dom::XNode>& xNode, + sal_Bool bCreate ) +{ + OSL_ENSURE( xNode.is(), "no node?" ); + + // We will iterate over all bindings and determine the + // appropriateness of the respective binding for this node. The + // best one will be used. If we don't find any and bCreate is set, + // then we will create a suitable binding. + rtl::Reference<Binding> pBestBinding; + sal_Int32 nBestScore = 0; + + for( sal_Int32 n = 0; n < mxBindings->countItems(); n++ ) + { + Binding* pBinding = comphelper::getFromUnoTunnel<Binding>( + mxBindings->Collection<XPropertySet_t>::getItem( n ) ); + + OSL_ENSURE( pBinding != nullptr, "no binding?" ); + Reference<XNodeList> xNodeList = pBinding->getXNodeList(); + + sal_Int32 nNodes = xNodeList.is() ? xNodeList->getLength() : 0; + if( nNodes > 0 && xNodeList->item( 0 ) == xNode ) + { + // allright, we found a suitable node. Let's determine how + // well it fits. Score: + // - bind to exactly this node is better than whole nodeset + // - simple binding expressions is better than complex ones + sal_Int32 nScore = 0; + if( nNodes == 1 ) + nScore ++; + if( pBinding->isSimpleBindingExpression() ) + nScore ++; + + // if we found a better binding, remember it + if( nScore > nBestScore ) + { + pBestBinding = pBinding; + nBestScore = nScore; + } + } + } + + // create binding, if none was found and bCreate is set + OSL_ENSURE( ( nBestScore == 0 ) == ( pBestBinding == nullptr ), + "score != binding?" ); + if( bCreate && pBestBinding == nullptr ) + { + pBestBinding = new Binding(); + pBestBinding->setBindingExpression( + getDefaultBindingExpressionForNode( xNode ) ); + mxBindings->addItem( pBestBinding ); + } + + return pBestBinding; +} + +void Model::removeBindingForNode( const css::uno::Reference<css::xml::dom::XNode>& ) +{ + // determine whether suitable binding is still used +} + +static OUString lcl_serializeForDisplay( const Reference< XAttr >& _rxAttrNode ) +{ + OUString sResult; + OSL_ENSURE( _rxAttrNode.is(), "lcl_serializeForDisplay( attr ): invalid argument!" ); + if ( _rxAttrNode.is() ) + { + OUString sValue = _rxAttrNode->getValue(); + sal_Unicode nQuote = '"'; + if ( sValue.indexOf( nQuote ) >= 0 ) + nQuote = '\''; + + sResult = _rxAttrNode->getName() + "=" + OUStringChar(nQuote) + sValue + OUStringChar(nQuote) + " "; + } + return sResult; +} + +static OUString lcl_serializeForDisplay( const Reference<XNodeList>& xNodes ) +{ + OUStringBuffer sResult; + + // create document fragment + Reference<XDocument> xDocument( getDocumentBuilder()->newDocument() ); + Reference<XDocumentFragment> xFragment( + xDocument->createDocumentFragment() ); + OSL_ENSURE( xFragment.is(), "xFragment" ); + + sal_Int32 nAttributeNodes = 0; + + // attach nodelist to fragment + sal_Int32 nLength = xNodes->getLength(); + for( sal_Int32 i = 0; i < nLength; i++ ) + { + Reference<XNode> xCurrent = xNodes->item( i ); + + switch ( xCurrent->getNodeType() ) + { + case NodeType_DOCUMENT_NODE: + // special-case documents: use top-level element instead + xCurrent = xCurrent->getFirstChild(); + break; + case NodeType_ATTRIBUTE_NODE: + { + Reference< XAttr > xAttr( xCurrent, UNO_QUERY ); + if ( xAttr.is() ) + { + sResult.append(lcl_serializeForDisplay( xAttr )); + ++nAttributeNodes; + } + } + continue; + + default: + break; + } + + // append node + xFragment->appendChild( xDocument->importNode( xCurrent, true ) ); + } + OSL_ENSURE( ( nAttributeNodes == 0 ) || ( nAttributeNodes == nLength ), + "lcl_serializeForDisplay: mixed attribute and non-attribute nodes?" ); + if ( nAttributeNodes ) + // had only attribute nodes + return sResult.makeStringAndClear(); + + // serialize fragment + CSerializationAppXML aSerialization; + aSerialization.setSource( xFragment ); + aSerialization.serialize(); + + // copy stream into buffer + Reference<XTextInputStream2> xTextInputStream = TextInputStream::create( comphelper::getProcessComponentContext() ); + xTextInputStream->setInputStream( aSerialization.getInputStream() ); + + /* WORK AROUND for problem in serialization: currently, multiple + XML declarations (<?xml...?>) are being written out and we don't + want them. When this is fixed, the code below is nice and + simple. The current code filters out the declarations. + OUString sResult = xTextInputStream->readString( Sequence<sal_Unicode>(), + sal_True ); + */ + + // well, the serialization prepends XML header(s) that we need to + // remove first. + sResult.setLength(0); + while( ! xTextInputStream->isEOF() ) + { + OUString sLine = xTextInputStream->readLine(); + if( !sLine.isEmpty() + && !sLine.startsWith( "<?xml" ) ) + { + sResult.append( sLine ); + sResult.append( '\n' ); + } + } + + return sResult.makeStringAndClear(); +} + +static OUString lcl_serializeForDisplay( const Reference<XXPathObject>& xResult ) +{ + // error handling first + if( ! xResult.is() ) + return getResource( RID_STR_XFORMS_CANT_EVALUATE ); + + // TODO: localize + switch( xResult->getObjectType() ) + { + case XPathObjectType_XPATH_BOOLEAN: + return OUString::boolean(xResult->getBoolean()); + + case XPathObjectType_XPATH_STRING: + return "\"" + xResult->getString() + "\""; + + case XPathObjectType_XPATH_NODESET: + return lcl_serializeForDisplay( xResult->getNodeList() ); + + case XPathObjectType_XPATH_NUMBER: + return OUString::number(xResult->getDouble()); + + case XPathObjectType_XPATH_UNDEFINED: + case XPathObjectType_XPATH_POINT: + case XPathObjectType_XPATH_RANGE: + case XPathObjectType_XPATH_LOCATIONSET: + case XPathObjectType_XPATH_USERS: + case XPathObjectType_XPATH_XSLT_TREE: + default: + // TODO: localized error message? + return OUString(); + } +} + +OUString Model::getResultForExpression( + const css::uno::Reference< ::css::beans::XPropertySet >& xBinding, + sal_Bool bIsBindingExpression, + const OUString& sExpression ) +{ + Binding* pBinding = comphelper::getFromUnoTunnel<Binding>( xBinding ); + if( pBinding == nullptr ) + throw RuntimeException(); + + // prepare & evaluate expression + OUStringBuffer aBuffer; + ComputedExpression aExpression; + aExpression.setExpression( sExpression ); + if( bIsBindingExpression ) + { + // binding: use binding context and evaluation + aExpression.evaluate( pBinding->getEvaluationContext() ); + aBuffer.append( lcl_serializeForDisplay( aExpression.getXPath() ) ); + } + else + { + // MIP (not binding): iterate over bindings contexts + std::vector<EvaluationContext> aContext = + pBinding->getMIPEvaluationContexts(); + for (auto const& elem : aContext) + { + aExpression.evaluate(elem); + aBuffer.append( lcl_serializeForDisplay(aExpression.getXPath()) ); + aBuffer.append( '\n' ); + } + } + return aBuffer.makeStringAndClear(); +} + +sal_Bool Model::isValidXMLName( const OUString& sName ) +{ + return isValidQName( sName, nullptr ); +} + +sal_Bool Model::isValidPrefixName( const OUString& sName ) +{ + return ::isValidPrefixName( sName, nullptr ); +} + +void Model::setNodeValue( + const css::uno::Reference< ::css::xml::dom::XNode >& xNode, + const OUString& sValue ) +{ + setSimpleContent( xNode, sValue ); +} + + +// helper functions from model_helper.hxx + + +void xforms::getInstanceData( + const Sequence<PropertyValue>& aValues, + OUString* pID, + Reference<XDocument>* pInstance, + OUString* pURL, + bool* pURLOnce ) +{ + sal_Int32 nValues = aValues.getLength(); + const PropertyValue* pValues = aValues.getConstArray(); + for( sal_Int32 n = 0; n < nValues; n++ ) + { + const PropertyValue& rValue = pValues[n]; + if( pID != nullptr && rValue.Name == "ID") + rValue.Value >>= *pID; + if( pInstance != nullptr && rValue.Name == "Instance") + rValue.Value >>= *pInstance; + if( pURL != nullptr && rValue.Name == "URL") + rValue.Value >>= *pURL; + if( pURLOnce != nullptr && rValue.Name == "URLOnce") + rValue.Value >>= *pURLOnce; + } +} + +void xforms::setInstanceData( + Sequence<PropertyValue>& aSequence, + const OUString* _pID, + const Reference<XDocument>* _pInstance, + const OUString* _pURL, + const bool* _pURLOnce ) +{ + // get old instance data + OUString sID; + Reference<XDocument> xInstance; + OUString sURL; + bool bURLOnce = false; + getInstanceData( aSequence, &sID, &xInstance, &sURL, &bURLOnce ); + const OUString* pID = !sID.isEmpty() ? &sID : nullptr; + const Reference<XDocument>* pInstance = xInstance.is() ? &xInstance : nullptr; + const OUString* pURL = !sURL.isEmpty() ? &sURL : nullptr; + const bool* pURLOnce = ( bURLOnce && pURL != nullptr ) ? &bURLOnce : nullptr; + + // determine new instance data + if (_pID != nullptr) + pID = _pID; + if (_pInstance != nullptr) + pInstance = _pInstance; + if (_pURL != nullptr) + pURL = _pURL; + if (_pURLOnce != nullptr) + pURLOnce = _pURLOnce; + + // count # of values we want to set + sal_Int32 nCount = 0; + if (pID != nullptr) + ++nCount; + if (pInstance != nullptr) + ++nCount; + if (pURL != nullptr) + ++nCount; + if (pURLOnce != nullptr) + ++nCount; + + // realloc sequence and enter values; + aSequence.realloc( nCount ); + PropertyValue* pSequence = aSequence.getArray(); + sal_Int32 nIndex = 0; + if(pID != nullptr) + { + pSequence[ nIndex ].Name = "ID"; + pSequence[ nIndex ].Value <<= *pID; + nIndex++; + } + if(pInstance != nullptr) + { + pSequence[ nIndex ].Name = "Instance"; + pSequence[ nIndex ].Value <<= *pInstance; + nIndex++; + } + if(pURL != nullptr) + { + pSequence[ nIndex ].Name = "URL"; + pSequence[ nIndex ].Value <<= *pURL; + nIndex++; + } + if(pURLOnce != nullptr) + { + pSequence[ nIndex ].Name = "URLOnce"; + pSequence[ nIndex ].Value <<= *pURLOnce; + nIndex++; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/forms/source/xforms/namedcollection.hxx b/forms/source/xforms/namedcollection.hxx new file mode 100644 index 000000000..4cbd99eb4 --- /dev/null +++ b/forms/source/xforms/namedcollection.hxx @@ -0,0 +1,111 @@ +/* -*- 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 . + */ + +#pragma once + +#include "collection.hxx" +#include <cppuhelper/implbase.hxx> +#include <comphelper/sequence.hxx> +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/container/XNamed.hpp> + +#include <algorithm> + +template<class T> +class NamedCollection : public cppu::ImplInheritanceHelper< + Collection<T>, + css::container::XNameAccess> +{ + using Collection<T>::maItems; + using Collection<T>::getItem; + using Collection<T>::hasItem; + +public: + + const T& getItem( const OUString& rName ) const + { + OSL_ENSURE( hasItem( rName ), "invalid name" ); + return *findItem( rName ); + } + + bool hasItem( const OUString& rName ) const + { + return findItem( rName ) != maItems.end(); + } + + css::uno::Sequence<OUString> getNames() const + { + // iterate over members, and collect all those that have names + std::vector<OUString> aNames; + for( const T& rItem : maItems ) + { + css::uno::Reference<css::container::XNamed> + xNamed( rItem, css::uno::UNO_QUERY ); + if( xNamed.is() ) + aNames.push_back( xNamed->getName() ); + } + + return comphelper::containerToSequence(aNames); + } + +protected: + typename std::vector<T>::const_iterator findItem( const OUString& rName ) const + { + return std::find_if(maItems.begin(), maItems.end(), [&rName](const T& rItem) { + css::uno::Reference<css::container::XNamed> + xNamed( rItem, css::uno::UNO_QUERY ); + return xNamed.is() && xNamed->getName() == rName; + }); + } + +public: + + // XElementAccess + virtual css::uno::Type SAL_CALL getElementType() override + { + return Collection<T>::getElementType(); + } + + virtual sal_Bool SAL_CALL hasElements() override + { + return Collection<T>::hasElements(); + } + + // XNameAccess : XElementAccess + virtual css::uno::Any SAL_CALL getByName( + const OUString& aName ) override + { + if( !hasItem( aName ) ) + throw css::container::NoSuchElementException(); + return css::uno::Any( getItem( aName ) ); + } + + virtual css::uno::Sequence<OUString> SAL_CALL getElementNames() override + { + return getNames(); + } + + virtual sal_Bool SAL_CALL hasByName( + const OUString& aName ) override + { + return hasItem( aName ); + } +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/forms/source/xforms/pathexpression.cxx b/forms/source/xforms/pathexpression.cxx new file mode 100644 index 000000000..c26d13d5a --- /dev/null +++ b/forms/source/xforms/pathexpression.cxx @@ -0,0 +1,113 @@ +/* -*- 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 . + */ + + +#include "pathexpression.hxx" +#include "unohelper.hxx" +#include "evaluationcontext.hxx" + +#include <com/sun/star/xml/dom/XNode.hpp> +#include <com/sun/star/xml/dom/XNodeList.hpp> +#include <com/sun/star/xml/xpath/XXPathObject.hpp> +#include <osl/diagnose.h> + +#include <algorithm> + + +using com::sun::star::uno::Reference; +using com::sun::star::xml::dom::XNode; +using com::sun::star::xml::dom::XNodeList; + + +namespace xforms +{ + +PathExpression::PathExpression() +{ +} + +PathExpression::~PathExpression() +{ +} + + +void PathExpression::setExpression( const OUString& rExpression ) +{ + // set new expression, and clear pre-computed results + ComputedExpression::setExpression( rExpression ); + + // check expression against regular expression to determine + // whether it contains only 'simple' (i.e. static) conditions. For + // now, we check whether it only contains number positions. + // (TODO: Only works for names containing only ASCII letters+digits.) + mbIsSimple = + _checkExpression( "( */@?[a-zA-Z0-9:]+( *\\[ *[0-9 ]+ *\\] *)?)+" ); + + maNodes.clear(); +} + +OUString PathExpression::_getExpressionForEvaluation() const +{ + OUString sExpr = ComputedExpression::_getExpressionForEvaluation(); + if( sExpr.isEmpty()) + sExpr = "."; + return sExpr; +} + +void PathExpression::evaluate( const EvaluationContext& rContext ) +{ + // for simple expression we don't need to re-bind (if we were bound before) + // (we will evaluate empty expressions, since they are interpreted as ".") + if( mxResult.is() && isSimpleExpression() ) + return; + + _evaluate( rContext, _getExpressionForEvaluation() ); + + // clear old result, and copy new + maNodes.clear(); + if( mxResult.is() ) + { + // copy node list + Reference<XNodeList> xNodeList = mxResult->getNodeList(); + OSL_ENSURE( xNodeList.is(), "empty object (instead of empty list)" ); + sal_Int32 nLength = xNodeList.is() ? xNodeList->getLength() : 0; + for( sal_Int32 n = 0; n < nLength; n++ ) + maNodes.push_back( xNodeList->item( n ) ); + } +} + + +Reference<XNode> PathExpression::getNode() const +{ + Reference<XNode> xResult; + if( ! maNodes.empty() ) + xResult = *maNodes.begin(); + return xResult; +} + + +Reference<XNodeList> PathExpression::getXNodeList() const +{ + return mxResult.is() ? mxResult->getNodeList() : Reference<XNodeList>(); +} + + +} // namespace xforms + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/forms/source/xforms/pathexpression.hxx b/forms/source/xforms/pathexpression.hxx new file mode 100644 index 000000000..319230160 --- /dev/null +++ b/forms/source/xforms/pathexpression.hxx @@ -0,0 +1,71 @@ +/* -*- 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 . + */ + +#pragma once + +#include "computedexpression.hxx" + +#include <vector> + +// forward declaractions +namespace com::sun::star::xml::dom +{ +class XNodeList; +namespace events +{ +class XEventListener; +} +} + +namespace xforms +{ +/** PathExpression represents an XPath Expression and caches results */ +class PathExpression final : public ComputedExpression +{ +public: + typedef std::vector<css::uno::Reference<css::xml::dom::XNode>> NodeVector_t; + +private: + /// the node-list result from the last bind (cached from mxResult) + NodeVector_t maNodes; + + /// get expression for evaluation + OUString _getExpressionForEvaluation() const; + +public: + PathExpression(); + ~PathExpression(); + + /// set the expression string + /// (overridden to do remove old listeners) + /// (also defines simple expressions) + void setExpression(const OUString& rExpression); + + /// evaluate the expression relative to the content node. + void evaluate(const xforms::EvaluationContext& rContext); + + // get the result of this expression as node/node list/... + css::uno::Reference<css::xml::dom::XNode> getNode() const; + const NodeVector_t& getNodeList() const { return maNodes; } + css::uno::Reference<css::xml::dom::XNodeList> getXNodeList() const; +}; + +} // namespace xforms + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/forms/source/xforms/propertysetbase.cxx b/forms/source/xforms/propertysetbase.cxx new file mode 100644 index 000000000..0b0787c49 --- /dev/null +++ b/forms/source/xforms/propertysetbase.cxx @@ -0,0 +1,158 @@ +/* -*- 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 . + */ + + +#include "propertysetbase.hxx" + +#include <com/sun/star/beans/XMultiPropertySet.hpp> +#include <com/sun/star/uno/Reference.hxx> + +using com::sun::star::uno::Any; +using com::sun::star::uno::Reference; +using com::sun::star::uno::Exception; +using com::sun::star::lang::IllegalArgumentException; +using com::sun::star::beans::Property; +using com::sun::star::beans::XPropertySetInfo; + +PropertyAccessorBase::~PropertyAccessorBase() +{ +} + +PropertySetBase::PropertySetBase( ) +{ +} + +PropertySetBase::~PropertySetBase( ) +{ +} + +cppu::IPropertyArrayHelper& SAL_CALL PropertySetBase::getInfoHelper() +{ + if ( !m_pProperties ) + { + OSL_ENSURE( !m_aProperties.empty(), "PropertySetBase::getInfoHelper: no registered properties!" ); + m_pProperties.reset(new cppu::OPropertyArrayHelper( m_aProperties.data(), m_aProperties.size(), false )); + } + return *m_pProperties; +} + +Reference< XPropertySetInfo > SAL_CALL PropertySetBase::getPropertySetInfo( ) +{ + return cppu::OPropertySetHelper::createPropertySetInfo( getInfoHelper() ); +} + +void PropertySetBase::registerProperty( const Property& rProperty, + const ::rtl::Reference< PropertyAccessorBase >& rAccessor ) +{ + OSL_ENSURE(rAccessor, + "PropertySetBase::registerProperty: invalid property accessor, this will crash!"); + m_aAccessors.emplace( rProperty.Handle, rAccessor ); + + OSL_ENSURE( rAccessor->isWriteable() + == ( ( rProperty.Attributes & css::beans::PropertyAttribute::READONLY ) == 0 ), + "PropertySetBase::registerProperty: inconsistence!" ); + + m_aProperties.push_back( rProperty ); +} + +void PropertySetBase::notifyAndCachePropertyValue( sal_Int32 nHandle ) +{ + ::osl::ClearableMutexGuard aGuard( GetMutex() ); + + PropertyValueCache::iterator aPos = m_aCache.find( nHandle ); + if ( aPos == m_aCache.end() ) + { // method has never before been invoked for this property + try + { + // determine the type of this property + ::cppu::IPropertyArrayHelper& rPropertyMetaData = getInfoHelper(); + OUString sPropName; + OSL_VERIFY( rPropertyMetaData.fillPropertyMembersByHandle( &sPropName, nullptr, nHandle ) ); + Property aProperty = rPropertyMetaData.getPropertyByName( sPropName ); + // default construct a value of this type + Any aEmptyValue( nullptr, aProperty.Type ); + // insert into the cache + aPos = m_aCache.emplace( nHandle, aEmptyValue ).first; + } + catch( const Exception& ) + { + OSL_FAIL( "PropertySetBase::notifyAndCachePropertyValue: this is not expected to fail!" ); + } + } + Any aOldValue = aPos->second; + // determine the current value + Any aNewValue; + getFastPropertyValue( aNewValue, nHandle ); + // remember the old value + aPos->second = aNewValue; + + aGuard.clear(); + if ( aNewValue != aOldValue ) + firePropertyChange( nHandle, aNewValue, aOldValue ); +} + +void PropertySetBase::initializePropertyValueCache( sal_Int32 nHandle ) +{ + Any aCurrentValue; + getFastPropertyValue( aCurrentValue, nHandle ); + + ::std::pair< PropertyValueCache::iterator, bool > aInsertResult = + m_aCache.emplace( nHandle, aCurrentValue ); + OSL_ENSURE( aInsertResult.second, "PropertySetBase::initializePropertyValueCache: already cached a value for this property!" ); +} + +PropertyAccessorBase& PropertySetBase::locatePropertyHandler( sal_Int32 nHandle ) const +{ + PropertyAccessors::const_iterator aPropertyPos = m_aAccessors.find( nHandle ); + OSL_ENSURE( aPropertyPos != m_aAccessors.end() && aPropertyPos->second, + "PropertySetBase::locatePropertyHandler: accessor map is corrupted!" ); + // neither should this be called for handles where there is no accessor, nor should a + // NULL accessor be in the map + return *aPropertyPos->second; +} + +sal_Bool SAL_CALL PropertySetBase::convertFastPropertyValue( Any& rConvertedValue, Any& rOldValue, sal_Int32 nHandle, + const Any& rValue ) +{ + PropertyAccessorBase& rAccessor = locatePropertyHandler( nHandle ); + if ( !rAccessor.approveValue( rValue ) ) + throw IllegalArgumentException( OUString(), *this, 0 ); + + rAccessor.getValue( rOldValue ); + if ( rOldValue != rValue ) + { + rConvertedValue = rValue; // no conversion at all + return true; + } + return false; +} + +void SAL_CALL PropertySetBase::setFastPropertyValue_NoBroadcast( sal_Int32 nHandle, const Any& rValue ) +{ + PropertyAccessorBase& rAccessor = locatePropertyHandler( nHandle ); + rAccessor.setValue( rValue ); +} + +void SAL_CALL PropertySetBase::getFastPropertyValue( Any& rValue, sal_Int32 nHandle ) const +{ + PropertyAccessorBase& rAccessor = locatePropertyHandler( nHandle ); + rAccessor.getValue( rValue ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/forms/source/xforms/propertysetbase.hxx b/forms/source/xforms/propertysetbase.hxx new file mode 100644 index 000000000..d6ae97405 --- /dev/null +++ b/forms/source/xforms/propertysetbase.hxx @@ -0,0 +1,340 @@ +/* -*- 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 . + */ +#pragma once + +#include <comphelper/propstate.hxx> +#include <rtl/ref.hxx> +#include <salhelper/simplereferenceobject.hxx> + +// include for inlined helper function below +#include <com/sun/star/beans/PropertyAttribute.hpp> + +#include <map> +#include <memory> + +namespace com::sun::star::uno { + class Any; + class RuntimeException; + template<class T> class Sequence; +} + +/** base class which encapsulates accessing (reading/writing) concrete property values +*/ +class PropertyAccessorBase : public salhelper::SimpleReferenceObject +{ +protected: + PropertyAccessorBase() { } + virtual ~PropertyAccessorBase() override; + +public: + + virtual bool approveValue( const css::uno::Any& rValue ) const = 0; + virtual void setValue( const css::uno::Any& rValue ) = 0; + virtual void getValue( css::uno::Any& rValue ) const = 0; + virtual bool isWriteable() const = 0; +}; + + +/** helper class for implementing property accessors through public member functions +*/ +template< typename CLASS, typename VALUE, class WRITER, class READER > +class GenericPropertyAccessor : public PropertyAccessorBase +{ +public: + typedef WRITER Writer; + typedef READER Reader; + +private: + CLASS* m_pInstance; + Writer m_pWriter; + Reader m_pReader; + +public: + GenericPropertyAccessor( CLASS* pInstance, Writer pWriter, Reader pReader ) + :m_pInstance( pInstance ) + ,m_pWriter( pWriter ) + ,m_pReader( pReader ) + { + } + + virtual bool approveValue( const css::uno::Any& rValue ) const override + { + VALUE aVal; + return ( rValue >>= aVal ); + } + + virtual void setValue( const css::uno::Any& rValue ) override + { + VALUE aTypedVal = VALUE(); + OSL_VERIFY( rValue >>= aTypedVal ); + (m_pInstance->*m_pWriter)( aTypedVal ); + } + + virtual void getValue( css::uno::Any& rValue ) const override + { + rValue = css::uno::Any( (m_pInstance->*m_pReader)() ); + } + + virtual bool isWriteable() const override + { + return m_pWriter != nullptr; + } +}; + +/** helper class for implementing property accessors via non-UNO methods +*/ +template< typename CLASS, typename VALUE > +class DirectPropertyAccessor + :public GenericPropertyAccessor < CLASS + , VALUE + , void (CLASS::*)( const VALUE& ) + , VALUE (CLASS::*)() const + > +{ +protected: + typedef void (CLASS::*Writer)( const VALUE& ); + typedef VALUE (CLASS::*Reader)() const; +public: + DirectPropertyAccessor( CLASS* pInstance, Writer pWriter, Reader pReader ) + :GenericPropertyAccessor< CLASS, VALUE, Writer, Reader >( pInstance, pWriter, pReader ) + { + } +}; + +/** helper class for implementing non-UNO accessors to a boolean property +*/ +template< typename CLASS > +class BooleanPropertyAccessor + :public GenericPropertyAccessor < CLASS + , bool + , void (CLASS::*)( bool ) + , bool (CLASS::*)() const + > +{ +protected: + typedef void (CLASS::*Writer)( bool ); + typedef bool (CLASS::*Reader)() const; +public: + BooleanPropertyAccessor( CLASS* pInstance, Writer pWriter, Reader pReader ) + :GenericPropertyAccessor< CLASS, bool, Writer, Reader >( pInstance, pWriter, pReader ) + { + } +}; + +/** helper class for implementing property accessors via UNO methods +*/ +template< typename CLASS, typename VALUE > +class APIPropertyAccessor + :public GenericPropertyAccessor < CLASS + , VALUE + , void (SAL_CALL CLASS::*)( const VALUE& ) + , VALUE (SAL_CALL CLASS::*)() + > +{ +protected: + typedef void (SAL_CALL CLASS::*Writer)( const VALUE& ); + typedef VALUE (SAL_CALL CLASS::*Reader)(); +public: + APIPropertyAccessor( CLASS* pInstance, Writer pWriter, Reader pReader ) + :GenericPropertyAccessor< CLASS, VALUE, Writer, Reader >( pInstance, pWriter, pReader ) + { + } +}; + +/** bridges two XPropertySet helper implementations + + The <type scope="comphelper">OStatefulPropertySet</type> (basically, the + <type scope="cppu">OPropertySetHelper</type>) implements a comprehensive framework + for property sets, including property change notifications. + However, it lacks some easy possibilities to declare the supported properties. + Other helper structs and classes allow for this, but are lacking needed features + such as property change notifications. + + The <type>PropertySetBase</type> bridges various implementations, + so you have the best of both worlds. + */ +class PropertySetBase : public ::comphelper::OStatefulPropertySet +{ +private: + typedef ::std::map< const sal_Int32, ::rtl::Reference< PropertyAccessorBase > > PropertyAccessors; + typedef ::std::vector< css::beans::Property > PropertyArray; + typedef ::std::map< const sal_Int32, css::uno::Any > PropertyValueCache; + + PropertyArray m_aProperties; + std::unique_ptr<cppu::IPropertyArrayHelper> m_pProperties; + PropertyAccessors m_aAccessors; + PropertyValueCache m_aCache; + +protected: + PropertySetBase(); + virtual ~PropertySetBase() override; + + /** registers a new property to be supported by this instance + @param rProperty + the descriptor for the to-be-supported property + @param rAccessor + an instance which is able to provide read and possibly write access to + the property. + @precond + Must not be called after any of the property set related UNO interfaces + has been used. Usually, you will do a number of <member>registerProperty</member> + calls in the constructor of your class. + */ + void registerProperty( + const css::beans::Property& rProperty, + const ::rtl::Reference< PropertyAccessorBase >& rAccessor + ); + + /** notifies a change in a given property value, if necessary + + The necessity of the notification is determined by a cached value for the given + property. Caching happens after notification. + + That is, when you call <member>notifyAndCachePropertyValue</member> for the first time, + a value for the given property is default constructed, and considered to be the "old value". + If this value differs from the current value, then this change is notified to all interested + listeners. Finally, the current value is remembered. + + Subsequent calls to <member>notifyAndCachePropertyValue</member> use the remembered value as + "old value", and from then on behave as the first call. + + @param nHandle + the handle of the property. Must denote a property supported by this instance, i.e. + one previously registered via <member>registerProperty</member>. + + @precond + our ref count must not be 0. The reason is that during this method's execution, + the instance might be acquired and released, which would immediately destroy + the instance if it has a ref count of 0. + + @seealso initializePropertyValueCache + */ + void notifyAndCachePropertyValue( sal_Int32 nHandle ); + + /** initializes the property value cache for the given property, with its current value + + Usually used to initialize the cache with values which are different from default + constructed values. Say you have a boolean property whose initial state + is <TRUE/>. Say you call <member>notifyAndCachePropertyValue</member> the first time: it will + default construct the "old value" for this property as <FALSE/>, and thus <b>not</b> do + any notifications if the "current value" is also <FALSE/> - which might be wrong, since + the guessing of the "old value" differed from the real initial value which was <TRUE/>. + + Too confusing? Okay, then just call this method for every property you have. + + @param nHandle + the handle of the property. Must denote a property supported by this instance, i.e. + one previously registered via <member>registerProperty</member>. + @param rValue + the value to cache + @seealso notifyAndCachePropertyValue + */ + void initializePropertyValueCache( sal_Int32 nHandle ); + + /// OPropertysetHelper methods + virtual sal_Bool SAL_CALL convertFastPropertyValue( css::uno::Any& rConvertedValue, css::uno::Any& rOldValue, sal_Int32 nHandle, const css::uno::Any& rValue ) override; + virtual void SAL_CALL setFastPropertyValue_NoBroadcast( sal_Int32 nHandle, const css::uno::Any& rValue ) override; + virtual void SAL_CALL getFastPropertyValue( css::uno::Any& rValue, sal_Int32 nHandle ) const override; + + virtual cppu::IPropertyArrayHelper& SAL_CALL getInfoHelper() override; + virtual css::uno::Reference< css::beans::XPropertySetInfo > SAL_CALL getPropertySetInfo( ) override; + +public: + /// helper struct for granting selective access to some notification-related methods + struct NotifierAccess { friend struct PropertyChangeNotifier; private: NotifierAccess() { } }; + /** retrieves the current property value for the given handle + @param nHandle + the handle of the property. Must denote a property supported by this instance, i.e. + one previously registered via <member>registerProperty</member>. + @see registerProperty + */ + void getCurrentPropertyValueByHandle( sal_Int32 nHandle, css::uno::Any& /* [out] */ rValue, const NotifierAccess& ) const + { + getFastPropertyValue( rValue, nHandle ); + } + + /** notifies a change in a given property to all interested listeners + */ + void notifyPropertyChange( sal_Int32 nHandle, const css::uno::Any& rOldValue, const css::uno::Any& rNewValue, const NotifierAccess& ) const + { + const_cast< PropertySetBase* >( this )->firePropertyChange( nHandle, rNewValue, rOldValue ); + } + + using ::comphelper::OStatefulPropertySet::getFastPropertyValue; + +private: + /** locates a property given by handle + @param nHandle + the handle of the property. Must denote a property supported by this instance, i.e. + one previously registered via <member>registerProperty</member>. + @see registerProperty + */ + PropertyAccessorBase& locatePropertyHandler( sal_Int32 nHandle ) const; +}; + +/** a helper class for notifying property changes in a <type>PropertySetBase</type> instance. + + You can create an instance of this class on the stack of a method which is to programmatically + change the value of a property. In its constructor, the instance will acquire the current property + value, and in its destructor, it will notify the change of this property's value (if necessary). + + You do not need this class if you are modifying property values by using the X(Fast|Multi)PropertSet + methods, since those already care for property notifications. You only need it if you're changing + the internal representation of your property directly. + + Also note that usually, notifications in the UNO world should be done without a locked mutex. So + if you use this class in conjunction with a <type>MutexGuard</type>, ensure that you <b>first</b> + instantiate the <type>PropertyChangeNotifier</type>, and <b>then</b> the <type>MutexGuard</type>, + so your mutex is released before the notification happens. +*/ +struct PropertyChangeNotifier +{ +private: + const PropertySetBase& m_rPropertySet; + sal_Int32 m_nHandle; + css::uno::Any m_aOldValue; + +public: + /** constructs a PropertyChangeNotifier + @param rPropertySet + the property set implementation whose property is going to be changed. Note + that this property set implementation must live at least as long as the + PropertyChangeNotifier instance does. + @param nHandle + the handle of the property which is going to be changed. Must be a valid property + handle for the given <arg>rPropertySet</arg> + */ + PropertyChangeNotifier( const PropertySetBase& rPropertySet, sal_Int32 nHandle ) + :m_rPropertySet( rPropertySet ) + ,m_nHandle( nHandle ) + { + m_rPropertySet.getCurrentPropertyValueByHandle( m_nHandle, m_aOldValue, PropertySetBase::NotifierAccess() ); + } + ~PropertyChangeNotifier() + { + css::uno::Any aNewValue; + m_rPropertySet.getCurrentPropertyValueByHandle( m_nHandle, aNewValue, PropertySetBase::NotifierAccess() ); + if ( aNewValue != m_aOldValue ) + { + m_rPropertySet.notifyPropertyChange( m_nHandle, m_aOldValue, aNewValue, PropertySetBase::NotifierAccess() ); + } + } +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/forms/source/xforms/resourcehelper.cxx b/forms/source/xforms/resourcehelper.cxx new file mode 100644 index 000000000..011d806f5 --- /dev/null +++ b/forms/source/xforms/resourcehelper.cxx @@ -0,0 +1,65 @@ +/* -*- 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 . + */ + + +#include "resourcehelper.hxx" +#include <frm_resource.hxx> + +#include <osl/diagnose.h> +#include <rtl/ustring.hxx> + + +namespace xforms +{ + +OUString getResource(TranslateId pResourceId) +{ + return getResource(pResourceId, {}, {}, {}); +} + +OUString getResource(TranslateId pResourceId, + std::u16string_view rInfo1) +{ + return getResource(pResourceId, rInfo1, {}, {}); +} + +OUString getResource(TranslateId pResourceId, + std::u16string_view rInfo1, + std::u16string_view rInfo2) +{ + return getResource(pResourceId, rInfo1, rInfo2, {}); +} + +OUString getResource(TranslateId pResourceId, + std::u16string_view rInfo1, + std::u16string_view rInfo2, + std::u16string_view rInfo3) +{ + OUString sResource = frm::ResourceManager::loadString(pResourceId); + OSL_ENSURE( !sResource.isEmpty(), "resource not found?" ); + + OUString sString = sResource.replaceAll( "$1", rInfo1 ); + sString = sString.replaceAll( "$2", rInfo2 ); + sString = sString.replaceAll( "$3", rInfo3 ); + return sString; +} + +} // namespace xforms + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/forms/source/xforms/resourcehelper.hxx b/forms/source/xforms/resourcehelper.hxx new file mode 100644 index 000000000..1453a7e4c --- /dev/null +++ b/forms/source/xforms/resourcehelper.hxx @@ -0,0 +1,41 @@ +/* -*- 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 . + */ +#pragma once + +#include <sal/config.h> + +#include <rtl/ustring.hxx> +#include <unotools/resmgr.hxx> + +namespace xforms +{ + /// get a resource string for the current language + OUString getResource(TranslateId); + + // overloaded: get a resource string, and substitute parameters + OUString getResource(TranslateId, std::u16string_view); + OUString getResource(TranslateId, std::u16string_view, + std::u16string_view); + OUString getResource(TranslateId, std::u16string_view, + std::u16string_view, + std::u16string_view); + +} // namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/forms/source/xforms/submission.cxx b/forms/source/xforms/submission.cxx new file mode 100644 index 000000000..d8d81abd5 --- /dev/null +++ b/forms/source/xforms/submission.cxx @@ -0,0 +1,609 @@ +/* -*- 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 . + */ + +#include "submission.hxx" + +#include "model.hxx" +#include "binding.hxx" +#include "mip.hxx" +#include "evaluationcontext.hxx" +#include "submission/submission_put.hxx" +#include "submission/submission_post.hxx" +#include "submission/submission_get.hxx" + +#include <rtl/ustring.hxx> +#include <com/sun/star/lang/NoSupportException.hpp> +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/xforms/XModel.hpp> +#include <com/sun/star/uno/RuntimeException.hpp> +#include <com/sun/star/xml/dom/XNodeList.hpp> +#include <com/sun/star/xml/dom/XDocument.hpp> +#include <com/sun/star/xml/dom/DocumentBuilder.hpp> +#include <com/sun/star/xml/dom/XDocumentFragment.hpp> +#include <com/sun/star/xml/dom/NodeType.hpp> +#include <com/sun/star/task/XInteractionHandler.hpp> +#include <com/sun/star/task/XInteractionRequest.hpp> +#include <com/sun/star/task/XInteractionContinuation.hpp> +#include <com/sun/star/xforms/InvalidDataOnSubmitException.hpp> +#include <com/sun/star/frame/XFrame.hpp> +#include <cppuhelper/exc_hlp.hxx> +#include <comphelper/interaction.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/servicehelper.hxx> +#include <memory> +#include <string_view> + +using com::sun::star::util::VetoException; +using com::sun::star::form::submission::XSubmissionVetoListener; +using com::sun::star::lang::WrappedTargetException; +using com::sun::star::lang::NoSupportException; +using com::sun::star::task::XInteractionHandler; +using com::sun::star::task::XInteractionRequest; +using com::sun::star::task::XInteractionContinuation; +using com::sun::star::xforms::XModel; +using com::sun::star::xforms::InvalidDataOnSubmitException; +using com::sun::star::xml::xpath::XXPathObject; +using com::sun::star::frame::XFrame; +using xforms::Submission; +using xforms::Model; +using xforms::MIP; + +using namespace com::sun::star::uno; +using namespace com::sun::star::lang; +using namespace com::sun::star::xml::dom; + +Submission::Submission() : + mbIndent(), + mbOmitXmlDeclaration(), + mbStandalone(), + msReplace( "none" ) +{ + initializePropertySet(); +} + +Submission::~Submission() noexcept +{ +} + + +void Submission::setModel( const Reference<XModel>& xModel ) +{ + mxModel = xModel; +} + + +void Submission::setID( const OUString& sID ) +{ + msID = sID; +} + + +void Submission::setBind( const OUString& sBind ) +{ + msBind = sBind; +} + +OUString Submission::getRef() const +{ + return maRef.getExpression(); +} + +void Submission::setRef( const OUString& sRef ) +{ + maRef.setExpression( sRef ); +} + + +void Submission::setAction( const OUString& sAction ) +{ + msAction = sAction; +} + + +void Submission::setMethod( const OUString& sMethod ) +{ + msMethod = sMethod; +} + + +void Submission::setVersion( const OUString& sVersion ) +{ + msVersion = sVersion; +} + + +void Submission::setIndent( bool bIndent ) +{ + mbIndent = bIndent; +} + + +void Submission::setMediaType( const OUString& sMediaType ) +{ + msMediaType = sMediaType; +} + + +void Submission::setEncoding( const OUString& sEncoding ) +{ + msEncoding = sEncoding; +} + + +void Submission::setOmitXmlDeclaration( bool bOmitXmlDeclaration ) +{ + mbOmitXmlDeclaration = bOmitXmlDeclaration; +} + + +void Submission::setStandalone( bool bStandalone ) +{ + mbStandalone = bStandalone; +} + + +void Submission::setCDataSectionElement( const OUString& sCDataSectionElement ) +{ + msCDataSectionElement = sCDataSectionElement; +} + + +void Submission::setReplace( const OUString& sReplace ) +{ + msReplace = sReplace; +} + + +void Submission::setSeparator( const OUString& sSeparator ) +{ + msSeparator = sSeparator; +} + + +void Submission::setIncludeNamespacePrefixes( const Sequence< OUString >& rIncludeNamespacePrefixes ) +{ + msIncludeNamespacePrefixes = rIncludeNamespacePrefixes; +} + +bool Submission::doSubmit( const Reference< XInteractionHandler >& xHandler ) +{ + liveCheck(); + + // construct XXPathObject for submission doc; use bind in preference of ref + EvaluationContext aEvalContext; + ComputedExpression aExpression; + if( !msBind.isEmpty() ) + { + Binding* pBinding = comphelper::getFromUnoTunnel<Binding>( mxModel->getBinding(msBind) ); + if( pBinding != nullptr ) + { + aExpression.setExpression( pBinding->getBindingExpression() ); + aEvalContext = pBinding->getEvaluationContext(); + } + // TODO: else: illegal binding name -> raise error + } + else if( !maRef.getExpression().isEmpty() ) + { + aExpression.setExpression( maRef.getExpression() ); + aEvalContext = comphelper::getFromUnoTunnel<Model>( mxModel )->getEvaluationContext(); + } + else + { + aExpression.setExpression( "/" ); + aEvalContext = comphelper::getFromUnoTunnel<Model>( mxModel )->getEvaluationContext(); + } + aExpression.evaluate( aEvalContext ); + Reference<XXPathObject> xResult = aExpression.getXPath(); + OSL_ENSURE( xResult.is(), "no result?" ); + + // early out if we have not obtained any result + if( ! xResult.is() ) + return false; + + + // Reference< XNodeList > aList = xResult->getNodeList(); + OUString aMethod = getMethod(); + + // strip whitespace-only text node for get submission + Reference< XDocumentFragment > aFragment = createSubmissionDocument( + xResult, aMethod.equalsIgnoreAsciiCase("get")); + + // submit result; set encoding, etc. + std::unique_ptr<CSubmission> xSubmission; + if (aMethod.equalsIgnoreAsciiCase("PUT")) + xSubmission.reset(new CSubmissionPut( getAction(), aFragment)); + else if (aMethod.equalsIgnoreAsciiCase("post")) + xSubmission.reset(new CSubmissionPost( getAction(), aFragment)); + else if (aMethod.equalsIgnoreAsciiCase("get")) + xSubmission.reset(new CSubmissionGet( getAction(), aFragment)); + else + { + OSL_FAIL("Unsupported xforms submission method"); + return false; + } + + if (!xSubmission->IsWebProtocol()) + return false; + + CSubmission::SubmissionResult aResult = xSubmission->submit( xHandler ); + + if (aResult == CSubmission::SUCCESS) + { + Reference< XDocument > aInstanceDoc = getInstanceDocument(xResult); + aResult = xSubmission->replace(getReplace(), aInstanceDoc, Reference< XFrame >()); + } + + return ( aResult == CSubmission::SUCCESS ); +} + +Sequence<sal_Int8> Submission::getUnoTunnelId() +{ + static const comphelper::UnoIdInit aImplementationId; + return aImplementationId.getSeq(); +} + + +void Submission::liveCheck() +{ + bool bValid = mxModel.is(); + + if( ! bValid ) + throw RuntimeException(); +} + +Model* Submission::getModelImpl() const +{ + Model* pModel = nullptr; + if( mxModel.is() ) + pModel = comphelper::getFromUnoTunnel<Model>( mxModel ); + return pModel; +} + + +// Property-Set implementation + + +#define HANDLE_ID 0 +#define HANDLE_Bind 1 +#define HANDLE_Ref 2 +#define HANDLE_Action 3 +#define HANDLE_Method 4 +#define HANDLE_Version 5 +#define HANDLE_Indent 6 +#define HANDLE_MediaType 7 +#define HANDLE_Encoding 8 +#define HANDLE_OmitXmlDeclaration 9 +#define HANDLE_Standalone 10 +#define HANDLE_CDataSectionElement 11 +#define HANDLE_Replace 12 +#define HANDLE_Separator 13 +#define HANDLE_IncludeNamespacePrefixes 14 +#define HANDLE_Model 15 + +void Submission::initializePropertySet() +{ + registerProperty( css::beans::Property("ID", HANDLE_ID, cppu::UnoType<OUString>::get(), css::beans::PropertyAttribute::BOUND ), + new DirectPropertyAccessor< Submission, OUString >(this, &Submission::setID, &Submission::getID) ); + + registerProperty( css::beans::Property("Bind", HANDLE_Bind, cppu::UnoType<OUString>::get(), css::beans::PropertyAttribute::BOUND ), + new DirectPropertyAccessor< Submission, OUString >(this, &Submission::setBind, &Submission::getBind) ); + + registerProperty( css::beans::Property("Ref", HANDLE_Ref, cppu::UnoType<OUString>::get(), css::beans::PropertyAttribute::BOUND ), + new DirectPropertyAccessor< Submission, OUString >(this, &Submission::setRef, &Submission::getRef) ); + + registerProperty( css::beans::Property("Action", HANDLE_Action, cppu::UnoType<OUString>::get(), css::beans::PropertyAttribute::BOUND ), + new DirectPropertyAccessor< Submission, OUString >(this, &Submission::setAction, &Submission::getAction) ); + + registerProperty( css::beans::Property("Method", HANDLE_Method, cppu::UnoType<OUString>::get(), css::beans::PropertyAttribute::BOUND ), + new DirectPropertyAccessor< Submission, OUString >(this, &Submission::setMethod, &Submission::getMethod) ); + + registerProperty( css::beans::Property("Version", HANDLE_Version, cppu::UnoType<OUString>::get(), css::beans::PropertyAttribute::BOUND ), + new DirectPropertyAccessor< Submission, OUString >(this, &Submission::setVersion, &Submission::getVersion) ); + + registerProperty( css::beans::Property("Indent", HANDLE_Indent, cppu::UnoType<bool>::get(), css::beans::PropertyAttribute::BOUND ), + new BooleanPropertyAccessor< Submission >(this, &Submission::setIndent, &Submission::getIndent)); + + registerProperty( css::beans::Property("MediaType", HANDLE_MediaType, cppu::UnoType<OUString>::get(), css::beans::PropertyAttribute::BOUND ), + new DirectPropertyAccessor< Submission, OUString >(this, &Submission::setMediaType, &Submission::getMediaType) ); + + registerProperty( css::beans::Property("Encoding", HANDLE_Encoding, cppu::UnoType<OUString>::get(), css::beans::PropertyAttribute::BOUND ), + new DirectPropertyAccessor< Submission, OUString >(this, &Submission::setEncoding, &Submission::getEncoding) ); + + registerProperty( css::beans::Property("OmitXmlDeclaration", HANDLE_OmitXmlDeclaration, cppu::UnoType<bool>::get(), css::beans::PropertyAttribute::BOUND ), + new BooleanPropertyAccessor< Submission >(this, &Submission::setOmitXmlDeclaration, &Submission::getOmitXmlDeclaration)); + + registerProperty( css::beans::Property("Standalone", HANDLE_Standalone, cppu::UnoType<bool>::get(), css::beans::PropertyAttribute::BOUND ), + new BooleanPropertyAccessor< Submission >(this, &Submission::setStandalone, &Submission::getStandalone)); + + registerProperty( css::beans::Property("CDataSectionElement", HANDLE_CDataSectionElement, cppu::UnoType<OUString>::get(), css::beans::PropertyAttribute::BOUND ), + new DirectPropertyAccessor< Submission, OUString >(this, &Submission::setCDataSectionElement, &Submission::getCDataSectionElement) ); + + registerProperty( css::beans::Property("Replace", HANDLE_Replace, cppu::UnoType<OUString>::get(), css::beans::PropertyAttribute::BOUND ), + new DirectPropertyAccessor< Submission, OUString >(this, &Submission::setReplace, &Submission::getReplace) ); + + registerProperty( css::beans::Property("Separator", HANDLE_Separator, cppu::UnoType<OUString>::get(), css::beans::PropertyAttribute::BOUND ), + new DirectPropertyAccessor< Submission, OUString >(this, &Submission::setSeparator, &Submission::getSeparator) ); + + registerProperty( css::beans::Property("IncludeNamespacePrefixes", HANDLE_IncludeNamespacePrefixes, cppu::UnoType<Sequence<OUString>>::get(), css::beans::PropertyAttribute::BOUND ), + new DirectPropertyAccessor< Submission, Sequence<OUString> >(this, &Submission::setIncludeNamespacePrefixes, &Submission::getIncludeNamespacePrefixes) ); + + registerProperty( css::beans::Property("Model", HANDLE_Model, cppu::UnoType<Reference<XModel>>::get(), css::beans::PropertyAttribute::BOUND ), + new DirectPropertyAccessor< Submission, Reference<XModel> >(this, &Submission::setModel, &Submission::getModel) ); + + initializePropertyValueCache( HANDLE_Indent ); + initializePropertyValueCache( HANDLE_OmitXmlDeclaration ); + initializePropertyValueCache( HANDLE_Standalone ); +} + +sal_Bool SAL_CALL Submission::convertFastPropertyValue( + Any& rConvertedValue, Any& rOldValue, sal_Int32 nHandle, const Any& rValue ) +{ + if ( nHandle == HANDLE_IncludeNamespacePrefixes ) + { + // for convenience reasons (????), we accept a string which contains + // a comma-separated list of namespace prefixes + OUString sTokenList; + if ( rValue >>= sTokenList ) + { + std::vector< OUString > aPrefixes; + sal_Int32 p = 0; + while ( p >= 0 ) + aPrefixes.push_back( sTokenList.getToken( 0, ',', p ) ); + + Sequence< OUString > aConvertedPrefixes( aPrefixes.data(), aPrefixes.size() ); + return PropertySetBase::convertFastPropertyValue( rConvertedValue, rOldValue, nHandle, Any( aConvertedPrefixes ) ); + } + } + + return PropertySetBase::convertFastPropertyValue( rConvertedValue, rOldValue, nHandle, rValue ); +} + +OUString SAL_CALL Submission::getName() +{ + return getID(); +} + +void SAL_CALL Submission::setName( const OUString& sID ) +{ + setID( sID ); +} + + +sal_Int64 SAL_CALL Submission::getSomething( + const Sequence<sal_Int8>& aId ) +{ + return comphelper::getSomethingImpl(aId, this); +} + + +static OUString lcl_message( std::u16string_view rID, std::u16string_view rText ) +{ + OUString aMessage = OUString::Concat("XForms submission '") + rID + "' failed" + rText + "."; + return aMessage; +} + +void SAL_CALL Submission::submitWithInteraction( + const Reference<XInteractionHandler>& _rxHandler ) +{ + // as long as this class is not really threadsafe, we need to copy + // the members we're interested in + Reference< XModel > xModel( mxModel ); + OUString sID( msID ); + + if ( !xModel.is() || msID.isEmpty() ) + throw RuntimeException( + "This is not a valid submission object.", + *this + ); + + Model* pModel = comphelper::getFromUnoTunnel<Model>( xModel ); + OSL_ENSURE( pModel != nullptr, "illegal model?" ); + + // #i36765# #i47248# warning on submission of illegal data + // check for validity (and query user if invalid) + bool bValid = pModel->isValid(); + if( ! bValid ) + { + InvalidDataOnSubmitException aInvalidDataException( + lcl_message(sID, u" due to invalid data" ), *this ); + + if( _rxHandler.is() ) + { + // laboriously create interaction request + rtl::Reference<comphelper::OInteractionRequest> pRequest + = new comphelper::OInteractionRequest( + Any( aInvalidDataException ) ); + + rtl::Reference<comphelper::OInteractionApprove> pContinue + = new comphelper::OInteractionApprove(); + pRequest->addContinuation( pContinue ); + + rtl::Reference<comphelper::OInteractionDisapprove> pCancel + = new comphelper::OInteractionDisapprove(); + pRequest->addContinuation( pCancel ); + + // ask the handler... + _rxHandler->handle( pRequest ); + OSL_ENSURE( pContinue->wasSelected() || pCancel->wasSelected(), + "handler didn't select" ); + + // and continue, if user chose 'continue' + if( pContinue->wasSelected() ) + bValid = true; + } + + // abort if invalid (and user didn't tell us to continue) + if( ! bValid ) + throw aInvalidDataException; + } + + // attempt submission + bool bResult = false; + try + { + bResult = doSubmit( _rxHandler ); + } + catch( const VetoException& ) + { + OSL_FAIL( "Model::submit: Hmm. How can a single submission have a veto right?" ); + // allowed to leave + throw; + } + catch( const Exception& ) + { + css::uno::Any anyEx = cppu::getCaughtException(); + // exception caught: re-throw as wrapped target exception + throw WrappedTargetException( + lcl_message( sID, u" due to exception being thrown" ), + *this, anyEx ); + } + + if( !bResult ) + { + // other failure: throw wrapped target exception, too. + throw WrappedTargetException( + lcl_message( sID, std::u16string_view() ), *this, Any() ); + } + mxModel->rebuild(); +} + +void SAL_CALL Submission::submit( ) +{ + submitWithInteraction( nullptr ); +} + +void SAL_CALL Submission::addSubmissionVetoListener( const Reference< XSubmissionVetoListener >& /*listener*/ ) +{ + // TODO + throw NoSupportException(); +} + +void SAL_CALL Submission::removeSubmissionVetoListener( const Reference< XSubmissionVetoListener >& /*listener*/ ) +{ + // TODO + throw NoSupportException(); +} + +static bool isIgnorable(const Reference< XNode >& aNode) +{ + // ignore whitespace-only textnodes + if (aNode->getNodeType() == NodeType_TEXT_NODE) + { + OUString aTrimmedValue = aNode->getNodeValue().trim(); + if (aTrimmedValue.isEmpty()) return true; + } + + return false; +} + +// recursively copy relevant nodes from A to B +static void cloneNodes(Model& aModel, const Reference< XNode >& dstParent, const Reference< XNode >& source, bool bRemoveWSNodes) +{ + if (!source.is()) return; + + Reference< XNode > cur = source; + Reference< XDocument > dstDoc = dstParent->getOwnerDocument(); + Reference< XNode > imported; + + if (!cur.is()) + return; + + // is this node relevant? + MIP mip = aModel.queryMIP(cur); + if(mip.isRelevant() && !(bRemoveWSNodes && isIgnorable(cur))) + { + imported = dstDoc->importNode(cur, false); + imported = dstParent->appendChild(imported); + // append source children to new imported parent + for( cur = cur->getFirstChild(); cur.is(); cur = cur->getNextSibling() ) + cloneNodes(aModel, imported, cur, bRemoveWSNodes); + } +} +Reference< XDocument > Submission::getInstanceDocument(const Reference< XXPathObject >& aObj) +{ + using namespace css::xml::xpath; + // result + Reference< XDocument > aDocument; + + if (aObj->getObjectType() == XPathObjectType_XPATH_NODESET) + { + Reference< XNodeList > aList = aObj->getNodeList(); + if (aList->getLength() > 0) + aDocument = aList->item(0)->getOwnerDocument(); + } + return aDocument; +} + +Reference< XDocumentFragment > Submission::createSubmissionDocument(const Reference< XXPathObject >& aObj, bool bRemoveWSNodes) +{ + using namespace css::xml::xpath; + Reference< XDocumentBuilder > aDocBuilder = DocumentBuilder::create(comphelper::getProcessComponentContext()); + Reference< XDocument > aDocument = aDocBuilder->newDocument(); + Reference< XDocumentFragment > aFragment = aDocument->createDocumentFragment(); + + + if (aObj->getObjectType() == XPathObjectType_XPATH_NODESET) + { + Reference< XNodeList > aList = aObj->getNodeList(); + Reference< XNode > aListItem; + for (sal_Int32 i=0; i < aList->getLength(); i++) + { + aListItem = aList->item(i); + if (aListItem->getNodeType()==NodeType_DOCUMENT_NODE) + aListItem = (Reference< XDocument >(aListItem, UNO_QUERY))->getDocumentElement(); + // copy relevant nodes from instance into fragment + cloneNodes(*getModelImpl(), aFragment, aListItem, bRemoveWSNodes); + } + } + return aFragment; +} + +// some forwarding: XPropertySet is implemented in our base class, +// but also available as base of XSubmission +Reference< css::beans::XPropertySetInfo > SAL_CALL Submission::getPropertySetInfo( ) +{ + return PropertySetBase::getPropertySetInfo(); +} +void SAL_CALL Submission::setPropertyValue( const OUString& aPropertyName, const Any& aValue ) +{ + PropertySetBase::setPropertyValue( aPropertyName, aValue ); +} +Any SAL_CALL Submission::getPropertyValue( const OUString& PropertyName ) +{ + return PropertySetBase::getPropertyValue( PropertyName ); +} +void SAL_CALL Submission::addPropertyChangeListener( const OUString& aPropertyName, const Reference< css::beans::XPropertyChangeListener >& xListener ) +{ + PropertySetBase::addPropertyChangeListener( aPropertyName, xListener ); +} +void SAL_CALL Submission::removePropertyChangeListener( const OUString& aPropertyName, const Reference< css::beans::XPropertyChangeListener >& aListener ) +{ + PropertySetBase::removePropertyChangeListener( aPropertyName, aListener ); +} +void SAL_CALL Submission::addVetoableChangeListener( const OUString& PropertyName, const Reference< css::beans::XVetoableChangeListener >& aListener ) +{ + PropertySetBase::addVetoableChangeListener( PropertyName, aListener ); +} +void SAL_CALL Submission::removeVetoableChangeListener( const OUString& PropertyName, const Reference< css::beans::XVetoableChangeListener >& aListener ) +{ + PropertySetBase::removeVetoableChangeListener( PropertyName, aListener ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/forms/source/xforms/submission.hxx b/forms/source/xforms/submission.hxx new file mode 100644 index 000000000..1e7f17e4c --- /dev/null +++ b/forms/source/xforms/submission.hxx @@ -0,0 +1,232 @@ +/* -*- 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 . + */ + +#pragma once + +#include <cppuhelper/implbase.hxx> +#include "propertysetbase.hxx" +#include <com/sun/star/lang/XUnoTunnel.hpp> +#include <com/sun/star/xml/dom/XDocument.hpp> +#include <com/sun/star/xml/dom/XDocumentFragment.hpp> +#include <com/sun/star/xml/xpath/XXPathObject.hpp> +#include <com/sun/star/xforms/XSubmission.hpp> + +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/uno/Sequence.hxx> +#include "computedexpression.hxx" + +// forward declaractions +namespace com::sun::star { + namespace xforms { class XModel; } + namespace uno { class Any; } + namespace beans { class UnknownPropertyException; + class PropertyVetoException; } + namespace lang { class IllegalArgumentException; + class WrappedTargetException; } +} +namespace xforms { class Model; } + + +namespace xforms +{ + +/** An XForms submission. + * + * See http://www.w3.org/TR/xforms/ for more information. + */ +typedef cppu::ImplInheritanceHelper< + PropertySetBase, + css::lang::XUnoTunnel, + css::xforms::XSubmission +> Submission_t; + +class Submission : public Submission_t +{ + // submission properties + OUString msID; + OUString msBind; + ComputedExpression maRef; + OUString msAction; + OUString msMethod; + OUString msVersion; + bool mbIndent; + OUString msMediaType; + OUString msEncoding; + bool mbOmitXmlDeclaration; + bool mbStandalone; + OUString msCDataSectionElement; + OUString msReplace; + OUString msSeparator; + css::uno::Sequence< OUString > msIncludeNamespacePrefixes; + +private: + + /// the Model to which this Submission belongs; may be NULL + css::uno::Reference<css::xforms::XModel> mxModel; + + // this will extract the document from the model that will be submitted + css::uno::Reference< css::xml::dom::XDocumentFragment > + createSubmissionDocument(const css::uno::Reference< css::xml::xpath::XXPathObject >& aObject, + bool bRemoveWSNodes); + static css::uno::Reference< css::xml::dom::XDocument > + getInstanceDocument(const css::uno::Reference< css::xml::xpath::XXPathObject >& aObject); + +public: + Submission(); + virtual ~Submission() noexcept override; + + + // property methods: get/set value + + + /// get XForms model + css::uno::Reference<css::xforms::XModel> + getModel() const { return mxModel;} + + /// set XForms model + void setModel( + const css::uno::Reference<css::xforms::XModel>& ); + + OUString getID() const { return msID;} /// get ID for this submission + void setID( const OUString& ); /// set ID for this submission + + OUString getBind() const { return msBind;} + void setBind( const OUString& ); + + OUString getRef() const; + void setRef( const OUString& ); + + OUString getAction() const { return msAction;} + void setAction( const OUString& ); + + OUString getMethod() const { return msMethod;} + void setMethod( const OUString& ); + + OUString getVersion() const { return msVersion;} + void setVersion( const OUString& ); + + bool getIndent() const { return mbIndent;} + void setIndent( bool ); + + OUString getMediaType() const { return msMediaType;} + void setMediaType( const OUString& ); + + OUString getEncoding() const { return msEncoding;} + void setEncoding( const OUString& ); + + bool getOmitXmlDeclaration() const { return mbOmitXmlDeclaration;} + void setOmitXmlDeclaration( bool ); + + bool getStandalone() const { return mbStandalone;} + void setStandalone( bool ); + + OUString getCDataSectionElement() const { return msCDataSectionElement;} + void setCDataSectionElement( const OUString& ); + + OUString getReplace() const { return msReplace;} + void setReplace( const OUString& ); + + OUString getSeparator() const { return msSeparator;} + void setSeparator( const OUString& ); + + css::uno::Sequence< OUString > getIncludeNamespacePrefixes() const { return msIncludeNamespacePrefixes;} + void setIncludeNamespacePrefixes( const css::uno::Sequence< OUString >& ); + + /** perform the submission + * @returns if submission was successful */ + bool doSubmit( const css::uno::Reference< css::task::XInteractionHandler >& aHandler ); + + // helpers for UNO tunnel + static css::uno::Sequence<sal_Int8> getUnoTunnelId(); + +private: + + /// check whether object is live, and throw suitable exception if not + /// (to be used be API methods before acting on the object) + /// + /// @throws css::uno::RuntimeException + void liveCheck(); + + /// get the model implementation + xforms::Model* getModelImpl() const; + +protected: + + + // XPropertySet & friends: + // implement abstract methods from PropertySetHelper + + + virtual sal_Bool SAL_CALL convertFastPropertyValue( + css::uno::Any& rConvertedValue, + css::uno::Any& rOldValue, + sal_Int32 nHandle, + const css::uno::Any& rValue ) override; + +private: + void initializePropertySet(); + + +public: + + + // XNamed: + // get/set name + + + virtual OUString SAL_CALL getName() override; + + virtual void SAL_CALL setName( const OUString& ) override; + + + // XUnoTunnel + + + virtual sal_Int64 SAL_CALL getSomething( + const css::uno::Sequence<sal_Int8>& ) override; + + + // XSubmission + + + virtual void SAL_CALL submit( ) override; + virtual void SAL_CALL submitWithInteraction( + const css::uno::Reference< css::task::XInteractionHandler >& aHandler ) override; + virtual void SAL_CALL addSubmissionVetoListener( + const css::uno::Reference< css::form::submission::XSubmissionVetoListener >& listener ) override; + virtual void SAL_CALL removeSubmissionVetoListener( + const css::uno::Reference< css::form::submission::XSubmissionVetoListener >& listener ) override; + + + // XPropertySet + // (need to disambiguate this) + + virtual css::uno::Reference< css::beans::XPropertySetInfo > SAL_CALL getPropertySetInfo( ) override; + virtual void SAL_CALL setPropertyValue( const OUString& aPropertyName, const css::uno::Any& aValue ) override; + virtual css::uno::Any SAL_CALL getPropertyValue( const OUString& PropertyName ) override; + virtual void SAL_CALL addPropertyChangeListener( const OUString& aPropertyName, const css::uno::Reference< css::beans::XPropertyChangeListener >& xListener ) override; + virtual void SAL_CALL removePropertyChangeListener( const OUString& aPropertyName, const css::uno::Reference< css::beans::XPropertyChangeListener >& aListener ) override; + virtual void SAL_CALL addVetoableChangeListener( const OUString& PropertyName, const css::uno::Reference< css::beans::XVetoableChangeListener >& aListener ) override; + virtual void SAL_CALL removeVetoableChangeListener( const OUString& PropertyName, const css::uno::Reference< css::beans::XVetoableChangeListener >& aListener ) override; +}; + + +} // namespace xforms + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/forms/source/xforms/submission/replace.cxx b/forms/source/xforms/submission/replace.cxx new file mode 100644 index 000000000..e6154ef61 --- /dev/null +++ b/forms/source/xforms/submission/replace.cxx @@ -0,0 +1,131 @@ +/* -*- 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 . + */ + +#include <memory> +#include "submission.hxx" +#include "serialization_app_xml.hxx" + +#include <rtl/ustring.hxx> +#include <rtl/ref.hxx> +#include <tools/diagnose_ex.h> +#include <o3tl/string_view.hxx> + +#include <comphelper/processfactory.hxx> +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/xml/dom/XDocument.hpp> +#include <com/sun/star/xml/dom/DocumentBuilder.hpp> +#include <com/sun/star/frame/Desktop.hpp> +#include <com/sun/star/frame/XComponentLoader.hpp> +#include <com/sun/star/frame/FrameSearchFlag.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/task/InteractionHandler.hpp> + +using namespace com::sun::star::uno; +using namespace com::sun::star::ucb; +using namespace com::sun::star::frame; +using namespace com::sun::star::lang; +using namespace com::sun::star::beans; +using namespace com::sun::star::task; +using namespace com::sun::star::xml::dom; + +CSubmission::SubmissionResult CSubmission::replace(std::u16string_view aReplace, const Reference<XDocument>& aDocument, const Reference<XFrame>& aFrame) +{ + if (!m_aResultStream.is()) + return CSubmission::UNKNOWN_ERROR; + + try { + Reference< XComponentContext > xContext = comphelper::getProcessComponentContext(); + if (o3tl::equalsIgnoreAsciiCase(aReplace, u"all") + || o3tl::equalsIgnoreAsciiCase(aReplace, u"document")) { + Reference< XComponentLoader > xLoader; + if (aFrame.is()) + xLoader.set(aFrame, UNO_QUERY); + + if (!xLoader.is()) + xLoader.set( Desktop::create(xContext), UNO_QUERY_THROW); + + // open the stream from the result... + // build media descriptor + Sequence< PropertyValue > descriptor{ + PropertyValue("InputStream", + -1, Any(m_aResultStream), PropertyState_DIRECT_VALUE), + PropertyValue("ReadOnly", + -1, Any(true), PropertyState_DIRECT_VALUE) + }; + + OUString aURL = m_aURLObj.GetMainURL(INetURLObject::DecodeMechanism::NONE); + xLoader->loadComponentFromURL(aURL, "_default", FrameSearchFlag::ALL, descriptor); + + return CSubmission::SUCCESS; + + } else if (o3tl::equalsIgnoreAsciiCase(aReplace, u"instance")) { + if (aDocument.is()) { + // parse the result stream into a new document + Reference< XDocumentBuilder > xBuilder(DocumentBuilder::create(xContext)); + Reference< XDocument > aNewDocument = xBuilder->parse(m_aResultStream); + + if (aNewDocument.is()) { + // and replace the content of the current instance + Reference< XElement > oldRoot = aDocument->getDocumentElement(); + Reference< XElement > newRoot = aNewDocument->getDocumentElement(); + + Reference< XNode > aImportedNode = aDocument->importNode(newRoot, true); + aDocument->replaceChild(aImportedNode, oldRoot); + return CSubmission::SUCCESS; + } else { + return CSubmission::UNKNOWN_ERROR; + } + } else { + // nothing to replace + return CSubmission::UNKNOWN_ERROR; + } + } else if (o3tl::equalsIgnoreAsciiCase(aReplace, u"none")) { + // do nothing \o/ + return CSubmission::SUCCESS; + } + } catch (const Exception&) { + TOOLS_WARN_EXCEPTION( "forms.xforms", "Exception during replace"); + } + return CSubmission::UNKNOWN_ERROR; +} + +::std::unique_ptr< CSerialization > CSubmission::createSerialization(const Reference< XInteractionHandler >& _xHandler,Reference<XCommandEnvironment>& _rOutEnv) +{ + // PUT always uses application/xml + ::std::unique_ptr< CSerialization > apSerialization(new CSerializationAppXML()); + apSerialization->setSource(m_aFragment); + apSerialization->serialize(); + + // create a commandEnvironment and use the default interaction handler + rtl::Reference<CCommandEnvironmentHelper> pHelper = new CCommandEnvironmentHelper; + if( _xHandler.is() ) + pHelper->m_aInteractionHandler = _xHandler; + else + pHelper->m_aInteractionHandler.set( + InteractionHandler::createWithParent(m_xContext, nullptr), UNO_QUERY_THROW); + + rtl::Reference<CProgressHandlerHelper> pProgressHelper = new CProgressHandlerHelper; + pHelper->m_aProgressHandler.set(pProgressHelper); + + // UCB has ownership of environment... + _rOutEnv = pHelper; + return apSerialization; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/forms/source/xforms/submission/serialization.hxx b/forms/source/xforms/submission/serialization.hxx new file mode 100644 index 000000000..19f072b0b --- /dev/null +++ b/forms/source/xforms/submission/serialization.hxx @@ -0,0 +1,60 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/io/XInputStream.hpp> +#include <com/sun/star/xml/dom/XDocumentFragment.hpp> + +/** +Serialize an XObject +*/ + +class CSerialization +{ +protected: + css::uno::Reference<css::xml::dom::XDocumentFragment> m_aFragment; + +public: + virtual ~CSerialization() {} + + /** + sets the XObject that is to serialized + */ + void setSource(const css::uno::Reference<css::xml::dom::XDocumentFragment>& aFragment) + { + m_aFragment = aFragment; + } + + /** + start the serialization process + */ + virtual void serialize() = 0; + + /** + get the serialized bytes. + reads up to buffer->getLength() bytes and returns the number of + bytes read. + returns -1 on error + */ + virtual css::uno::Reference<css::io::XInputStream> getInputStream() = 0; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/forms/source/xforms/submission/serialization_app_xml.cxx b/forms/source/xforms/submission/serialization_app_xml.cxx new file mode 100644 index 000000000..61729d027 --- /dev/null +++ b/forms/source/xforms/submission/serialization_app_xml.cxx @@ -0,0 +1,126 @@ +/* -*- 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 . + */ + + +#include "serialization_app_xml.hxx" + +#include <com/sun/star/io/Pipe.hpp> +#include <com/sun/star/xml/dom/DocumentBuilder.hpp> +#include <com/sun/star/xml/dom/XNode.hpp> +#include <com/sun/star/xml/dom/XDocument.hpp> +#include <com/sun/star/xml/dom/NodeType.hpp> +#include <com/sun/star/xml/sax/XSAXSerializable.hpp> +#include <com/sun/star/xml/sax/Writer.hpp> +#include <com/sun/star/beans/StringPair.hpp> +#include <com/sun/star/xml/dom/XDocumentBuilder.hpp> + +#include <tools/diagnose_ex.h> +#include <comphelper/processfactory.hxx> + +using ::com::sun::star::uno::Reference; +using ::com::sun::star::uno::Exception; +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::UNO_SET_THROW; +using ::com::sun::star::xml::dom::DocumentBuilder; +using ::com::sun::star::xml::dom::XNode; +using ::com::sun::star::xml::dom::XDocument; +using ::com::sun::star::xml::sax::XSAXSerializable; +using ::com::sun::star::beans::StringPair; +using ::com::sun::star::xml::dom::NodeType_DOCUMENT_NODE; +using ::com::sun::star::xml::dom::NodeType_ELEMENT_NODE; +using ::com::sun::star::xml::dom::XDocumentBuilder; +using ::com::sun::star::xml::sax::Writer; +using ::com::sun::star::xml::sax::XDocumentHandler; + +CSerializationAppXML::CSerializationAppXML() + : m_xBuffer(css::io::Pipe::create(comphelper::getProcessComponentContext())) +{ +} + +Reference< css::io::XInputStream > +CSerializationAppXML::getInputStream() +{ + // The pipes output is provided through it's + // XOutputStream interface aspect + return m_xBuffer; +} + +void +CSerializationAppXML::serialize_node(const Reference< XNode >& rNode) +{ + try + { + Reference< XSAXSerializable > xSerializer( rNode, UNO_QUERY ); + if ( !xSerializer.is() ) + { + // ensure we have a "real" node + Reference< XNode > xNode = rNode; + if ( xNode->getNodeType() == NodeType_DOCUMENT_NODE ) + { + Reference< XDocument > const xDoc( xNode, UNO_QUERY_THROW ); + xNode.set( xDoc->getDocumentElement(), UNO_QUERY_THROW ); + } + ENSURE_OR_RETURN_VOID( xNode->getNodeType() == NodeType_ELEMENT_NODE, + "CSerializationAppXML::serialize_node: invalid node type!" ); + + // create a new document + Reference< XDocumentBuilder > const xDocBuilder = DocumentBuilder::create( comphelper::getProcessComponentContext() ); + Reference< XDocument > const xDocument( xDocBuilder->newDocument(), UNO_SET_THROW ); + + // copy the to-be-serialized node + Reference< XNode > const xImportedNode( xDocument->importNode( xNode, true ), UNO_SET_THROW ); + xDocument->appendChild( xImportedNode ); + + // ask the doc for the serializer + xSerializer.set( xDocument, UNO_QUERY ); + } + + ENSURE_OR_RETURN_VOID( xSerializer.is(), + "CSerializationAppXML::serialize_node: no serialization access to the node/document!" ); + + // create a SAXWriter to take the serialization events, and connect it to our pipe + Reference< css::xml::sax::XWriter > const xSaxWriter = Writer::create( comphelper::getProcessComponentContext() ); + xSaxWriter->setOutputStream( Reference< css::io::XOutputStream >( m_xBuffer, UNO_QUERY_THROW) ); + + // do the serialization + xSerializer->serialize( Reference< XDocumentHandler >(xSaxWriter, UNO_QUERY_THROW), Sequence< StringPair >() ); + } + catch( const Exception& ) + { + DBG_UNHANDLED_EXCEPTION("forms.xforms"); + } +} + +void +CSerializationAppXML::serialize() +{ + if (!m_aFragment.is()) return; + + Reference< XNode > cur = m_aFragment->getFirstChild(); + while (cur.is()) + { + serialize_node(cur); + cur = cur->getNextSibling(); + } + m_xBuffer->closeOutput(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/forms/source/xforms/submission/serialization_app_xml.hxx b/forms/source/xforms/submission/serialization_app_xml.hxx new file mode 100644 index 000000000..51fb1eb54 --- /dev/null +++ b/forms/source/xforms/submission/serialization_app_xml.hxx @@ -0,0 +1,40 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/io/XPipe.hpp> + +#include "serialization.hxx" + +class CSerializationAppXML : public CSerialization +{ +private: + css::uno::Reference<css::io::XPipe> m_xBuffer; + + void serialize_node(const css::uno::Reference<css::xml::dom::XNode>& aNode); + +public: + CSerializationAppXML(); + + virtual void serialize() override; + virtual css::uno::Reference<css::io::XInputStream> getInputStream() override; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/forms/source/xforms/submission/serialization_urlencoded.cxx b/forms/source/xforms/submission/serialization_urlencoded.cxx new file mode 100644 index 000000000..22a625ccb --- /dev/null +++ b/forms/source/xforms/submission/serialization_urlencoded.cxx @@ -0,0 +1,181 @@ +/* -*- 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 . + */ + + +#include <com/sun/star/io/Pipe.hpp> +#include <com/sun/star/xml/xpath/XPathObjectType.hpp> +#include <com/sun/star/xml/dom/XNode.hpp> +#include <com/sun/star/xml/dom/XText.hpp> +#include <com/sun/star/xml/dom/XNodeList.hpp> +#include <com/sun/star/xml/dom/NodeType.hpp> +#include <rtl/character.hxx> +#include <rtl/ustrbuf.hxx> +#include <rtl/strbuf.hxx> +#include <comphelper/processfactory.hxx> + +#include <stdio.h> + +#include "serialization_urlencoded.hxx" + +using namespace css::uno; +using namespace css::io; +using namespace css::xml::xpath; +using namespace css::xml::dom; + +CSerializationURLEncoded::CSerializationURLEncoded() + : m_aPipe(Pipe::create(comphelper::getProcessComponentContext())) +{ +} + + +/* + rfc2396 + reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | + "$" | "," + mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")" + unreserved = alphanum | mark +*/ +bool CSerializationURLEncoded::is_unreserved(char c) +{ + if (rtl::isAsciiAlphanumeric(static_cast<unsigned char>(c))) + return true; + switch (c) { + case '-': + case '_': + case '.': + case '!': + case '~': + case '*': + case '\'': + case '(': + case ')': + return true; + } + return false; +} +void CSerializationURLEncoded::encode_and_append( + std::u16string_view aString, OStringBuffer& aBuffer) +{ + OString utf8String = OUStringToOString(aString, RTL_TEXTENCODING_UTF8); + const sal_uInt8 *pString = reinterpret_cast< const sal_uInt8 * >( utf8String.getStr() ); + char tmpChar[4]; + + while( *pString != 0) + { + if( *pString < 0x80 ) + { + if ( is_unreserved(*pString) ) { + aBuffer.append(char(*pString)); + } else if (*pString == 0x20) { + aBuffer.append('+'); + } else if (*pString == 0x0d && *(pString+1) == 0x0a) { + aBuffer.append("%0D%0A"); + pString++; + } else if (*pString == 0x0a) { + aBuffer.append("%0D%0A"); + } else { + snprintf(tmpChar, 4, "%%%X", *pString % 0x100); + aBuffer.append(tmpChar); + } + } else { + snprintf(tmpChar, 4, "%%%X", *pString % 0x100); + aBuffer.append(tmpChar); + while (*pString >= 0x80) { + // continuation... + pString++; + snprintf(tmpChar, 4, "%%%X", *pString % 0x100); + aBuffer.append(tmpChar); + } + } + pString++; + } +} + +void CSerializationURLEncoded::serialize_node(const Reference< XNode >& aNode) +{ + // serialize recursive + // every element node E that has a text child T will be serialized in document order + // <E1>T1<E2>T2</E2></E1><E3>T3</E3> -> E1=T2&E2=T2&E3=T3 (En := local name) + + // this node + Reference< XNodeList > aChildList = aNode->getChildNodes(); + Reference< XNode > aChild; + // is this an element node? + if (aNode->getNodeType() == NodeType_ELEMENT_NODE) + { + OUString aName = aNode->getNodeName(); + // find any text children + OUStringBuffer aValue; + Reference< XText > aText; + for(sal_Int32 i=0; i < aChildList->getLength(); i++) + { + aChild = aChildList->item(i); + if (aChild->getNodeType() == NodeType_TEXT_NODE) + { + aText.set(aChild, UNO_QUERY); + aValue.append(aText->getData()); + } + } + + // found anything? + if (!aValue.isEmpty()) + { + OUString aUnencValue = aValue.makeStringAndClear(); + OStringBuffer aEncodedBuffer; + encode_and_append(aName, aEncodedBuffer); + aEncodedBuffer.append("="); + encode_and_append(aUnencValue, aEncodedBuffer); + aEncodedBuffer.append("&"); + sal_Int8 const *pData = reinterpret_cast<sal_Int8 const *>(aEncodedBuffer.getStr()); + Sequence< sal_Int8 > sData(pData, aEncodedBuffer.getLength()); + m_aPipe->writeBytes(sData); + } + } + + // element children... + for(sal_Int32 i=0; i < aChildList->getLength(); i++) + { + aChild = aChildList->item(i); + // if this is an element node, it might be a candidate for serialization + if (aChild.is() && aChild->getNodeType() == NodeType_ELEMENT_NODE) + serialize_node(aChild); + } +} + +void CSerializationURLEncoded::serialize() +{ + + // output stream to the pipe buffer + + css::uno::Reference< css::xml::dom::XNode > cur = m_aFragment->getFirstChild(); + while (cur.is()) + { + serialize_node(cur); + cur = cur->getNextSibling(); + } + m_aPipe->closeOutput(); +} + +Reference< XInputStream > CSerializationURLEncoded::getInputStream() +{ + return m_aPipe; +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/forms/source/xforms/submission/serialization_urlencoded.hxx b/forms/source/xforms/submission/serialization_urlencoded.hxx new file mode 100644 index 000000000..2ae91c1f1 --- /dev/null +++ b/forms/source/xforms/submission/serialization_urlencoded.hxx @@ -0,0 +1,47 @@ +/* -*- 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 . + */ + +#pragma once + +#include <sal/config.h> + +#include <string_view> + +#include <com/sun/star/io/XPipe.hpp> + +#include <rtl/strbuf.hxx> + +#include "serialization.hxx" + +class CSerializationURLEncoded : public CSerialization +{ +private: + css::uno::Reference<css::io::XPipe> m_aPipe; + + static bool is_unreserved(char); + static void encode_and_append(std::u16string_view aString, OStringBuffer& aBuffer); + void serialize_node(const css::uno::Reference<css::xml::dom::XNode>& aNode); + +public: + CSerializationURLEncoded(); + virtual void serialize() override; + virtual css::uno::Reference<css::io::XInputStream> getInputStream() override; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/forms/source/xforms/submission/submission.hxx b/forms/source/xforms/submission/submission.hxx new file mode 100644 index 000000000..ac80947aa --- /dev/null +++ b/forms/source/xforms/submission/submission.hxx @@ -0,0 +1,137 @@ +/* -*- 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 . + */ + +#pragma once + +#include <tools/urlobj.hxx> +#include <osl/conditn.hxx> +#include <osl/mutex.hxx> +#include <comphelper/processfactory.hxx> +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/xml/dom/XDocumentFragment.hpp> + +#include <com/sun/star/ucb/XCommandEnvironment.hpp> +#include <com/sun/star/ucb/XProgressHandler.hpp> + +#include <com/sun/star/task/XInteractionHandler.hpp> + +#include <com/sun/star/frame/XFrame.hpp> + +#include <cppuhelper/implbase.hxx> + +#include "serialization.hxx" + +#include <memory> + +class CSubmissionPut; +class CSubmissionPost; +class CSubmissionGet; + +class CCommandEnvironmentHelper final : public cppu::WeakImplHelper< css::ucb::XCommandEnvironment > +{ + friend class CSubmissionPut; + friend class CSubmissionPost; + friend class CSubmissionGet; + friend class CSubmission; + + css::uno::Reference< css::task::XInteractionHandler > m_aInteractionHandler; + css::uno::Reference< css::ucb::XProgressHandler > m_aProgressHandler; + +public: + virtual css::uno::Reference< css::task::XInteractionHandler > SAL_CALL getInteractionHandler() override + { + return m_aInteractionHandler; + } + virtual css::uno::Reference< css::ucb::XProgressHandler > SAL_CALL getProgressHandler() override + { + return m_aProgressHandler; + } +}; + +class CProgressHandlerHelper final : public cppu::WeakImplHelper< css::ucb::XProgressHandler > +{ + friend class CSubmissionPut; + friend class CSubmissionPost; + friend class CSubmissionGet; + osl::Condition m_cFinished; + osl::Mutex m_mLock; + sal_Int32 m_count; +public: + CProgressHandlerHelper() + : m_count(0) + {} + virtual void SAL_CALL push( const css::uno::Any& /*aStatus*/) override + { + m_mLock.acquire(); + m_count++; + m_mLock.release(); + } + virtual void SAL_CALL update(const css::uno::Any& /*aStatus*/) override + { + } + virtual void SAL_CALL pop() override + { + m_mLock.acquire(); + m_count--; + if (m_count == 0) + m_cFinished.set(); + m_mLock.release(); + } +}; + +class CSubmission +{ + +protected: + INetURLObject m_aURLObj; + css::uno::Reference< css::xml::dom::XDocumentFragment > m_aFragment; + css::uno::Reference< css::io::XInputStream > m_aResultStream; + css::uno::Reference< css::uno::XComponentContext > m_xContext; + + ::std::unique_ptr< CSerialization > createSerialization(const css::uno::Reference< css::task::XInteractionHandler >& aHandler + ,css::uno::Reference<css::ucb::XCommandEnvironment>& _rOutEnv); + +public: + enum SubmissionResult { + SUCCESS, + UNKNOWN_ERROR + }; + + CSubmission(std::u16string_view aURL, const css::uno::Reference< css::xml::dom::XDocumentFragment >& aFragment) + : m_aURLObj(aURL) + , m_aFragment(aFragment) + , m_xContext(::comphelper::getProcessComponentContext()) + {} + + bool IsWebProtocol() const + { + INetProtocol eProtocol = m_aURLObj.GetProtocol(); + return eProtocol == INetProtocol::Http || eProtocol == INetProtocol::Https; + } + + virtual ~CSubmission() {} + + virtual SubmissionResult submit(const css::uno::Reference< css::task::XInteractionHandler >& ) = 0; + + SubmissionResult replace(std::u16string_view, const css::uno::Reference< css::xml::dom::XDocument >&, const css::uno::Reference< css::frame::XFrame>&); + +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/forms/source/xforms/submission/submission_get.cxx b/forms/source/xforms/submission/submission_get.cxx new file mode 100644 index 000000000..5a82aeae0 --- /dev/null +++ b/forms/source/xforms/submission/submission_get.cxx @@ -0,0 +1,103 @@ +/* -*- 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 . + */ + + +#include "submission_get.hxx" +#include "serialization_urlencoded.hxx" + +#include <rtl/strbuf.hxx> +#include <osl/diagnose.h> +#include <ucbhelper/content.hxx> +#include <com/sun/star/io/Pipe.hpp> +#include <com/sun/star/task/InteractionHandler.hpp> +#include <tools/diagnose_ex.h> + +using namespace css::uno; +using namespace css::ucb; +using namespace css::task; +using namespace css::io; +using namespace osl; +using namespace ucbhelper; + + +CSubmissionGet::CSubmissionGet(std::u16string_view aURL, const css::uno::Reference< css::xml::dom::XDocumentFragment >& aFragment) + : CSubmission(aURL, aFragment) +{ +} + +CSubmission::SubmissionResult CSubmissionGet::submit(const css::uno::Reference< css::task::XInteractionHandler >& aInteractionHandler) +{ + // GET always uses application/x-www-formurlencoded + CSerializationURLEncoded aSerialization; + aSerialization.setSource(m_aFragment); + aSerialization.serialize(); + + css::uno::Reference< XInputStream > aInStream = aSerialization.getInputStream(); + + // create a commandEnvironment and use the default interaction handler + rtl::Reference<CCommandEnvironmentHelper> pHelper = new CCommandEnvironmentHelper; + if( aInteractionHandler.is() ) + pHelper->m_aInteractionHandler = aInteractionHandler; + else + pHelper->m_aInteractionHandler.set( + css::task::InteractionHandler::createWithParent(m_xContext, nullptr), UNO_QUERY_THROW); + rtl::Reference<CProgressHandlerHelper> pProgressHelper = new CProgressHandlerHelper; + pHelper->m_aProgressHandler.set(pProgressHelper); + + // UCB has ownership of environment... + css::uno::Reference< XCommandEnvironment > aEnvironment(pHelper); + + // append query string to the URL + try { + OStringBuffer aUTF8QueryURL(OUStringToOString(m_aURLObj.GetMainURL(INetURLObject::DecodeMechanism::NONE), + RTL_TEXTENCODING_UTF8)); + OStringBuffer aQueryString; + + const sal_Int32 size = 1024; + sal_Int32 n = 0; + Sequence< sal_Int8 > aByteBuffer(size); + while ((n = aInStream->readSomeBytes(aByteBuffer, size-1)) != 0) + aQueryString.append(reinterpret_cast<char const *>(aByteBuffer.getConstArray()), n); + if (!aQueryString.isEmpty() && m_aURLObj.GetProtocol() != INetProtocol::File) + { + aUTF8QueryURL.append('?'); + aUTF8QueryURL.append(aQueryString); + } + OUString aQueryURL = OStringToOUString(aUTF8QueryURL.makeStringAndClear(), RTL_TEXTENCODING_UTF8); + ucbhelper::Content aContent(aQueryURL, aEnvironment, m_xContext); + css::uno::Reference< XOutputStream > aPipe( css::io::Pipe::create(m_xContext), UNO_QUERY_THROW ); + if (!aContent.openStream(aPipe)) + return UNKNOWN_ERROR; + // get reply + try { + m_aResultStream = aContent.openStream(); + } catch (const Exception&) { + OSL_FAIL("Cannot open reply stream from content"); + } + } catch (const Exception&) + { + // XXX + TOOLS_WARN_EXCEPTION( "forms.misc", "Exception during UCB operation."); + return UNKNOWN_ERROR; + } + + return SUCCESS; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/forms/source/xforms/submission/submission_get.hxx b/forms/source/xforms/submission/submission_get.hxx new file mode 100644 index 000000000..086fab25b --- /dev/null +++ b/forms/source/xforms/submission/submission_get.hxx @@ -0,0 +1,33 @@ +/* -*- 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 . + */ + +#pragma once + +#include "submission.hxx" + +class CSubmissionGet : public CSubmission +{ +public: + CSubmissionGet(std::u16string_view aURL, + const css::uno::Reference<css::xml::dom::XDocumentFragment>& aFragment); + virtual SubmissionResult + submit(const css::uno::Reference<css::task::XInteractionHandler>& aInteractionHandler) override; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/forms/source/xforms/submission/submission_post.cxx b/forms/source/xforms/submission/submission_post.cxx new file mode 100644 index 000000000..89ffd2323 --- /dev/null +++ b/forms/source/xforms/submission/submission_post.cxx @@ -0,0 +1,78 @@ +/* -*- 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 . + */ + + +#include <memory> + +#include "submission_post.hxx" + +#include <osl/diagnose.h> +#include <ucbhelper/content.hxx> +#include <ucbhelper/activedatasink.hxx> +#include <com/sun/star/ucb/PostCommandArgument2.hpp> +#include <tools/diagnose_ex.h> + +using namespace css::uno; +using namespace css::ucb; +using namespace css::task; +using namespace css::io; +using namespace osl; +using namespace ucbhelper; + + +CSubmissionPost::CSubmissionPost(std::u16string_view aURL, const css::uno::Reference< css::xml::dom::XDocumentFragment >& aFragment) + : CSubmission(aURL, aFragment) +{ +} + +CSubmission::SubmissionResult CSubmissionPost::submit(const css::uno::Reference< css::task::XInteractionHandler >& aInteractionHandler) +{ + // PUT always uses application/xml + css::uno::Reference< XCommandEnvironment > aEnvironment; + std::unique_ptr< CSerialization > apSerialization(createSerialization(aInteractionHandler,aEnvironment)); + + try { + ucbhelper::Content aContent(m_aURLObj.GetMainURL(INetURLObject::DecodeMechanism::NONE), aEnvironment, comphelper::getProcessComponentContext()); + + // use post command + PostCommandArgument2 aPostArgument; + aPostArgument.Source = apSerialization->getInputStream(); + css::uno::Reference< XActiveDataSink > aSink(new ucbhelper::ActiveDataSink); + aPostArgument.Sink = aSink; + aPostArgument.MediaType = "application/xml"; + aPostArgument.Referer.clear(); + Any aCommandArgument; + aCommandArgument <<= aPostArgument; + aContent.executeCommand( "post", aCommandArgument); + + try { + m_aResultStream = aSink->getInputStream(); + } catch (const Exception&) { + OSL_FAIL("Cannot open reply stream from content"); + } + } catch (const Exception&) + { + TOOLS_WARN_EXCEPTION( "forms.misc", "Exception during UCB operation."); + return UNKNOWN_ERROR; + } + + return SUCCESS; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/forms/source/xforms/submission/submission_post.hxx b/forms/source/xforms/submission/submission_post.hxx new file mode 100644 index 000000000..0fb63472b --- /dev/null +++ b/forms/source/xforms/submission/submission_post.hxx @@ -0,0 +1,32 @@ +/* -*- 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 . + */ + +#pragma once + +#include "submission.hxx" + +class CSubmissionPost : public CSubmission +{ +public: + CSubmissionPost(std::u16string_view aURL, const css::uno::Reference< css::xml::dom::XDocumentFragment >& aFragment); + virtual SubmissionResult submit(const css::uno::Reference< css::task::XInteractionHandler >& aInteractionHandler) override; + +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/forms/source/xforms/submission/submission_put.cxx b/forms/source/xforms/submission/submission_put.cxx new file mode 100644 index 000000000..678cd7921 --- /dev/null +++ b/forms/source/xforms/submission/submission_put.cxx @@ -0,0 +1,67 @@ +/* -*- 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 . + */ + + +#include <memory> + +#include "submission_put.hxx" + +#include <comphelper/processfactory.hxx> +#include <ucbhelper/content.hxx> +#include <tools/diagnose_ex.h> + +using namespace css::uno; +using namespace css::ucb; +using namespace css::task; +using namespace css::io; +using namespace osl; +using namespace ucbhelper; + + +CSubmissionPut::CSubmissionPut(std::u16string_view aURL, const css::uno::Reference< css::xml::dom::XDocumentFragment >& aFragment) + : CSubmission(aURL, aFragment) +{ +} + +CSubmission::SubmissionResult CSubmissionPut::submit(const css::uno::Reference< css::task::XInteractionHandler >& aInteractionHandler) +{ + css::uno::Reference< XCommandEnvironment > aEnvironment; + std::unique_ptr< CSerialization > apSerialization(createSerialization(aInteractionHandler,aEnvironment)); + + try { + ucbhelper::Content aContent(m_aURLObj.GetMainURL(INetURLObject::DecodeMechanism::NONE), aEnvironment, comphelper::getProcessComponentContext()); + + // insert serialized data to content -> PUT + css::uno::Reference< XInputStream > aInStream = apSerialization->getInputStream(); + aContent.writeStream(aInStream, true); + //aContent.closeStream(); + + // no content as a result of put... + } + catch ( const Exception& ) + { + TOOLS_WARN_EXCEPTION( "forms.misc", "Exception during UCB operation." ); + return UNKNOWN_ERROR; + } + + + return SUCCESS; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/forms/source/xforms/submission/submission_put.hxx b/forms/source/xforms/submission/submission_put.hxx new file mode 100644 index 000000000..2dce84307 --- /dev/null +++ b/forms/source/xforms/submission/submission_put.hxx @@ -0,0 +1,33 @@ +/* -*- 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 . + */ + +#pragma once + +#include "submission.hxx" + +class CSubmissionPut : public CSubmission +{ +public: + CSubmissionPut(std::u16string_view aURL, + const css::uno::Reference<css::xml::dom::XDocumentFragment>& aFragment); + virtual SubmissionResult + submit(const css::uno::Reference<css::task::XInteractionHandler>& aInteractionHandler) override; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/forms/source/xforms/unohelper.cxx b/forms/source/xforms/unohelper.cxx new file mode 100644 index 000000000..8576e5e29 --- /dev/null +++ b/forms/source/xforms/unohelper.cxx @@ -0,0 +1,73 @@ +/* -*- 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 . + */ + +#include "unohelper.hxx" + +#include <osl/diagnose.h> + +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/uno/Exception.hpp> +#include <com/sun/star/beans/Property.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/beans/XPropertySetInfo.hpp> +#include <com/sun/star/beans/PropertyAttribute.hpp> + +using com::sun::star::uno::Reference; +using com::sun::star::uno::Sequence; +using com::sun::star::uno::Exception; +using com::sun::star::beans::Property; +using com::sun::star::beans::XPropertySet; +using com::sun::star::beans::XPropertySetInfo; +using com::sun::star::beans::PropertyAttribute::READONLY; + +void xforms::copy(const Reference<XPropertySet>& xFrom, Reference<XPropertySet> const& xTo) +{ + OSL_ENSURE(xFrom.is(), "no source"); + OSL_ENSURE(xTo.is(), "no target"); + + // get property names & infos, and iterate over target properties + Sequence<Property> aProperties = xTo->getPropertySetInfo()->getProperties(); + sal_Int32 nProperties = aProperties.getLength(); + const Property* pProperties = aProperties.getConstArray(); + Reference<XPropertySetInfo> xFromInfo = xFrom->getPropertySetInfo(); + for (sal_Int32 n = 0; n < nProperties; n++) + { + const OUString& rName = pProperties[n].Name; + + // if both set have the property, copy the value + // (catch and ignore exceptions, if any) + if (xFromInfo->hasPropertyByName(rName)) + { + try + { + Property aProperty = xFromInfo->getPropertyByName(rName); + if ((aProperty.Attributes & READONLY) == 0) + xTo->setPropertyValue(rName, xFrom->getPropertyValue(rName)); + } + catch (const Exception&) + { + // ignore any errors; we'll copy as good as we can + } + } + // else: no property? then ignore. + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/forms/source/xforms/unohelper.hxx b/forms/source/xforms/unohelper.hxx new file mode 100644 index 000000000..5aace2f3c --- /dev/null +++ b/forms/source/xforms/unohelper.hxx @@ -0,0 +1,41 @@ +/* -*- 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 . + */ +#pragma once + +#include <sal/types.h> + +namespace com::sun::star { + namespace uno { + class XInterface; + template<class T> class Reference; + } + namespace beans { + class XPropertySet; + } +} + +namespace xforms +{ + +/** copy the properties from one PropertySet into the next */ +void copy( const css::uno::Reference<css::beans::XPropertySet>& , css::uno::Reference<css::beans::XPropertySet> const & ); + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/forms/source/xforms/xforms_services.cxx b/forms/source/xforms/xforms_services.cxx new file mode 100644 index 000000000..23a1ee400 --- /dev/null +++ b/forms/source/xforms/xforms_services.cxx @@ -0,0 +1,59 @@ +/* -*- 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 . + */ + +#include "NameContainer.hxx" + +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/uno/XInterface.hpp> +#include <cppuhelper/supportsservice.hxx> + +using namespace ::com::sun::star; + +namespace { + +class Implementation: + public cppu::ImplInheritanceHelper< + NameContainer<css::uno::Reference<css::beans::XPropertySet>>, + css::lang::XServiceInfo> +{ + OUString SAL_CALL getImplementationName() override + { return "com.sun.star.form.XForms"; } + + sal_Bool SAL_CALL supportsService(OUString const & ServiceName) override + { return cppu::supportsService(this, ServiceName); } + + css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override + { + return {"com.sun.star.xforms.XForms"}; + } +}; + +} + +extern "C" SAL_DLLPUBLIC_EXPORT uno::XInterface* +com_sun_star_form_XForms_get_implementation(uno::XComponentContext*, + uno::Sequence<uno::Any> const &) +{ + return cppu::acquire(new Implementation); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/forms/source/xforms/xformsevent.cxx b/forms/source/xforms/xformsevent.cxx new file mode 100644 index 000000000..fb9758f1f --- /dev/null +++ b/forms/source/xforms/xformsevent.cxx @@ -0,0 +1,83 @@ +/* -*- 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 . + */ + + +#include "xformsevent.hxx" + +namespace com::sun::star::xforms { + +void SAL_CALL XFormsEventConcrete::initXFormsEvent(const OUString& typeArg, + sal_Bool canBubbleArg, sal_Bool cancelableArg) +{ + initEvent(typeArg, canBubbleArg, cancelableArg); +} + +OUString SAL_CALL XFormsEventConcrete::getType() +{ + return m_eventType; +} + +css::uno::Reference< css::xml::dom::events::XEventTarget > SAL_CALL XFormsEventConcrete::getTarget() +{ + return css::uno::Reference< css::xml::dom::events::XEventTarget >(); +} + +css::uno::Reference< css::xml::dom::events::XEventTarget > SAL_CALL XFormsEventConcrete::getCurrentTarget() +{ + return css::uno::Reference< css::xml::dom::events::XEventTarget >(); +} + +css::xml::dom::events::PhaseType SAL_CALL XFormsEventConcrete::getEventPhase() +{ + return css::xml::dom::events::PhaseType_CAPTURING_PHASE; +} + +sal_Bool SAL_CALL XFormsEventConcrete::getBubbles() +{ + return m_bubbles; +} + +sal_Bool SAL_CALL XFormsEventConcrete::getCancelable() +{ + return m_cancelable; +} + +css::util::Time SAL_CALL XFormsEventConcrete::getTimeStamp() +{ + return css::util::Time(); +} + +void SAL_CALL XFormsEventConcrete::stopPropagation() +{ +} +void SAL_CALL XFormsEventConcrete::preventDefault() +{ +} + +void SAL_CALL XFormsEventConcrete::initEvent(const OUString& eventTypeArg, sal_Bool canBubbleArg, + sal_Bool cancelableArg) +{ + m_eventType = eventTypeArg; + m_bubbles = canBubbleArg; + m_cancelable = cancelableArg; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/forms/source/xforms/xformsevent.hxx b/forms/source/xforms/xformsevent.hxx new file mode 100644 index 000000000..2622780be --- /dev/null +++ b/forms/source/xforms/xformsevent.hxx @@ -0,0 +1,68 @@ +/* -*- 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 . + */ +#pragma once + +#include <sal/types.h> +#include <cppuhelper/implbase.hxx> +#include <com/sun/star/uno/Reference.h> +#include <com/sun/star/xforms/XFormsEvent.hpp> +#include <com/sun/star/xml/dom/events/XEventTarget.hpp> + +namespace com::sun::star::xforms { + +class XFormsEventConcrete : public cppu::WeakImplHelper< XFormsEvent > { + + public: + + XFormsEventConcrete() + : m_bubbles(false) + , m_cancelable(false) + { + } + + virtual OUString SAL_CALL getType() override; + virtual css::uno::Reference< css::xml::dom::events::XEventTarget > SAL_CALL getTarget() override; + virtual css::uno::Reference< css::xml::dom::events::XEventTarget > SAL_CALL getCurrentTarget() override; + virtual css::xml::dom::events::PhaseType SAL_CALL getEventPhase() override; + virtual sal_Bool SAL_CALL getBubbles() override; + virtual sal_Bool SAL_CALL getCancelable() override; + virtual css::util::Time SAL_CALL getTimeStamp() override; + virtual void SAL_CALL stopPropagation() override; + virtual void SAL_CALL preventDefault() override; + + virtual void SAL_CALL initXFormsEvent( + const OUString& typeArg, + sal_Bool canBubbleArg, + sal_Bool cancelableArg ) override; + + virtual void SAL_CALL initEvent( + const OUString& eventTypeArg, + sal_Bool canBubbleArg, + sal_Bool cancelableArg) override; + + private: + + OUString m_eventType; + bool m_bubbles; + bool m_cancelable; +}; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/forms/source/xforms/xmlhelper.cxx b/forms/source/xforms/xmlhelper.cxx new file mode 100644 index 000000000..75f0f92bd --- /dev/null +++ b/forms/source/xforms/xmlhelper.cxx @@ -0,0 +1,133 @@ +/* -*- 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 . + */ + + +#include "xmlhelper.hxx" + +#include <rtl/ustring.hxx> +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/xml/dom/DocumentBuilder.hpp> +#include <comphelper/processfactory.hxx> + +using com::sun::star::uno::Reference; +using com::sun::star::container::XNameContainer; +using com::sun::star::xml::dom::DocumentBuilder; +using com::sun::star::xml::dom::XDocumentBuilder; + + +// determine valid XML name + + +// character class: +// 1: NameStartChar +// 2: NameChar +// 4: NCNameStartChar +// 8: NCNameChar +static sal_uInt8 lcl_getCharClass( sal_Unicode c ) +{ + sal_uInt8 nClass = 0; + + // NameStartChar + if( (c >= 'A' && c <= 'Z') + || c == '_' + || (c >= 'a' && c <= 'z') + || (c >= 0x00C0 && c <= 0x00D6) + || (c >= 0x00D8 && c <= 0x00F6) + || (c >= 0x00F8 && c <= 0x02FF) + || (c >= 0x0370 && c <= 0x037D) + || (c >= 0x037F && c <= 0x1FFF) + || (c >= 0x200C && c <= 0x200D) + || (c >= 0x2070 && c <= 0x218F) + || (c >= 0x2C00 && c <= 0x2FEF) + || (c >= 0x3001 && c <= 0xD7FF) + || (c >= 0xF900 && c <= 0xFDCF) + || (c >= 0xFDF0 && c <= 0xFFFD) + + // surrogates + || (c >= 0xD800 && c <= 0xDBFF) + || (c >= 0xDC00 && c <= 0xDFFF) ) + { + nClass = 15; + } + else if( c == '-' + || c == '.' + || (c >= '0' && c <= '9') + || (c == 0x00B7) + || (c >= 0x0300 && c <= 0x036F) + || (c >= 0x203F && c <= 0x2040) ) + { + nClass = 10; + } + else if( c == ':' ) + { + nClass = 3; + } + + return nClass; +} + +bool isValidQName( const OUString& sName, + const Reference<XNameContainer>& /*xNamespaces*/ ) +{ + sal_Int32 nLength = sName.getLength(); + const sal_Unicode* pName = sName.getStr(); + + bool bRet = false; + sal_Int32 nColon = 0; + if( nLength > 0 ) + { + bRet = ( ( lcl_getCharClass( pName[0] ) & 4 ) != 0 ); + for( sal_Int32 n = 1; n < nLength; n++ ) + { + sal_uInt8 nClass = lcl_getCharClass( pName[n] ); + bRet &= ( ( nClass & 2 ) != 0 ); + if( nClass == 3 ) + nColon++; + } + } + if( nColon > 1 ) + bRet = false; + + return bRet; +} + +bool isValidPrefixName( const OUString& sName, + const Reference<XNameContainer>& /*xNamespaces*/ ) +{ + sal_Int32 nLength = sName.getLength(); + const sal_Unicode* pName = sName.getStr(); + bool bRet = false; + + if( nLength > 0 ) + { + bRet = ( ( lcl_getCharClass( pName[0] ) & 4 ) != 0 ); + for( sal_Int32 n = 1; n < nLength; n++ ) + bRet &= ( ( lcl_getCharClass( pName[n] ) & 8 ) != 0 ); + } + + return bRet; +} + +Reference<XDocumentBuilder> getDocumentBuilder() +{ + Reference<XDocumentBuilder> xBuilder(DocumentBuilder::create(::comphelper::getProcessComponentContext())); + return xBuilder; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/forms/source/xforms/xmlhelper.hxx b/forms/source/xforms/xmlhelper.hxx new file mode 100644 index 000000000..e8995f764 --- /dev/null +++ b/forms/source/xforms/xmlhelper.hxx @@ -0,0 +1,40 @@ +/* -*- 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 . + */ + +#pragma once + +#include <rtl/ustring.hxx> + +namespace com::sun::star { + namespace uno { template<typename T> class Reference; } + namespace container { class XNameContainer; } + namespace xml::dom { class XDocumentBuilder; } +} + + +bool isValidQName( const OUString& sName, + const css::uno::Reference<css::container::XNameContainer>& xNamespaces ); + +bool isValidPrefixName( const OUString& sName, + const css::uno::Reference<css::container::XNameContainer>& xNamespaces ); + +css::uno::Reference<css::xml::dom::XDocumentBuilder> getDocumentBuilder(); + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/forms/source/xforms/xpathlib/extension.cxx b/forms/source/xforms/xpathlib/extension.cxx new file mode 100644 index 000000000..0af6a3eb1 --- /dev/null +++ b/forms/source/xforms/xpathlib/extension.cxx @@ -0,0 +1,74 @@ +/* -*- 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 . + */ + + +#include <com/sun/star/xml/xpath/Libxml2ExtensionHandle.hpp> +#include <com/sun/star/beans/NamedValue.hpp> +#include "extension.hxx" +#include "xpathlib.hxx" + +namespace com::sun::star::uno { + class XComponentContext; +} + +using namespace com::sun::star::uno; +using namespace com::sun::star::lang; +using namespace com::sun::star::xforms; +using namespace com::sun::star::xml::xpath; +using namespace com::sun::star::beans; + +Libxml2ExtensionHandle SAL_CALL CLibxml2XFormsExtension::getLibxml2ExtensionHandle() +{ + Libxml2ExtensionHandle aHandle; + aHandle.functionLookupFunction = reinterpret_cast< sal_Int64 >( &xforms_lookupFunc ); + aHandle.functionData = reinterpret_cast< sal_Int64 >( this ); + aHandle.variableLookupFunction = sal_Int64(0); + aHandle.variableData = sal_Int64(0); + return aHandle; +} + +void SAL_CALL CLibxml2XFormsExtension::initialize(const Sequence< Any >& aSequence) +{ + if (aSequence.getLength() == 2 + && (aSequence[0] >>= m_aModel) + && (aSequence[1] >>= m_aContextNode)) + { + return; + } + + NamedValue aValue; + for (const Any& rArg : aSequence) + { + if (! (rArg >>= aValue)) + throw RuntimeException(); + if ( aValue.Name == "Model" ) + aValue.Value >>= m_aModel; + else if ( aValue.Name == "ContextNode" ) + aValue.Value >>= m_aContextNode; + } +} + + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +com_sun_star_comp_xml_xpath_XFormsExtension_get_implementation(css::uno::XComponentContext*, + css::uno::Sequence<css::uno::Any> const &) +{ + return cppu::acquire(new CLibxml2XFormsExtension()); +} +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/forms/source/xforms/xpathlib/extension.hxx b/forms/source/xforms/xpathlib/extension.hxx new file mode 100644 index 000000000..267576b74 --- /dev/null +++ b/forms/source/xforms/xpathlib/extension.hxx @@ -0,0 +1,52 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/uno/Any.hxx> +#include <cppuhelper/implbase.hxx> + +#include <com/sun/star/lang/XInitialization.hpp> +#include <com/sun/star/xml/xpath/XXPathExtension.hpp> +#include <com/sun/star/xforms/XModel.hpp> +#include <com/sun/star/xml/dom/XNode.hpp> + +class CLibxml2XFormsExtension + : public cppu::WeakImplHelper<css::xml::xpath::XXPathExtension, css::lang::XInitialization> +{ +private: + css::uno::Reference<css::xforms::XModel> m_aModel; + css::uno::Reference<css::xml::dom::XNode> m_aContextNode; + +public: + CLibxml2XFormsExtension() {} + + const css::uno::Reference<css::xforms::XModel>& getModel() const { return m_aModel; } + const css::uno::Reference<css::xml::dom::XNode>& getContextNode() const + { + return m_aContextNode; + } + + virtual css::xml::xpath::Libxml2ExtensionHandle SAL_CALL getLibxml2ExtensionHandle() override; + virtual void SAL_CALL initialize(const css::uno::Sequence<css::uno::Any>& aSequence) override; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/forms/source/xforms/xpathlib/xpathlib.cxx b/forms/source/xforms/xpathlib/xpathlib.cxx new file mode 100644 index 000000000..e2f0a10eb --- /dev/null +++ b/forms/source/xforms/xpathlib/xpathlib.cxx @@ -0,0 +1,539 @@ +/* -*- 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 . + */ + + +#include <string.h> + +#include <comphelper/servicehelper.hxx> +#include <o3tl/string_view.hxx> +#include <sal/types.h> +#include <rtl/ustring.hxx> +#include <rtl/string.hxx> +#include <rtl/strbuf.hxx> +#include <tools/date.hxx> +#include <tools/time.hxx> +#include <tools/datetime.hxx> + +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/xforms/XModel.hpp> +#include <com/sun/star/xml/dom/XNode.hpp> +#include <com/sun/star/xml/dom/XDocument.hpp> +#include <com/sun/star/lang/XUnoTunnel.hpp> + +#include <boost/lexical_cast.hpp> +#include <libxml/xpathInternals.h> + +#include "xpathlib.hxx" +#include "extension.hxx" + +// C interface + +using namespace com::sun::star::uno; +using namespace com::sun::star::xml::dom; +using namespace com::sun::star::xforms; +using namespace com::sun::star::lang; + +xmlXPathFunction xforms_lookupFunc(void *, const xmlChar *xname, const xmlChar *) +{ + + const char *name = reinterpret_cast<char const *>(xname); + if (strcmp("boolean-from-string", name)==0) + return xforms_booleanFromStringFunction; + else if ((strcmp("if", name))==0) + return xforms_ifFunction; + else if ((strcmp("avg", name))==0) + return xforms_avgFunction; + else if ((strcmp("min", name))==0) + return xforms_minFunction; + else if ((strcmp("max", name))==0) + return xforms_maxFunction; + else if ((strcmp("count-non-empty", name))==0) + return xforms_countNonEmptyFunction; + else if ((strcmp("index", name))==0) + return xforms_indexFunction; + else if ((strcmp("property", name))==0) + return xforms_propertyFunction; + else if ((strcmp("now", name))==0) + return xforms_nowFunction; + else if ((strcmp("days-from-date", name))==0) + return xforms_daysFromDateFunction; + else if ((strcmp("seconds-from-dateTime", name))==0) + return xforms_secondsFromDateTimeFunction; + else if ((strcmp("seconds", name))==0) + return xforms_secondsFunction; + else if ((strcmp("months", name))==0) + return xforms_monthsFunction; + else if ((strcmp("instance", name))==0) + return xforms_instanceFunction; + else if ((strcmp("current", name))==0) + return xforms_currentFunction; + else + return nullptr; +} + +// boolean functions +void xforms_booleanFromStringFunction(xmlXPathParserContextPtr ctxt, int nargs) +{ + if (nargs != 1) XP_ERROR(XPATH_INVALID_ARITY); + xmlChar *pString = xmlXPathPopString(ctxt); + if (xmlXPathCheckError(ctxt)) XP_ERROR(XPATH_INVALID_TYPE); + OUString aString(reinterpret_cast<char*>(pString), strlen(reinterpret_cast<char*>(pString)), RTL_TEXTENCODING_UTF8); + if (aString.equalsIgnoreAsciiCase("true") || + aString.equalsIgnoreAsciiCase("1")) + xmlXPathReturnTrue(ctxt); + else if (aString.equalsIgnoreAsciiCase("false") || + aString.equalsIgnoreAsciiCase("0")) + xmlXPathReturnFalse(ctxt); + else + XP_ERROR(XPATH_NUMBER_ERROR); +} + +void xforms_ifFunction(xmlXPathParserContextPtr ctxt, int nargs) +{ + if (nargs != 3) XP_ERROR(XPATH_INVALID_ARITY); + xmlChar *s2 = xmlXPathPopString(ctxt); + + if (xmlXPathCheckError(ctxt)) XP_ERROR(XPATH_INVALID_TYPE); + xmlChar *s1 = xmlXPathPopString(ctxt); + if (xmlXPathCheckError(ctxt)) XP_ERROR(XPATH_INVALID_TYPE); + bool aBool = xmlXPathPopBoolean(ctxt); + if (xmlXPathCheckError(ctxt)) XP_ERROR(XPATH_INVALID_TYPE); + + if (aBool) + xmlXPathReturnString(ctxt, s1); + else + xmlXPathReturnString(ctxt, s2); + +} + +// Number Functions +void xforms_avgFunction(xmlXPathParserContextPtr ctxt, int nargs) +{ + // use sum(), div() and count() + if (nargs != 1) XP_ERROR(XPATH_INVALID_ARITY); + + // save nodeset + xmlXPathObjectPtr pObject = valuePop(ctxt); + if (xmlXPathCheckError(ctxt)) XP_ERROR(XPATH_INVALID_TYPE); + //push back a copy + valuePush(ctxt, xmlXPathObjectCopy(pObject)); + // get the Sum + xmlXPathSumFunction(ctxt, 1); + double nSum = xmlXPathPopNumber(ctxt); + // push a copy once more + valuePush(ctxt, xmlXPathObjectCopy(pObject)); + xmlXPathCountFunction(ctxt, 1); + double nCount = xmlXPathPopNumber(ctxt); + // push args for div() + xmlXPathReturnNumber(ctxt, nSum); + xmlXPathReturnNumber(ctxt, nCount); + xmlXPathDivValues(ctxt); + // the result is now on the ctxt stack + xmlXPathFreeObject(pObject); +} + +void xforms_minFunction(xmlXPathParserContextPtr ctxt, int nargs) +{ + if (nargs != 1) XP_ERROR(XPATH_INVALID_ARITY); + xmlNodeSetPtr pNodeSet = xmlXPathPopNodeSet(ctxt); + if (xmlXPathCheckError(ctxt)) XP_ERROR(XPATH_INVALID_TYPE); + double nMinimum = 0; + for (int i = 0; i < xmlXPathNodeSetGetLength(pNodeSet); i++) + { + double nNumber = xmlXPathCastNodeToNumber(xmlXPathNodeSetItem(pNodeSet, i)); + if (xmlXPathIsNaN(nNumber)) + { + xmlXPathReturnNumber(ctxt, xmlXPathNAN); + return; + } + if (i == 0) + nMinimum = nNumber; + else if (nNumber < nMinimum) + nMinimum = nNumber; + } + xmlXPathReturnNumber(ctxt, nMinimum); +} + +void xforms_maxFunction(xmlXPathParserContextPtr ctxt, int nargs) +{ + if (nargs != 1) XP_ERROR(XPATH_INVALID_ARITY); + xmlNodeSetPtr pNodeSet = xmlXPathPopNodeSet(ctxt); + if (xmlXPathCheckError(ctxt)) XP_ERROR(XPATH_INVALID_TYPE); + double nMaximum = 0; + for (int i = 0; i < xmlXPathNodeSetGetLength(pNodeSet); i++) + { + double nNumber = xmlXPathCastNodeToNumber(xmlXPathNodeSetItem(pNodeSet, i)); + if (xmlXPathIsNaN(nNumber)) + { + xmlXPathReturnNumber(ctxt, xmlXPathNAN); + return; + } + if (i == 0) + nMaximum = nNumber; + else if (nNumber > nMaximum) + nMaximum = nNumber; + } + xmlXPathReturnNumber(ctxt, nMaximum); +} +void xforms_countNonEmptyFunction(xmlXPathParserContextPtr ctxt, int nargs) +{ + if (nargs != 1) XP_ERROR(XPATH_INVALID_ARITY); + xmlNodeSetPtr pNodeSet = xmlXPathPopNodeSet(ctxt); + if (xmlXPathCheckError(ctxt)) XP_ERROR(XPATH_INVALID_TYPE); + sal_Int32 nNotEmpty = 0; + for (int i = 0; i < xmlXPathNodeSetGetLength(pNodeSet); i++) + { + const xmlChar *aString = xmlXPathCastNodeToString(xmlXPathNodeSetItem(pNodeSet, i)); + if (*aString != 0) nNotEmpty++; + } + xmlXPathReturnNumber(ctxt, nNotEmpty); +} +void xforms_indexFunction(xmlXPathParserContextPtr /*ctxt*/, int /*nargs*/) +{ + // function index takes a string argument that is the IDREF of a + // 'repeat' and returns the current 1-based position of the repeat + // index of the identified repeat -- see xforms/9.3.1 + + // doc.getElementByID + // (...) +} + +// String Functions +void xforms_propertyFunction(xmlXPathParserContextPtr ctxt, int nargs) +{ + if (nargs != 1) XP_ERROR(XPATH_INVALID_ARITY); + xmlChar* pString = xmlXPathPopString(ctxt); + if (xmlXPathCheckError(ctxt)) XP_ERROR(XPATH_INVALID_TYPE); + OUString aString(reinterpret_cast<char*>(pString), strlen(reinterpret_cast<char*>(pString)), RTL_TEXTENCODING_UTF8); + if (aString.equalsIgnoreAsciiCase("version")) + xmlXPathReturnString(ctxt, reinterpret_cast<xmlChar *>(const_cast<char *>("1.0"))); + else if (aString.equalsIgnoreAsciiCase("conformance-level")) + xmlXPathReturnString(ctxt, reinterpret_cast<xmlChar *>(const_cast<char *>("conformance"))); + else + xmlXPathReturnEmptyString(ctxt); +} + +// Date and Time Functions + +static OString makeDateTimeString (const DateTime& aDateTime) +{ + OStringBuffer aDateTimeString; + aDateTimeString.append(static_cast<sal_Int32>(aDateTime.GetYear())); + aDateTimeString.append('-'); + if (aDateTime.GetMonth()<10) aDateTimeString.append('0'); + aDateTimeString.append(static_cast<sal_Int32>(aDateTime.GetMonth())); + aDateTimeString.append('-'); + if (aDateTime.GetDay()<10) aDateTimeString.append('0'); + aDateTimeString.append(static_cast<sal_Int32>(aDateTime.GetDay())); + aDateTimeString.append('T'); + if (aDateTime.GetHour()<10) aDateTimeString.append('0'); + aDateTimeString.append(static_cast<sal_Int32>(aDateTime.GetHour())); + aDateTimeString.append(':'); + if (aDateTime.GetMin()<10) aDateTimeString.append('0'); + aDateTimeString.append(static_cast<sal_Int32>(aDateTime.GetMin())); + aDateTimeString.append(':'); + if (aDateTime.GetSec()<10) aDateTimeString.append('0'); + aDateTimeString.append(static_cast<sal_Int32>(aDateTime.GetSec())); + aDateTimeString.append('Z'); + + return aDateTimeString.makeStringAndClear(); +} + +// returns current system date and time in canonical xsd:dateTime +// format +void xforms_nowFunction(xmlXPathParserContextPtr ctxt, int /*nargs*/) +{ + /* + A single lexical representation, which is a subset of the lexical representations + allowed by [ISO 8601], is allowed for dateTime. This lexical representation is the + [ISO 8601] extended format CCYY-MM-DDThh:mm:ss where "CC" represents the century, + "YY" the year, "MM" the month and "DD" the day, preceded by an optional leading "-" + sign to indicate a negative number. If the sign is omitted, "+" is assumed. The letter + "T" is the date/time separator and "hh", "mm", "ss" represent hour, minute and second + respectively. + */ + + /* + 3.2.7.2 Canonical representation + The canonical representation for dateTime is defined by prohibiting certain options + from the Lexical representation (par.3.2.7.1). Specifically, either the time zone must + be omitted or, if present, the time zone must be Coordinated Universal tools::Time (UTC) + indicated by a "Z". + */ + DateTime aDateTime( DateTime::SYSTEM ); + OString aDateTimeString = makeDateTimeString(aDateTime); + xmlChar *pString = static_cast<xmlChar*>(xmlMalloc(aDateTimeString.getLength()+1)); + strncpy(reinterpret_cast<char*>(pString), aDateTimeString.getStr(), aDateTimeString.getLength()); + pString[aDateTimeString.getLength()] = 0; + xmlXPathReturnString(ctxt, pString); +} + +static bool parseDateTime(std::u16string_view aString, DateTime& aDateTime) +{ + // take apart a canonical literal xsd:dateTime string + //CCYY-MM-DDThh:mm:ss(Z) + + OUString aDateTimeString( o3tl::trim(aString) ); + + // check length + if (aDateTimeString.getLength() < 19 || aDateTimeString.getLength() > 20) + return false; + + sal_Int32 nIndex = 0; + sal_Int32 nYear = o3tl::toInt32(o3tl::getToken(aDateTimeString, 0, '-', nIndex)); + sal_Int32 nMonth = o3tl::toInt32(o3tl::getToken(aDateTimeString, 0, '-', nIndex)); + sal_Int32 nDay = o3tl::toInt32(o3tl::getToken(aDateTimeString, 0, 'T', nIndex)); + sal_Int32 nHour = o3tl::toInt32(o3tl::getToken(aDateTimeString, 0, ':', nIndex)); + sal_Int32 nMinute = o3tl::toInt32(o3tl::getToken(aDateTimeString, 0, ':', nIndex)); + sal_Int32 nSecond = o3tl::toInt32(o3tl::getToken(aDateTimeString, 0, 'Z', nIndex)); + + Date tmpDate(static_cast<sal_uInt16>(nDay), static_cast<sal_uInt16>(nMonth), static_cast<sal_uInt16>(nYear)); + tools::Time tmpTime(nHour, nMinute, nSecond); + DateTime tmpDateTime(tmpDate, tmpTime); + if (aString.rfind('Z') == std::u16string_view::npos) + tmpDateTime.ConvertToUTC(); + + aDateTime = tmpDateTime; + + return true; +} + + +void xforms_daysFromDateFunction(xmlXPathParserContextPtr ctxt, int nargs) +{ + // number of days from 1970-01-01 to supplied xsd:date(Time) + + if (nargs != 1) XP_ERROR(XPATH_INVALID_ARITY); + xmlChar* pString = xmlXPathPopString(ctxt); + if (xmlXPathCheckError(ctxt)) XP_ERROR(XPATH_INVALID_TYPE); + OUString aString(reinterpret_cast<char*>(pString), strlen(reinterpret_cast<char*>(pString)), RTL_TEXTENCODING_UTF8); + + DateTime aDateTime( DateTime::EMPTY ); + if (parseDateTime(aString, aDateTime)) + { + Date aReferenceDate(1, 1, 1970); + sal_Int32 nDays = aDateTime - aReferenceDate; + xmlXPathReturnNumber(ctxt, nDays); + } + else + xmlXPathReturnNumber(ctxt, xmlXPathNAN); + + +} + + +void xforms_secondsFromDateTimeFunction(xmlXPathParserContextPtr ctxt, int nargs) +{ + // number of seconds from 1970-01-01T00:00:00Z to supplied xsd:date(Time) + + if (nargs != 1) XP_ERROR(XPATH_INVALID_ARITY); + xmlChar* pString = xmlXPathPopString(ctxt); + if (xmlXPathCheckError(ctxt)) XP_ERROR(XPATH_INVALID_TYPE); + OUString aString(reinterpret_cast<char*>(pString), strlen(reinterpret_cast<char*>(pString)), RTL_TEXTENCODING_UTF8); + + DateTime aDateTime( DateTime::EMPTY ); + + if (parseDateTime(aString, aDateTime)) + { + Date aReferenceDate(1, 1, 1970); + sal_Int32 nDays = aDateTime - aReferenceDate; + sal_Int32 nSeconds = nDays * 24 * 60 * 60; + nSeconds += aDateTime.GetHour() * 60 * 60; + nSeconds += aDateTime.GetMin() * 60; + nSeconds += aDateTime.GetSec(); + xmlXPathReturnNumber(ctxt, nSeconds); + } + else + xmlXPathReturnNumber(ctxt, xmlXPathNAN); + +} + +static bool parseDuration(const xmlChar* aString, bool& bNegative, sal_Int32& nYears, sal_Int32& nMonth, sal_Int32& nDays, + sal_Int32& nHours, sal_Int32& nMinutes, sal_Int32& nSeconds) +{ + bool bTime = false; // in part after T + const xmlChar *pString = aString; + + if (pString[0] == '-') { + bNegative = true; + pString++; + } + + if (pString[0] != 'P') + { + return false; + } + + pString++; + const xmlChar* pToken = pString; + while(pToken[0] != 0) + { + switch(pToken[0]) { + case 'Y': + nYears = boost::lexical_cast<sal_Int32>(pString, pString-pToken); + pString = ++pToken; + break; + case 'M': + if (!bTime) + nMonth = boost::lexical_cast<sal_Int32>(pString, pString-pToken); + else + nMinutes = boost::lexical_cast<sal_Int32>(pString, pString-pToken); + pString = ++pToken; + break; + case 'D': + nDays = boost::lexical_cast<sal_Int32>(pString, pString-pToken); + pString = ++pToken; + break; + case 'H': + nHours = boost::lexical_cast<sal_Int32>(pString, pString-pToken); + pString = ++pToken; + break; + case 'S': + nSeconds = boost::lexical_cast<sal_Int32>(pString, pString-pToken); + pString = ++pToken; + break; + case 'T': + bTime = true; + pString = ++pToken; + break; + default: + pToken++; + } + } + return true; +} + +void xforms_secondsFunction(xmlXPathParserContextPtr ctxt, int nargs) +{ + // convert a xsd:duration to seconds + // (-)PnYnMnDTnHnMnS + if (nargs != 1) XP_ERROR(XPATH_INVALID_ARITY); + xmlChar* pString = xmlXPathPopString(ctxt); + if (xmlXPathCheckError(ctxt)) XP_ERROR(XPATH_INVALID_TYPE); + + bool bNegative = false; + sal_Int32 nYears = 0; + sal_Int32 nMonths = 0; + sal_Int32 nDays = 0; + sal_Int32 nHours = 0; + sal_Int32 nMinutes = 0; + sal_Int32 nSeconds = 0; + + if (parseDuration(pString, bNegative, nYears, nMonths, nDays, nHours, nMinutes, nSeconds)) + { + nSeconds += nMinutes*60; + nSeconds += nHours*60*60; + nSeconds += nDays*24*60*60; + // year and month are ignored according to spec + if (bNegative) + nSeconds = 0 - nSeconds; + xmlXPathReturnNumber(ctxt, nSeconds); + } + else + xmlXPathReturnNumber(ctxt, xmlXPathNAN); +} + +void xforms_monthsFunction(xmlXPathParserContextPtr ctxt, int nargs) +{ + // convert a xsd:duration to seconds + // (-)PnYnMnDTnHnMnS + if (nargs != 1) XP_ERROR(XPATH_INVALID_ARITY); + xmlChar* pString = xmlXPathPopString(ctxt); + if (xmlXPathCheckError(ctxt)) XP_ERROR(XPATH_INVALID_TYPE); + + bool bNegative = false; + sal_Int32 nYears = 0; + sal_Int32 nMonths = 0; + sal_Int32 nDays = 0; + sal_Int32 nHours = 0; + sal_Int32 nMinutes = 0; + sal_Int32 nSeconds = 0; + + if (parseDuration(pString, bNegative, nYears, nMonths, nDays, nHours, nMinutes, nSeconds)) + { + nMonths += nYears*12; + // Days, Hours, Minutes and seconds are ignored, see spec + if (bNegative) + nMonths = 0 - nMonths; + xmlXPathReturnNumber(ctxt, nMonths); + } + else + xmlXPathReturnNumber(ctxt, xmlXPathNAN); + +} + +// Node-set Functions +void xforms_instanceFunction(xmlXPathParserContextPtr ctxt, int nargs) +{ + if (nargs != 1) XP_ERROR(XPATH_INVALID_ARITY); + xmlChar *pString = xmlXPathPopString(ctxt); + if (xmlXPathCheckError(ctxt)) XP_ERROR(XPATH_INVALID_TYPE); + OUString aString(reinterpret_cast<char*>(pString), strlen(reinterpret_cast<char*>(pString)), RTL_TEXTENCODING_UTF8); + + Reference< XModel > aModel = static_cast<CLibxml2XFormsExtension*>(ctxt->context->funcLookupData)->getModel(); + if (aModel.is()) + { + Reference< XDocument > aInstance = aModel->getInstanceDocument(aString); + if (aInstance.is()) + { + try { + // xmlXPathObjectPtr xmlXPathNewNodeSet (xmlNodePtr val); + Reference< XUnoTunnel > aTunnel(aInstance, UNO_QUERY_THROW); + xmlNodePtr pNode = comphelper::getSomething_cast<xmlNode>(aTunnel->getSomething(Sequence<sal_Int8>())); + xmlXPathObjectPtr pObject = xmlXPathNewNodeSet(pNode); + xmlXPathReturnNodeSet(ctxt, pObject->nodesetval); + } catch (const RuntimeException&) + { + xmlXPathReturnEmptyNodeSet(ctxt); + } + } + else + xmlXPathReturnEmptyNodeSet(ctxt); + } + else + xmlXPathReturnEmptyNodeSet(ctxt); + +} + +// Node-set Functions, XForms 1.1 +void xforms_currentFunction(xmlXPathParserContextPtr ctxt, int nargs) +{ + if (nargs != 0) XP_ERROR(XPATH_INVALID_ARITY); + + Reference< XNode > aNode = static_cast<CLibxml2XFormsExtension*>(ctxt->context->funcLookupData)->getContextNode(); + + if (aNode.is()) + { + try { + Reference< XUnoTunnel > aTunnel(aNode, UNO_QUERY_THROW); + xmlNodePtr pNode = comphelper::getSomething_cast<xmlNode>(aTunnel->getSomething(Sequence<sal_Int8>())); + xmlXPathObjectPtr pObject = xmlXPathNewNodeSet(pNode); + xmlXPathReturnNodeSet(ctxt, pObject->nodesetval); + } + catch (const RuntimeException&) + { + xmlXPathReturnEmptyNodeSet(ctxt); + } + } + else + xmlXPathReturnEmptyNodeSet(ctxt); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/forms/source/xforms/xpathlib/xpathlib.hxx b/forms/source/xforms/xpathlib/xpathlib.hxx new file mode 100644 index 000000000..52a477c97 --- /dev/null +++ b/forms/source/xforms/xpathlib/xpathlib.hxx @@ -0,0 +1,74 @@ +/* -*- 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 . + */ + +#pragma once + +#include <libxml/xpath.h> + +/* + entry functions for libxml xpath engine + +*/ + +/** + * xmlXPathParserContext: + * + * An XPath parser context. It contains pure parsing information, + * an xmlXPathContext, and the stack of objects. + */ + +extern "C" +{ + +// XForms + +// XForms Core Functions +// boolean functions +void xforms_booleanFromStringFunction(xmlXPathParserContextPtr ctxt, int nargs); +void xforms_ifFunction(xmlXPathParserContextPtr ctxt, int nargs); + +// Number Functions +void xforms_avgFunction(xmlXPathParserContextPtr ctxt, int nargs); +void xforms_minFunction(xmlXPathParserContextPtr ctxt, int nargs); +void xforms_maxFunction(xmlXPathParserContextPtr ctxt, int nargs); +void xforms_countNonEmptyFunction(xmlXPathParserContextPtr ctxt, int nargs); +void xforms_indexFunction(xmlXPathParserContextPtr ctxt, int nargs); + +// String Functions +void xforms_propertyFunction(xmlXPathParserContextPtr ctxt, int nargs); + +// Date and Time Functions +void xforms_nowFunction(xmlXPathParserContextPtr ctxt, int nargs); +void xforms_daysFromDateFunction(xmlXPathParserContextPtr ctxt, int nargs); +void xforms_secondsFromDateTimeFunction(xmlXPathParserContextPtr ctxt, int nargs); +void xforms_secondsFunction(xmlXPathParserContextPtr ctxt, int nargs); +void xforms_monthsFunction(xmlXPathParserContextPtr ctxt, int nargs); + +// Node-set Functions +void xforms_instanceFunction(xmlXPathParserContextPtr ctxt, int nargs); + +// Node-set Functions; XForms 1.1 +void xforms_currentFunction(xmlXPathParserContextPtr ctxt, int nargs); + +// --- lookup --- +xmlXPathFunction xforms_lookupFunc(void *ctxt, const xmlChar *name, const xmlChar *ns_uri); + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |