Update git submodules
[LibreOffice.git] / svx / source / xml / xmleohlp.cxx
blobe3164e4b838c5220dfec12600155b6baeec68309
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 <com/sun/star/io/XStream.hpp>
22 #include <com/sun/star/beans/XPropertySet.hpp>
23 #include <com/sun/star/embed/XTransactedObject.hpp>
24 #include <com/sun/star/embed/ElementModes.hpp>
25 #include <com/sun/star/embed/XEmbeddedObject.hpp>
26 #include <com/sun/star/embed/XEmbedPersist.hpp>
27 #include <com/sun/star/embed/EmbedStates.hpp>
28 #include <com/sun/star/embed/Aspects.hpp>
29 #include <com/sun/star/lang/WrappedTargetRuntimeException.hpp>
30 #include <osl/diagnose.h>
31 #include <sot/storage.hxx>
32 #include <tools/debug.hxx>
33 #include <sal/log.hxx>
34 #include <unotools/streamwrap.hxx>
35 #include <unotools/tempfile.hxx>
37 #include <svtools/embedhlp.hxx>
38 #include <unotools/ucbstreamhelper.hxx>
39 #include <comphelper/propertyvalue.hxx>
40 #include <comphelper/storagehelper.hxx>
41 #include <comphelper/embeddedobjectcontainer.hxx>
43 #include <comphelper/fileformat.h>
44 #include <cppuhelper/exc_hlp.hxx>
45 #include <cppuhelper/implbase.hxx>
46 #include <svx/xmleohlp.hxx>
47 #include <map>
48 #include <memory>
49 #include <mutex>
51 using namespace ::cppu;
52 using namespace ::utl;
53 using namespace ::com::sun::star;
54 using namespace ::com::sun::star::document;
55 using namespace ::com::sun::star::uno;
56 using namespace ::com::sun::star::container;
57 using namespace ::com::sun::star::io;
58 using namespace ::com::sun::star::lang;
60 constexpr OUStringLiteral XML_CONTAINERSTORAGE_NAME_60 = u"Pictures";
61 constexpr OUStringLiteral XML_CONTAINERSTORAGE_NAME = u"ObjectReplacements";
62 constexpr OUString XML_EMBEDDEDOBJECT_URL_BASE = u"vnd.sun.star.EmbeddedObject:"_ustr;
63 constexpr OUStringLiteral XML_EMBEDDEDOBJECTGRAPHIC_URL_BASE = u"vnd.sun.star.GraphicObject:";
66 class OutputStorageWrapper_Impl : public ::cppu::WeakImplHelper<XOutputStream>
68 std::mutex maMutex;
69 rtl::Reference < OOutputStreamWrapper > xOut;
70 TempFileFast aTempFile;
71 bool bStreamClosed : 1;
72 SvStream* pStream;
74 public:
75 OutputStorageWrapper_Impl();
77 // css::io::XOutputStream
78 virtual void SAL_CALL writeBytes(const Sequence< sal_Int8 >& aData) override;
79 virtual void SAL_CALL flush() override;
80 virtual void SAL_CALL closeOutput() override;
82 SvStream* GetStream();
85 OutputStorageWrapper_Impl::OutputStorageWrapper_Impl()
86 : bStreamClosed( false )
87 , pStream(nullptr)
89 pStream = aTempFile.GetStream( StreamMode::READWRITE );
90 xOut = new OOutputStreamWrapper( *pStream );
93 SvStream *OutputStorageWrapper_Impl::GetStream()
95 if( bStreamClosed )
96 return pStream;
97 return nullptr;
100 void SAL_CALL OutputStorageWrapper_Impl::writeBytes(
101 const Sequence< sal_Int8 >& aData)
103 std::scoped_lock aGuard( maMutex );
104 xOut->writeBytes( aData );
107 void SAL_CALL OutputStorageWrapper_Impl::flush()
109 std::scoped_lock aGuard( maMutex );
110 xOut->flush();
113 void SAL_CALL OutputStorageWrapper_Impl::closeOutput()
115 std::scoped_lock aGuard( maMutex );
116 xOut->closeOutput();
117 bStreamClosed = true;
120 SvXMLEmbeddedObjectHelper::SvXMLEmbeddedObjectHelper() :
121 mpDocPersist( nullptr ),
122 meCreateMode( SvXMLEmbeddedObjectHelperMode::Read )
126 SvXMLEmbeddedObjectHelper::SvXMLEmbeddedObjectHelper( ::comphelper::IEmbeddedHelper& rDocPersist, SvXMLEmbeddedObjectHelperMode eCreateMode ) :
127 mpDocPersist( nullptr ),
128 meCreateMode( SvXMLEmbeddedObjectHelperMode::Read )
130 Init( nullptr, rDocPersist, eCreateMode );
133 SvXMLEmbeddedObjectHelper::~SvXMLEmbeddedObjectHelper()
137 void SvXMLEmbeddedObjectHelper::disposing(std::unique_lock<std::mutex>&)
139 if( mxTempStorage.is() )
141 mxTempStorage->dispose();
142 mxTempStorage.clear();
146 void SvXMLEmbeddedObjectHelper::splitObjectURL(const OUString& _aURLNoPar,
147 OUString& rContainerStorageName,
148 OUString& rObjectStorageName)
150 DBG_ASSERT(_aURLNoPar.isEmpty() || '#' != _aURLNoPar[0], "invalid object URL" );
151 OUString aURLNoPar = _aURLNoPar;
153 sal_Int32 _nPos = aURLNoPar.lastIndexOf( '/' );
154 if( -1 == _nPos )
156 rContainerStorageName.clear();
157 rObjectStorageName = aURLNoPar;
159 else
161 //eliminate 'superfluous' slashes at start and end
162 //#i103076# load objects with all allowed xlink:href syntaxes
164 //eliminate './' at start
165 sal_Int32 nStart = 0;
166 sal_Int32 nCount = aURLNoPar.getLength();
167 if( aURLNoPar.startsWith( "./" ) )
169 nStart = 2;
170 nCount -= 2;
173 //eliminate '/' at end
174 sal_Int32 nEnd = aURLNoPar.lastIndexOf( '/' );
175 if( nEnd == aURLNoPar.getLength()-1 && nEnd != (nStart-1) )
176 nCount--;
178 aURLNoPar = aURLNoPar.copy( nStart, nCount );
181 _nPos = aURLNoPar.lastIndexOf( '/' );
182 if( _nPos >= 0 )
183 rContainerStorageName = aURLNoPar.copy( 0, _nPos );
184 rObjectStorageName = aURLNoPar.copy( _nPos+1 );
188 bool SvXMLEmbeddedObjectHelper::ImplGetStorageNames(
189 const OUString& rURLStr,
190 OUString& rContainerStorageName,
191 OUString& rObjectStorageName,
192 bool bInternalToExternal,
193 bool *pGraphicRepl,
194 bool *pOasisFormat ) const
196 // internal URL: vnd.sun.star.EmbeddedObject:<object-name>
197 // or: vnd.sun.star.EmbeddedObject:<path>/<object-name>
198 // internal replacement images:
199 // vnd.sun.star.EmbeddedObjectGraphic:<object-name>
200 // or: vnd.sun.star.EmbeddedObjectGraphic:<path>/<object-name>
201 // external URL: ./<path>/<object-name>
202 // or: <path>/<object-name>
203 // or: <object-name>
204 // currently, path may only consist of a single directory name
205 // it is also possible to have additional arguments at the end of URL: <main URL>[?<name>=<value>[,<name>=<value>]*]
207 if( pGraphicRepl )
208 *pGraphicRepl = false;
210 if( pOasisFormat )
211 *pOasisFormat = true; // the default value
213 if( rURLStr.isEmpty() )
214 return false;
216 // get rid of arguments
217 sal_Int32 nPos = rURLStr.indexOf( '?' );
218 OUString aURLNoPar;
219 if ( nPos == -1 )
220 aURLNoPar = rURLStr;
221 else
223 aURLNoPar = rURLStr.copy( 0, nPos );
225 // check the arguments
226 nPos++;
227 while( nPos >= 0 && nPos < rURLStr.getLength() )
229 OUString aToken = rURLStr.getToken( 0, ',', nPos );
230 if ( aToken.equalsIgnoreAsciiCase( "oasis=false" ) )
232 if ( pOasisFormat )
233 *pOasisFormat = false;
234 break;
236 else
238 SAL_WARN( "svx", "invalid arguments was found in URL!" );
243 if( bInternalToExternal )
245 nPos = aURLNoPar.indexOf( ':' );
246 if( -1 == nPos )
247 return false;
248 bool bObjUrl = aURLNoPar.startsWith( XML_EMBEDDEDOBJECT_URL_BASE );
249 bool bGrUrl = !bObjUrl &&
250 aURLNoPar.startsWith( XML_EMBEDDEDOBJECTGRAPHIC_URL_BASE );
251 if( !(bObjUrl || bGrUrl) )
252 return false;
254 sal_Int32 nPathStart = nPos + 1;
255 nPos = aURLNoPar.lastIndexOf( '/' );
256 if( -1 == nPos )
258 rContainerStorageName.clear();
259 rObjectStorageName = aURLNoPar.copy( nPathStart );
261 else if( nPos > nPathStart )
263 rContainerStorageName = aURLNoPar.copy( nPathStart, nPos-nPathStart);
264 rObjectStorageName = aURLNoPar.copy( nPos+1 );
266 else
267 return false;
269 if( bGrUrl )
271 bool bOASIS = mxRootStorage.is() &&
272 ( SotStorage::GetVersion( mxRootStorage ) > SOFFICE_FILEFORMAT_60 );
273 if (bOASIS)
274 rContainerStorageName = XML_CONTAINERSTORAGE_NAME;
275 else
276 rContainerStorageName = XML_CONTAINERSTORAGE_NAME_60;
278 if( pGraphicRepl )
279 *pGraphicRepl = true;
284 else
286 splitObjectURL(aURLNoPar, rContainerStorageName, rObjectStorageName);
289 if( -1 != rContainerStorageName.indexOf( '/' ) )
291 OSL_FAIL( "SvXMLEmbeddedObjectHelper: invalid path name" );
292 return false;
295 return true;
298 uno::Reference < embed::XStorage > const & SvXMLEmbeddedObjectHelper::ImplGetContainerStorage(
299 const OUString& rStorageName )
301 DBG_ASSERT( -1 == rStorageName.indexOf( '/' ) &&
302 -1 == rStorageName.indexOf( '\\' ),
303 "nested embedded storages aren't supported" );
304 if( !mxContainerStorage.is() ||
305 ( rStorageName != maCurContainerStorageName ) )
307 if( mxContainerStorage.is() &&
308 !maCurContainerStorageName.isEmpty() &&
309 SvXMLEmbeddedObjectHelperMode::Write == meCreateMode )
311 uno::Reference < embed::XTransactedObject > xTrans( mxContainerStorage, uno::UNO_QUERY );
312 if ( xTrans.is() )
313 xTrans->commit();
316 if( !rStorageName.isEmpty() && mxRootStorage.is() )
318 sal_Int32 nMode = SvXMLEmbeddedObjectHelperMode::Write == meCreateMode
319 ? ::embed::ElementModes::READWRITE
320 : ::embed::ElementModes::READ;
321 mxContainerStorage = mxRootStorage->openStorageElement( rStorageName,
322 nMode );
324 else
326 mxContainerStorage = mxRootStorage;
328 maCurContainerStorageName = rStorageName;
331 return mxContainerStorage;
334 void SvXMLEmbeddedObjectHelper::ImplReadObject(
335 const OUString& rContainerStorageName,
336 OUString& rObjName,
337 const SvGlobalName *, // pClassId, see "TODO/LATER" below
338 SvStream* pTemp )
340 uno::Reference < embed::XStorage > xDocStor( mpDocPersist->getStorage() );
341 uno::Reference < embed::XStorage > xCntnrStor( ImplGetContainerStorage( rContainerStorageName ) );
343 if( !xCntnrStor.is() && !pTemp )
344 return;
346 OUString aSrcObjName( rObjName );
347 comphelper::EmbeddedObjectContainer& rContainer = mpDocPersist->getEmbeddedObjectContainer();
349 // Is the object name unique?
350 // if the object is already instantiated by GetEmbeddedObject
351 // that means that the duplication is being loaded
352 bool bDuplicate = rContainer.HasInstantiatedEmbeddedObject( rObjName );
353 DBG_ASSERT( !bDuplicate, "An object in the document is referenced twice!" );
355 if( xDocStor != xCntnrStor || pTemp || bDuplicate )
357 // TODO/LATER: make this altogether a method in the EmbeddedObjectContainer
359 // create a unique name for the duplicate object
360 if( bDuplicate )
361 rObjName = rContainer.CreateUniqueObjectName();
363 if( pTemp )
367 pTemp->Seek( 0 );
368 uno::Reference < io::XStream > xStm = xDocStor->openStreamElement( rObjName,
369 embed::ElementModes::READWRITE | embed::ElementModes::TRUNCATE );
370 std::unique_ptr<SvStream> pStream(::utl::UcbStreamHelper::CreateStream( xStm ));
371 pTemp->ReadStream( *pStream );
372 pStream.reset();
374 // TODO/LATER: what to do when other types of objects are based on substream persistence?
375 // This is an ole object
376 uno::Reference< beans::XPropertySet > xProps( xStm, uno::UNO_QUERY );
377 if (xProps)
379 xProps->setPropertyValue(
380 u"MediaType"_ustr,
381 uno::Any( u"application/vnd.sun.star.oleobject"_ustr ) );
384 xStm->getOutputStream()->closeOutput();
387 catch ( uno::Exception& )
389 return;
392 else
396 xCntnrStor->copyElementTo( aSrcObjName, xDocStor, rObjName );
398 catch ( uno::Exception& )
400 return;
405 // make object known to the container
406 // TODO/LATER: could be done a little bit more efficient!
407 OUString aName( rObjName );
409 // TODO/LATER: The provided pClassId is ignored for now.
410 // The stream contains OLE storage internally and this storage already has a class id specifying the
411 // server that was used to create the object. pClassId could be used to specify the server that should
412 // be used for the next opening, but this information seems to be out of the file format responsibility
413 // area.
414 OUString const baseURL(mpDocPersist->getDocumentBaseURL());
415 rContainer.GetEmbeddedObject(aName, &baseURL);
418 OUString SvXMLEmbeddedObjectHelper::ImplInsertEmbeddedObjectURL(
419 const OUString& rURLStr )
421 OUString sRetURL;
423 OUString aContainerStorageName, aObjectStorageName;
424 if( !ImplGetStorageNames( rURLStr, aContainerStorageName,
425 aObjectStorageName,
426 SvXMLEmbeddedObjectHelperMode::Write == meCreateMode ) )
427 return sRetURL;
429 if( SvXMLEmbeddedObjectHelperMode::Read == meCreateMode )
431 OutputStorageWrapper_Impl *pOut = nullptr;
432 std::map< OUString, rtl::Reference<OutputStorageWrapper_Impl> >::iterator aIter;
434 if( mxStreamMap )
436 aIter = mxStreamMap->find( rURLStr );
437 if( aIter != mxStreamMap->end() && aIter->second.is() )
438 pOut = aIter->second.get();
441 SvGlobalName aClassId, *pClassId = nullptr;
442 sal_Int32 nPos = aObjectStorageName.lastIndexOf( '!' );
443 if( -1 != nPos && aClassId.MakeId( aObjectStorageName.subView( nPos+1 ) ) )
445 aObjectStorageName = aObjectStorageName.copy( 0, nPos );
446 pClassId = &aClassId;
449 ImplReadObject( aContainerStorageName, aObjectStorageName, pClassId, pOut ? pOut->GetStream() : nullptr );
450 sRetURL = XML_EMBEDDEDOBJECT_URL_BASE + aObjectStorageName;
452 if( pOut )
454 mxStreamMap->erase( aIter );
457 else
459 // Objects are written using ::comphelper::IEmbeddedHelper::SaveAs
460 sRetURL = "./";
461 if( !aContainerStorageName.isEmpty() )
463 sRetURL += aContainerStorageName + "/";
465 sRetURL += aObjectStorageName;
468 return sRetURL;
471 uno::Reference< io::XInputStream > SvXMLEmbeddedObjectHelper::ImplGetReplacementImage(
472 const uno::Reference< embed::XEmbeddedObject >& xObj )
474 uno::Reference< io::XInputStream > xStream;
476 if( xObj.is() )
480 bool bSwitchBackToLoaded = false;
481 sal_Int32 nCurState = xObj->getCurrentState();
482 if ( nCurState == embed::EmbedStates::LOADED || nCurState == embed::EmbedStates::RUNNING )
484 // means that the object is not active
485 // copy replacement image from old to new container
486 OUString aMediaType;
487 xStream = mpDocPersist->getEmbeddedObjectContainer().GetGraphicStream( xObj, &aMediaType );
490 if ( !xStream.is() )
492 // the image must be regenerated
493 // TODO/LATER: another aspect could be used
494 if ( nCurState == embed::EmbedStates::LOADED )
495 bSwitchBackToLoaded = true;
497 OUString aMediaType;
498 xStream = svt::EmbeddedObjectRef::GetGraphicReplacementStream(
499 embed::Aspects::MSOLE_CONTENT,
500 xObj,
501 &aMediaType );
504 if ( bSwitchBackToLoaded )
505 // switch back to loaded state; that way we have a minimum cache confusion
506 xObj->changeState( embed::EmbedStates::LOADED );
508 catch( uno::Exception& )
512 return xStream;
515 void SvXMLEmbeddedObjectHelper::Init(
516 const uno::Reference < embed::XStorage >& rRootStorage,
517 ::comphelper::IEmbeddedHelper& rPersist,
518 SvXMLEmbeddedObjectHelperMode eCreateMode )
520 mxRootStorage = rRootStorage;
521 mpDocPersist = &rPersist;
522 meCreateMode = eCreateMode;
525 rtl::Reference<SvXMLEmbeddedObjectHelper> SvXMLEmbeddedObjectHelper::Create(
526 const uno::Reference < embed::XStorage >& rRootStorage,
527 ::comphelper::IEmbeddedHelper& rDocPersist,
528 SvXMLEmbeddedObjectHelperMode eCreateMode )
530 rtl::Reference<SvXMLEmbeddedObjectHelper> pThis(new SvXMLEmbeddedObjectHelper);
532 pThis->Init( rRootStorage, rDocPersist, eCreateMode );
534 return pThis;
537 rtl::Reference<SvXMLEmbeddedObjectHelper> SvXMLEmbeddedObjectHelper::Create(
538 ::comphelper::IEmbeddedHelper& rDocPersist,
539 SvXMLEmbeddedObjectHelperMode eCreateMode )
541 rtl::Reference<SvXMLEmbeddedObjectHelper> pThis(new SvXMLEmbeddedObjectHelper);
543 pThis->Init( nullptr, rDocPersist, eCreateMode );
545 return pThis;
548 OUString SAL_CALL SvXMLEmbeddedObjectHelper::resolveEmbeddedObjectURL(const OUString& rURL)
550 std::unique_lock aGuard( m_aMutex );
552 OUString sRet;
555 sRet = ImplInsertEmbeddedObjectURL(rURL);
557 catch (const RuntimeException&)
559 throw;
561 catch (const Exception&)
563 css::uno::Any anyEx = cppu::getCaughtException();
564 throw WrappedTargetRuntimeException(
565 u"SvXMLEmbeddedObjectHelper::resolveEmbeddedObjectURL non-RuntimeException"_ustr,
566 getXWeak(), anyEx);
568 return sRet;
571 // XNameAccess: alien objects!
572 Any SAL_CALL SvXMLEmbeddedObjectHelper::getByName(
573 const OUString& rURLStr )
575 std::unique_lock aGuard( m_aMutex );
576 Any aRet;
577 if( SvXMLEmbeddedObjectHelperMode::Read == meCreateMode )
579 Reference < XOutputStream > xStrm;
580 if( mxStreamMap )
582 auto aIter = mxStreamMap->find( rURLStr );
583 if( aIter != mxStreamMap->end() && aIter->second.is() )
584 xStrm = aIter->second.get();
586 if( !xStrm.is() )
588 rtl::Reference<OutputStorageWrapper_Impl> xOut = new OutputStorageWrapper_Impl;
589 if( !mxStreamMap )
590 mxStreamMap.emplace();
591 (*mxStreamMap)[rURLStr] = xOut;
592 xStrm = xOut.get();
595 aRet <<= xStrm;
597 else
599 bool bGraphicRepl = false;
600 bool bOasisFormat = true;
601 Reference < XInputStream > xStrm;
602 OUString aContainerStorageName, aObjectStorageName;
603 if( ImplGetStorageNames( rURLStr, aContainerStorageName,
604 aObjectStorageName,
605 true,
606 &bGraphicRepl,
607 &bOasisFormat ) )
611 comphelper::EmbeddedObjectContainer& rContainer =
612 mpDocPersist->getEmbeddedObjectContainer();
614 Reference < embed::XEmbeddedObject > xObj = rContainer.GetEmbeddedObject( aObjectStorageName );
615 DBG_ASSERT( xObj.is(), "Didn't get object" );
617 if( xObj.is() )
619 if( bGraphicRepl )
621 xStrm = ImplGetReplacementImage( xObj );
623 else
625 Reference < embed::XEmbedPersist > xPersist( xObj, UNO_QUERY );
626 if( xPersist.is() )
628 if( !mxTempStorage.is() )
629 mxTempStorage =
630 comphelper::OStorageHelper::GetTemporaryStorage();
631 Sequence < beans::PropertyValue > aDummy,
632 aEmbDescr{ comphelper::makePropertyValue(u"StoreVisualReplacement"_ustr,
633 !bOasisFormat) };
634 if ( !bOasisFormat )
636 uno::Reference< io::XInputStream > xGrInStream = ImplGetReplacementImage( xObj );
637 if ( xGrInStream.is() )
639 aEmbDescr.realloc( 2 );
640 auto pEmbDescr = aEmbDescr.getArray();
641 pEmbDescr[1].Name = "VisualReplacement";
642 pEmbDescr[1].Value <<= xGrInStream;
646 xPersist->storeToEntry( mxTempStorage, aObjectStorageName,
647 aDummy, aEmbDescr );
648 Reference < io::XStream > xStream =
649 mxTempStorage->openStreamElement(
650 aObjectStorageName,
651 embed::ElementModes::READ);
652 if( xStream.is() )
653 xStrm = xStream->getInputStream();
658 catch ( uno::Exception& )
663 aRet <<= xStrm;
666 return aRet;
669 Sequence< OUString > SAL_CALL SvXMLEmbeddedObjectHelper::getElementNames()
671 return {};
674 sal_Bool SAL_CALL SvXMLEmbeddedObjectHelper::hasByName( const OUString& rURLStr )
676 std::unique_lock aGuard( m_aMutex );
677 if( SvXMLEmbeddedObjectHelperMode::Read == meCreateMode )
679 return true;
681 else
683 OUString aContainerStorageName, aObjectStorageName;
684 if( !ImplGetStorageNames( rURLStr, aContainerStorageName,
685 aObjectStorageName,
686 true ) )
687 return false;
689 comphelper::EmbeddedObjectContainer& rContainer = mpDocPersist->getEmbeddedObjectContainer();
690 return !aObjectStorageName.isEmpty() &&
691 rContainer.HasEmbeddedObject( aObjectStorageName );
695 // XNameAccess
696 Type SAL_CALL SvXMLEmbeddedObjectHelper::getElementType()
698 std::unique_lock aGuard( m_aMutex );
699 if( SvXMLEmbeddedObjectHelperMode::Read == meCreateMode )
700 return cppu::UnoType<XOutputStream>::get();
701 else
702 return cppu::UnoType<XInputStream>::get();
705 sal_Bool SAL_CALL SvXMLEmbeddedObjectHelper::hasElements()
707 std::unique_lock aGuard( m_aMutex );
708 if( SvXMLEmbeddedObjectHelperMode::Read == meCreateMode )
710 return true;
712 else
714 comphelper::EmbeddedObjectContainer& rContainer = mpDocPersist->getEmbeddedObjectContainer();
715 return rContainer.HasEmbeddedObjects();
719 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */