Update git submodules
[LibreOffice.git] / forms / source / xforms / model.cxx
blob105f7f449963b5500e342653ad2908bc4e4d7215
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
21 #include "model.hxx"
23 #include "model_helper.hxx"
24 #include "unohelper.hxx"
25 #include "binding.hxx"
26 #include "submission.hxx"
27 #include "mip.hxx"
28 #include "evaluationcontext.hxx"
29 #include "xmlhelper.hxx"
30 #include "datatyperepository.hxx"
31 #include "NameContainer.hxx"
33 #include <rtl/ustring.hxx>
34 #include <rtl/ustrbuf.hxx>
35 #include <tools/debug.hxx>
37 #include <comphelper/processfactory.hxx>
38 #include <comphelper/servicehelper.hxx>
39 #include <cppuhelper/supportsservice.hxx>
41 #include <algorithm>
43 // UNO classes
44 #include <com/sun/star/lang/IndexOutOfBoundsException.hpp>
45 #include <com/sun/star/xml/dom/XDocument.hpp>
46 #include <com/sun/star/xml/dom/XCharacterData.hpp>
47 #include <com/sun/star/xml/dom/NodeType.hpp>
48 #include <com/sun/star/xml/dom/XDocumentBuilder.hpp>
49 #include <com/sun/star/uno/Sequence.hxx>
50 #include <com/sun/star/beans/PropertyValue.hpp>
51 #include <com/sun/star/ucb/SimpleFileAccess.hpp>
52 #include <com/sun/star/io/XInputStream.hpp>
55 using com::sun::star::beans::XPropertySet;
56 using com::sun::star::beans::PropertyValue;
57 using com::sun::star::ucb::SimpleFileAccess;
58 using com::sun::star::io::XInputStream;
60 using namespace com::sun::star::uno;
61 using namespace com::sun::star::xml::dom;
62 using namespace xforms;
65 #if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG
66 #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);
68 #define DBG_INVARIANT() DBG_INVARIANT_TYPE(Model)
69 #else
70 #define DBG_INVARIANT()
71 #endif
74 // The Model
77 void Model::ensureAtLeastOneInstance()
79 if( ! mxInstances->hasItems() )
81 // create a default instance
82 newInstance( OUString(), OUString(), true );
87 /** Model default constructor; create empty model */
88 Model::Model() :
89 mxInstances( new InstanceCollection ),
90 mxNamespaces( new NameContainer<OUString>() ),
91 mbInitialized( false ),
92 mbExternalData( true )
94 initializePropertySet();
96 // initialize bindings collections
97 // (not in initializer list to avoid use of incomplete 'this')
98 mxBindings = new BindingCollection( this );
99 mxSubmissions = new SubmissionCollection( this );
101 // invariant only holds after construction
102 DBG_INVARIANT();
105 Model::~Model() noexcept
109 EvaluationContext Model::getEvaluationContext()
111 // the default context is the top-level element node. A default
112 // node (instanceData' is inserted when there is no default node
113 Reference<XDocument> xInstance = getDefaultInstance();
114 Reference<XNode> xElement = xInstance->getDocumentElement();
116 // no element found? Then insert default element 'instanceData'
117 if( ! xElement.is() )
119 xElement.set( xInstance->createElement( u"instanceData"_ustr ), UNO_QUERY_THROW );
120 xInstance->appendChild( xElement );
123 OSL_ENSURE( xElement.is() &&
124 xElement->getNodeType() == NodeType_ELEMENT_NODE,
125 "no element in evaluation context" );
127 return EvaluationContext( xElement, this, mxNamespaces );
131 void Model::setForeignSchema( const css::uno::Reference<css::xml::dom::XDocument>& rDocument )
133 mxForeignSchema = rDocument;
137 void Model::setSchemaRef( const OUString& rSchemaRef )
139 msSchemaRef = rSchemaRef;
143 void Model::setNamespaces( const css::uno::Reference<css::container::XNameContainer>& rNamespaces )
145 if( rNamespaces.is() )
146 mxNamespaces = rNamespaces;
150 void Model::setExternalData( bool _bData )
152 mbExternalData = _bData;
155 #if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG
156 void Model::dbg_assertInvariant() const
158 assert(mxInstances && "no instances found");
159 assert(mxBindings && "no bindings element");
160 assert(mxSubmissions && "no submissions element");
162 #endif
165 // MIP management
166 void Model::addMIP( void* pTag, const XNode_t& xNode, const MIP& rMIP )
168 OSL_ENSURE( pTag != nullptr, "empty tag?" );
169 OSL_ENSURE( xNode.is(), "no node" );
171 MIPs_t::value_type aValue( xNode, ::std::pair<void*,MIP>( pTag, rMIP ) );
172 maMIPs.insert( aValue );
175 void Model::removeMIPs( void const * pTag )
177 OSL_ENSURE( pTag != nullptr, "empty tag?" );
179 for( MIPs_t::iterator aIter = maMIPs.begin();
180 aIter != maMIPs.end(); )
182 if( aIter->second.first == pTag )
184 aIter = maMIPs.erase( aIter );
186 else
187 ++aIter;
191 MIP Model::queryMIP( const XNode_t& xNode ) const
193 // travel up inheritance chain and inherit MIPs
194 MIP aRet;
195 for( XNode_t xCurrent = xNode;
196 xCurrent.is();
197 xCurrent = xCurrent->getParentNode() )
199 // iterate over all MIPs for this node, and join MIPs
200 MIP aMIP;
201 MIPs_t::const_iterator aEnd = maMIPs.upper_bound( xCurrent );
202 MIPs_t::const_iterator aIter = maMIPs.lower_bound( xCurrent );
203 for( ; aIter != aEnd; ++aIter )
204 aMIP.join( aIter->second.second );
206 // inherit from current node (or set if we are at the start node)
207 if( xCurrent == xNode )
208 aRet = std::move(aMIP);
209 else
210 aRet.inherit( aMIP );
213 return aRet;
217 void Model::rebind()
219 OSL_ENSURE( mxBindings, "bindings?" );
221 // iterate over all bindings and call update
222 sal_Int32 nCount = mxBindings->countItems();
223 for( sal_Int32 i = 0; i < nCount; i++ )
225 Binding* pBind = comphelper::getFromUnoTunnel<Binding>( mxBindings->Collection<XPropertySet_t>::getItem( i ) );
226 OSL_ENSURE( pBind != nullptr, "binding?" );
227 pBind->update();
232 void Model::deferNotifications( bool bDefer )
234 // iterate over all bindings and defer notifications
235 sal_Int32 nCount = mxBindings->countItems();
236 for( sal_Int32 i = 0; i < nCount; i++ )
238 Binding* pBind = comphelper::getFromUnoTunnel<Binding>( mxBindings->Collection<XPropertySet_t>::getItem( i ) );
239 OSL_ENSURE( pBind != nullptr, "binding?" );
240 pBind->deferNotifications( bDefer );
245 bool Model::setSimpleContent( const XNode_t& xConstNode,
246 const OUString& sValue )
248 OSL_ENSURE( xConstNode.is(), "need node to set data" );
250 bool bRet = false;
251 if( xConstNode.is() )
253 // non-const node reference so we can assign children (if necessary)
254 XNode_t xNode( xConstNode );
256 switch( xNode->getNodeType() )
258 case NodeType_ELEMENT_NODE:
260 // find first text node child
261 Reference<XNode> xChild;
262 for( xChild = xNode->getFirstChild();
263 xChild.is() && xChild->getNodeType() != NodeType_TEXT_NODE;
264 xChild = xChild->getNextSibling() )
265 ; // empty loop; only find first text node child
267 // create text node, if none is found
268 if( ! xChild.is() )
270 xChild.set(
271 xNode->getOwnerDocument()->createTextNode( OUString() ),
272 UNO_QUERY_THROW );
273 xNode->appendChild( xChild );
275 xNode = std::move(xChild);
277 OSL_ENSURE( xNode.is() &&
278 xNode->getNodeType() == NodeType_TEXT_NODE,
279 "text node creation failed?" );
280 [[fallthrough]]; // continue as with text node:
283 case NodeType_TEXT_NODE:
284 case NodeType_ATTRIBUTE_NODE:
286 // set the node value (defer notifications)
287 if( xNode->getNodeValue() != sValue )
289 deferNotifications( true );
290 xNode->setNodeValue( sValue );
291 deferNotifications( false );
293 bRet = true;
295 break;
297 default:
299 OSL_FAIL( "bound to unknown node type?" );
301 break;
305 return bRet;
308 void Model::loadInstance( sal_Int32 nInstance )
310 Sequence<PropertyValue> aSequence = mxInstances->getItem( nInstance );
312 // find URL from instance
313 OUString sURL;
314 bool bOnce = false;
315 getInstanceData( aSequence, nullptr, nullptr, &sURL, &bOnce );
317 // if we have a URL, load the document and set it into the instance
318 if( sURL.isEmpty() )
319 return;
323 Reference<XInputStream> xInput =
324 SimpleFileAccess::create( ::comphelper::getProcessComponentContext() )->openFileRead( sURL );
325 if( xInput.is() )
327 Reference<XDocument> xInstance =
328 getDocumentBuilder()->parse( xInput );
329 if( xInstance.is() )
331 OUString sEmpty;
332 setInstanceData( aSequence, nullptr, &xInstance,
333 bOnce ? &sEmpty : &sURL, nullptr);
334 mxInstances->setItem( nInstance, aSequence );
338 catch( const Exception& )
340 // couldn't load the instance -> ignore!
344 void Model::loadInstances()
346 // iterate over instance array to get PropertyValue-Sequence
347 const sal_Int32 nInstances = mxInstances->countItems();
348 for( sal_Int32 nInstance = 0; nInstance < nInstances; nInstance++ )
350 loadInstance( nInstance );
355 bool Model::isValid() const
357 bool bValid = true;
358 sal_Int32 nCount = mxBindings->countItems();
359 for( sal_Int32 i = 0; bValid && i < nCount; i++ )
361 Binding* pBind = comphelper::getFromUnoTunnel<Binding>( mxBindings->Collection<XPropertySet_t>::getItem( i ) );
362 assert(pBind != nullptr && "binding?");
363 bValid = pBind->isValid();
365 return bValid;
369 // implement xforms::XModel
372 OUString Model::getID()
374 DBG_INVARIANT();
375 return msID;
378 void Model::setID( const OUString& sID )
380 DBG_INVARIANT();
381 msID = sID;
384 void Model::initialize()
386 DBG_ASSERT( ! mbInitialized, "model already initialized" );
388 // load instances
389 loadInstances();
391 // let's pretend we're initialized and rebind all bindings
392 mbInitialized = true;
393 rebind();
396 void Model::rebuild()
398 if( ! mbInitialized )
399 initialize();
400 else
401 rebind();
404 void Model::recalculate()
406 rebind();
409 void Model::revalidate()
411 // do nothing. We don't validate anyways!
414 void Model::refresh()
416 rebind();
420 void SAL_CALL Model::submitWithInteraction(
421 const OUString& sID,
422 const css::uno::Reference<css::task::XInteractionHandler>& _rxHandler )
424 DBG_INVARIANT();
426 if( mxSubmissions->hasItem( sID ) )
428 Submission* pSubmission =
429 dynamic_cast<Submission*>( mxSubmissions->getItem( sID ).get() );
430 assert(pSubmission && "no submission?");
431 OSL_ENSURE( pSubmission->getModelImpl() == this,
432 "wrong model" );
434 // submit. All exceptions are allowed to leave.
435 pSubmission->submitWithInteraction( _rxHandler );
439 void Model::submit( const OUString& sID )
441 submitWithInteraction( sID, nullptr );
444 css::uno::Reference<css::xforms::XDataTypeRepository> SAL_CALL Model::getDataTypeRepository( )
446 if ( !mxDataTypes.is() )
447 mxDataTypes = new ODataTypeRepository;
449 return mxDataTypes;
453 // instance management
456 css::uno::Reference<css::container::XSet> Model::getInstances()
458 return mxInstances;
461 css::uno::Reference<css::xml::dom::XDocument> Model::getInstanceDocument( const OUString& rName )
463 ensureAtLeastOneInstance();
464 Reference<XDocument> aInstance;
465 sal_Int32 nInstance = lcl_findInstance( mxInstances.get(), rName );
466 if( nInstance != -1 )
467 getInstanceData( mxInstances->getItem( nInstance ),
468 nullptr, &aInstance, nullptr, nullptr );
469 return aInstance;
472 css::uno::Reference<css::xml::dom::XDocument> SAL_CALL Model::getDefaultInstance()
474 ensureAtLeastOneInstance();
475 DBG_ASSERT( mxInstances->countItems() > 0, "no instance?" );
476 Reference<XDocument> aInstance;
477 getInstanceData( mxInstances->getItem( 0 ), nullptr, &aInstance, nullptr, nullptr );
478 return aInstance;
482 // bindings management
485 css::uno::Reference<css::beans::XPropertySet> SAL_CALL Model::createBinding()
487 DBG_INVARIANT();
488 return new Binding();
491 css::uno::Reference<css::beans::XPropertySet> Model::cloneBinding( const css::uno::Reference<css::beans::XPropertySet>& xBinding )
493 DBG_INVARIANT();
494 XPropertySet_t xNewBinding = createBinding();
495 copy( xBinding, xNewBinding );
496 return xNewBinding;
499 css::uno::Reference<css::beans::XPropertySet> Model::getBinding( const OUString& sId )
501 DBG_INVARIANT();
502 return mxBindings->hasItem( sId ) ? mxBindings->getItem( sId ) : nullptr;
505 css::uno::Reference<css::container::XSet> Model::getBindings()
507 DBG_INVARIANT();
508 return mxBindings;
512 // submission management
515 css::uno::Reference<css::xforms::XSubmission> Model::createSubmission()
517 DBG_INVARIANT();
518 return new Submission();
521 css::uno::Reference<css::xforms::XSubmission> Model::cloneSubmission(const css::uno::Reference<css::beans::XPropertySet>& xSubmission)
523 DBG_INVARIANT();
524 css::uno::Reference<css::xforms::XSubmission> xNewSubmission = createSubmission();
525 XPropertySet_t xAsPropertySet( xNewSubmission );
526 copy( xSubmission, xAsPropertySet );
527 return xNewSubmission;
530 css::uno::Reference<css::xforms::XSubmission> Model::getSubmission( const OUString& sId )
532 DBG_INVARIANT();
533 css::uno::Reference<css::xforms::XSubmission> xSubmission;
534 if ( mxSubmissions->hasItem( sId ) )
535 xSubmission.set(mxSubmissions->getItem( sId ), css::uno::UNO_QUERY);
536 return xSubmission;
539 css::uno::Reference<css::container::XSet> Model::getSubmissions()
541 DBG_INVARIANT();
542 return mxSubmissions;
546 // implement XPropertySet & friends
549 #define HANDLE_ID 0
550 #define HANDLE_ForeignSchema 3
551 #define HANDLE_SchemaRef 4
552 #define HANDLE_Namespaces 5
553 #define HANDLE_ExternalData 6
555 void Model::initializePropertySet()
557 registerProperty( css::beans::Property(u"ID"_ustr, HANDLE_ID, cppu::UnoType<OUString>::get(), css::beans::PropertyAttribute::BOUND ),
558 new APIPropertyAccessor< Model, OUString >(this, &Model::setID, &Model::getID) );
559 registerProperty( css::beans::Property(u"ForeignSchema"_ustr, HANDLE_ForeignSchema, cppu::UnoType<css::uno::Reference<css::xml::dom::XDocument>>::get(), css::beans::PropertyAttribute::BOUND ),
560 new DirectPropertyAccessor< Model, css::uno::Reference<css::xml::dom::XDocument> >( this, &Model::setForeignSchema, &Model::getForeignSchema) );
562 registerProperty( css::beans::Property(u"SchemaRef"_ustr, HANDLE_SchemaRef, cppu::UnoType<OUString>::get(), css::beans::PropertyAttribute::BOUND ),
563 new DirectPropertyAccessor< Model, OUString >( this, &Model::setSchemaRef, &Model::getSchemaRef) );
565 registerProperty( css::beans::Property(u"Namespaces"_ustr, HANDLE_Namespaces, cppu::UnoType<css::uno::Reference<css::container::XNameContainer>>::get(), css::beans::PropertyAttribute::BOUND ),
566 new DirectPropertyAccessor< Model, css::uno::Reference<css::container::XNameContainer> >( this, &Model::setNamespaces, &Model::getNamespaces) );
568 registerProperty( css::beans::Property(u"ExternalData"_ustr, HANDLE_ExternalData, cppu::UnoType<sal_Bool>::get(), css::beans::PropertyAttribute::BOUND ),
569 new BooleanPropertyAccessor< Model >( this, &Model::setExternalData, &Model::getExternalData ) );
572 void Model::update()
574 rebuild();
578 Sequence<sal_Int8> Model::getImplementationId()
580 return css::uno::Sequence<sal_Int8>();
583 OUString Model::getImplementationName()
585 return u"com.sun.star.form.Model"_ustr;
588 sal_Bool Model::supportsService(OUString const & ServiceName)
590 return cppu::supportsService(this, ServiceName);
593 css::uno::Sequence<OUString> Model::getSupportedServiceNames()
595 return {u"com.sun.star.xforms.Model"_ustr};
598 extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
599 com_sun_star_form_Model_get_implementation(css::uno::XComponentContext*,
600 css::uno::Sequence<css::uno::Any> const &)
602 return cppu::acquire(new xforms::Model());
606 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */