/* -*- 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 #include #include #include #include #include #include #include // UNO classes #include #include #include #include #include #include #include #include #include using com::sun::star::lang::XUnoTunnel; using com::sun::star::beans::XPropertySet; using com::sun::star::beans::PropertyValue; using com::sun::star::lang::WrappedTargetException; using com::sun::star::ucb::XSimpleFileAccess3; 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 #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() : msID(), mxInstances( new InstanceCollection ), mxNamespaces( new NameContainer() ), 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() throw() { } static Model* lcl_getModel( const Reference& xTunnel ) { Model* pModel = nullptr; if( xTunnel.is() ) pModel = reinterpret_cast( xTunnel->getSomething( Model::getUnoTunnelID() ) ); return pModel; } Model* Model::getModel( const Reference& xModel ) { return lcl_getModel( Reference( xModel, UNO_QUERY ) ); } 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 xInstance = getDefaultInstance(); Reference xElement( xInstance->getDocumentElement(), UNO_QUERY ); // 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 Model::getUnoTunnelID() { static cppu::OImplementationId aImplementationId; return aImplementationId.getImplementationId(); } void Model::setForeignSchema( const css::uno::Reference& rDocument ) { mxForeignSchema = rDocument; } void Model::setSchemaRef( const OUString& rSchemaRef ) { msSchemaRef = rSchemaRef; } void Model::setNamespaces( const css::uno::Reference& rNamespaces ) { if( rNamespaces.is() ) mxNamespaces = rNamespaces; } void Model::setExternalData( bool _bData ) { mbExternalData = _bData; } #if OSL_DEBUG_LEVEL > 0 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( 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 = Binding::getBinding( mxBindings->Collection::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 = Binding::getBinding( mxBindings->Collection::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 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?" ); SAL_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 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() ) { try { Reference xInput = Reference( SimpleFileAccess::create( ::comphelper::getProcessComponentContext() ) )->openFileRead( sURL ); if( xInput.is() ) { Reference 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 = Binding::getBinding( mxBindings->Collection::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& _rxHandler ) { DBG_INVARIANT(); if( mxSubmissions->hasItem( sID ) ) { Submission* pSubmission = Submission::getSubmission( mxSubmissions->getItem( sID ) ); OSL_ENSURE( pSubmission != nullptr, "no submission?" ); OSL_ENSURE( pSubmission->getModel() == Reference( 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 SAL_CALL Model::getDataTypeRepository( ) { if ( !mxDataTypes.is() ) mxDataTypes = new ODataTypeRepository; return mxDataTypes; } // instance management css::uno::Reference Model::getInstances() { return mxInstances.get(); } css::uno::Reference Model::getInstanceDocument( const OUString& rName ) { ensureAtLeastOneInstance(); Reference 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 SAL_CALL Model::getDefaultInstance() { ensureAtLeastOneInstance(); DBG_ASSERT( mxInstances->countItems() > 0, "no instance?" ); Reference aInstance; getInstanceData( mxInstances->getItem( 0 ), nullptr, &aInstance, nullptr, nullptr ); return aInstance; } // bindings management Model::XPropertySet_t SAL_CALL Model::createBinding() { DBG_INVARIANT(); return new Binding(); } Model::XPropertySet_t Model::cloneBinding( const XPropertySet_t& xBinding ) { DBG_INVARIANT(); XPropertySet_t xNewBinding = createBinding(); copy( xBinding, xNewBinding ); return xNewBinding; } Model::XPropertySet_t Model::getBinding( const OUString& sId ) { DBG_INVARIANT(); return mxBindings->hasItem( sId ) ? mxBindings->getItem( sId ) : nullptr; } css::uno::Reference Model::getBindings() { DBG_INVARIANT(); return mxBindings.get(); } // submission management css::uno::Reference Model::createSubmission() { DBG_INVARIANT(); return new Submission(); } css::uno::Reference Model::cloneSubmission(const XPropertySet_t& xSubmission) { DBG_INVARIANT(); css::uno::Reference xNewSubmission = createSubmission(); XPropertySet_t xAsPropertySet( xNewSubmission.get() ); copy( xSubmission.get(), xAsPropertySet ); return xNewSubmission; } css::uno::Reference Model::getSubmission( const OUString& sId ) { DBG_INVARIANT(); css::uno::Reference xSubmission; if ( mxSubmissions->hasItem( sId ) ) xSubmission.set(mxSubmissions->getItem( sId ), css::uno::UNO_QUERY); return xSubmission; } css::uno::Reference Model::getSubmissions() { DBG_INVARIANT(); return mxSubmissions.get(); } // implement XPropertySet & friends #define HANDLE_ID 0 #define HANDLE_ForeignSchema 3 #define HANDLE_SchemaRef 4 #define HANDLE_Namespaces 5 #define HANDLE_ExternalData 6 #define REGISTER_PROPERTY( property, type ) \ registerProperty( PROPERTY( property, type ), \ new DirectPropertyAccessor< Model, type >( this, &Model::set##property, &Model::get##property ) ); #define REGISTER_PROPERTY_API( property, type ) \ registerProperty( PROPERTY( property, type ), \ new APIPropertyAccessor< Model, type >( this, &Model::set##property, &Model::get##property ) ); #define REGISTER_BOOL_PROPERTY( property ) \ registerProperty( PROPERTY( property, sal_Bool ), \ new BooleanPropertyAccessor< Model >( this, &Model::set##property, &Model::get##property ) ); void Model::initializePropertySet() { REGISTER_PROPERTY_API ( ID, OUString ); REGISTER_PROPERTY ( ForeignSchema, css::uno::Reference ); REGISTER_PROPERTY ( SchemaRef, OUString ); REGISTER_PROPERTY ( Namespaces, css::uno::Reference ); REGISTER_BOOL_PROPERTY( ExternalData ); } void Model::update() { rebuild(); } sal_Int64 Model::getSomething( const css::uno::Sequence& xId ) { return reinterpret_cast( ( xId == getUnoTunnelID() ) ? this : nullptr ); } Sequence Model::getImplementationId() { return css::uno::Sequence(); } OUString Model::getImplementationName() { return OUString("com.sun.star.form.Model"); } sal_Bool Model::supportsService(OUString const & ServiceName) { return cppu::supportsService(this, ServiceName); } css::uno::Sequence Model::getSupportedServiceNames() { return css::uno::Sequence{"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 const &) { return cppu::acquire(new xforms::Model()); } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */