1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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 <sot/storage.hxx>
31 #include <tools/debug.hxx>
32 #include <sal/log.hxx>
33 #include <unotools/streamwrap.hxx>
34 #include <unotools/tempfile.hxx>
36 #include <svtools/embedhlp.hxx>
37 #include <unotools/ucbstreamhelper.hxx>
38 #include <comphelper/storagehelper.hxx>
39 #include <comphelper/embeddedobjectcontainer.hxx>
41 #include <comphelper/fileformat.h>
42 #include <cppuhelper/exc_hlp.hxx>
43 #include <cppuhelper/implbase.hxx>
44 #include <svx/xmleohlp.hxx>
47 #include <string_view>
49 using namespace ::osl
;
50 using namespace ::cppu
;
51 using namespace ::utl
;
52 using namespace ::com::sun::star
;
53 using namespace ::com::sun::star::document
;
54 using namespace ::com::sun::star::uno
;
55 using namespace ::com::sun::star::container
;
56 using namespace ::com::sun::star::io
;
57 using namespace ::com::sun::star::lang
;
59 #define XML_CONTAINERSTORAGE_NAME_60 "Pictures"
60 #define XML_CONTAINERSTORAGE_NAME "ObjectReplacements"
61 #define XML_EMBEDDEDOBJECT_URL_BASE "vnd.sun.star.EmbeddedObject:"
62 #define XML_EMBEDDEDOBJECTGRAPHIC_URL_BASE "vnd.sun.star.GraphicObject:"
65 class OutputStorageWrapper_Impl
: public ::cppu::WeakImplHelper
<XOutputStream
>
68 Reference
< XOutputStream
> xOut
;
70 bool bStreamClosed
: 1;
74 OutputStorageWrapper_Impl();
76 // css::io::XOutputStream
77 virtual void SAL_CALL
writeBytes(const Sequence
< sal_Int8
>& aData
) override
;
78 virtual void SAL_CALL
flush() override
;
79 virtual void SAL_CALL
closeOutput() override
;
81 SvStream
* GetStream();
84 OutputStorageWrapper_Impl::OutputStorageWrapper_Impl()
85 : bStreamClosed( false )
88 aTempFile
.EnableKillingFile();
89 pStream
= aTempFile
.GetStream( StreamMode::READWRITE
);
90 xOut
= new OOutputStreamWrapper( *pStream
);
93 SvStream
*OutputStorageWrapper_Impl::GetStream()
100 void SAL_CALL
OutputStorageWrapper_Impl::writeBytes(
101 const Sequence
< sal_Int8
>& aData
)
103 MutexGuard
aGuard( maMutex
);
104 xOut
->writeBytes( aData
);
107 void SAL_CALL
OutputStorageWrapper_Impl::flush()
109 MutexGuard
aGuard( maMutex
);
113 void SAL_CALL
OutputStorageWrapper_Impl::closeOutput()
115 MutexGuard
aGuard( maMutex
);
117 bStreamClosed
= true;
120 const std::u16string_view
gaReplacementGraphicsContainerStorageName( u
"" XML_CONTAINERSTORAGE_NAME
);
121 const std::u16string_view
gaReplacementGraphicsContainerStorageName60( u
"" XML_CONTAINERSTORAGE_NAME_60
);
123 SvXMLEmbeddedObjectHelper::SvXMLEmbeddedObjectHelper() :
124 WeakComponentImplHelper
< XEmbeddedObjectResolver
, XNameAccess
>( maMutex
),
125 mpDocPersist( nullptr ),
126 meCreateMode( SvXMLEmbeddedObjectHelperMode::Read
)
130 SvXMLEmbeddedObjectHelper::SvXMLEmbeddedObjectHelper( ::comphelper::IEmbeddedHelper
& rDocPersist
, SvXMLEmbeddedObjectHelperMode eCreateMode
) :
131 WeakComponentImplHelper
< XEmbeddedObjectResolver
, XNameAccess
>( maMutex
),
132 mpDocPersist( nullptr ),
133 meCreateMode( SvXMLEmbeddedObjectHelperMode::Read
)
135 Init( nullptr, rDocPersist
, eCreateMode
);
138 SvXMLEmbeddedObjectHelper::~SvXMLEmbeddedObjectHelper()
142 void SAL_CALL
SvXMLEmbeddedObjectHelper::disposing()
144 if( mxTempStorage
.is() )
146 mxTempStorage
->dispose();
150 void SvXMLEmbeddedObjectHelper::splitObjectURL(const OUString
& _aURLNoPar
,
151 OUString
& rContainerStorageName
,
152 OUString
& rObjectStorageName
)
154 DBG_ASSERT(_aURLNoPar
.isEmpty() || '#' != _aURLNoPar
[0], "invalid object URL" );
155 OUString aURLNoPar
= _aURLNoPar
;
157 sal_Int32 _nPos
= aURLNoPar
.lastIndexOf( '/' );
160 rContainerStorageName
.clear();
161 rObjectStorageName
= aURLNoPar
;
165 //eliminate 'superfluous' slashes at start and end
166 //#i103076# load objects with all allowed xlink:href syntaxes
168 //eliminate './' at start
169 sal_Int32 nStart
= 0;
170 sal_Int32 nCount
= aURLNoPar
.getLength();
171 if( aURLNoPar
.startsWith( "./" ) )
177 //eliminate '/' at end
178 sal_Int32 nEnd
= aURLNoPar
.lastIndexOf( '/' );
179 if( nEnd
== aURLNoPar
.getLength()-1 && nEnd
!= (nStart
-1) )
182 aURLNoPar
= aURLNoPar
.copy( nStart
, nCount
);
185 _nPos
= aURLNoPar
.lastIndexOf( '/' );
187 rContainerStorageName
= aURLNoPar
.copy( 0, _nPos
);
188 rObjectStorageName
= aURLNoPar
.copy( _nPos
+1 );
192 bool SvXMLEmbeddedObjectHelper::ImplGetStorageNames(
193 const OUString
& rURLStr
,
194 OUString
& rContainerStorageName
,
195 OUString
& rObjectStorageName
,
196 bool bInternalToExternal
,
198 bool *pOasisFormat
) const
200 // internal URL: vnd.sun.star.EmbeddedObject:<object-name>
201 // or: vnd.sun.star.EmbeddedObject:<path>/<object-name>
202 // internal replacement images:
203 // vnd.sun.star.EmbeddedObjectGraphic:<object-name>
204 // or: vnd.sun.star.EmbeddedObjectGraphic:<path>/<object-name>
205 // external URL: ./<path>/<object-name>
206 // or: <path>/<object-name>
208 // currently, path may only consist of a single directory name
209 // it is also possible to have additional arguments at the end of URL: <main URL>[?<name>=<value>[,<name>=<value>]*]
212 *pGraphicRepl
= false;
215 *pOasisFormat
= true; // the default value
217 if( rURLStr
.isEmpty() )
220 // get rid of arguments
221 sal_Int32 nPos
= rURLStr
.indexOf( '?' );
227 aURLNoPar
= rURLStr
.copy( 0, nPos
);
229 // check the arguments
231 while( nPos
>= 0 && nPos
< rURLStr
.getLength() )
233 OUString aToken
= rURLStr
.getToken( 0, ',', nPos
);
234 if ( aToken
.equalsIgnoreAsciiCase( "oasis=false" ) )
237 *pOasisFormat
= false;
242 SAL_WARN( "svx", "invalid arguments was found in URL!" );
247 if( bInternalToExternal
)
249 nPos
= aURLNoPar
.indexOf( ':' );
252 bool bObjUrl
= aURLNoPar
.startsWith( XML_EMBEDDEDOBJECT_URL_BASE
);
253 bool bGrUrl
= !bObjUrl
&&
254 aURLNoPar
.startsWith( XML_EMBEDDEDOBJECTGRAPHIC_URL_BASE
);
255 if( !(bObjUrl
|| bGrUrl
) )
258 sal_Int32 nPathStart
= nPos
+ 1;
259 nPos
= aURLNoPar
.lastIndexOf( '/' );
262 rContainerStorageName
.clear();
263 rObjectStorageName
= aURLNoPar
.copy( nPathStart
);
265 else if( nPos
> nPathStart
)
267 rContainerStorageName
= aURLNoPar
.copy( nPathStart
, nPos
-nPathStart
);
268 rObjectStorageName
= aURLNoPar
.copy( nPos
+1 );
275 bool bOASIS
= mxRootStorage
.is() &&
276 ( SotStorage::GetVersion( mxRootStorage
) > SOFFICE_FILEFORMAT_60
);
277 rContainerStorageName
= bOASIS
278 ? gaReplacementGraphicsContainerStorageName
279 : gaReplacementGraphicsContainerStorageName60
;
282 *pGraphicRepl
= true;
289 splitObjectURL(aURLNoPar
, rContainerStorageName
, rObjectStorageName
);
292 if( -1 != rContainerStorageName
.indexOf( '/' ) )
294 OSL_FAIL( "SvXMLEmbeddedObjectHelper: invalid path name" );
301 uno::Reference
< embed::XStorage
> const & SvXMLEmbeddedObjectHelper::ImplGetContainerStorage(
302 const OUString
& rStorageName
)
304 DBG_ASSERT( -1 == rStorageName
.indexOf( '/' ) &&
305 -1 == rStorageName
.indexOf( '\\' ),
306 "nested embedded storages aren't supported" );
307 if( !mxContainerStorage
.is() ||
308 ( rStorageName
!= maCurContainerStorageName
) )
310 if( mxContainerStorage
.is() &&
311 !maCurContainerStorageName
.isEmpty() &&
312 SvXMLEmbeddedObjectHelperMode::Write
== meCreateMode
)
314 uno::Reference
< embed::XTransactedObject
> xTrans( mxContainerStorage
, uno::UNO_QUERY
);
319 if( !rStorageName
.isEmpty() && mxRootStorage
.is() )
321 sal_Int32 nMode
= SvXMLEmbeddedObjectHelperMode::Write
== meCreateMode
322 ? ::embed::ElementModes::READWRITE
323 : ::embed::ElementModes::READ
;
324 mxContainerStorage
= mxRootStorage
->openStorageElement( rStorageName
,
329 mxContainerStorage
= mxRootStorage
;
331 maCurContainerStorageName
= rStorageName
;
334 return mxContainerStorage
;
337 void SvXMLEmbeddedObjectHelper::ImplReadObject(
338 const OUString
& rContainerStorageName
,
340 const SvGlobalName
*, // pClassId, see "TODO/LATER" below
343 uno::Reference
< embed::XStorage
> xDocStor( mpDocPersist
->getStorage() );
344 uno::Reference
< embed::XStorage
> xCntnrStor( ImplGetContainerStorage( rContainerStorageName
) );
346 if( !xCntnrStor
.is() && !pTemp
)
349 OUString
aSrcObjName( rObjName
);
350 comphelper::EmbeddedObjectContainer
& rContainer
= mpDocPersist
->getEmbeddedObjectContainer();
352 // Is the object name unique?
353 // if the object is already instantiated by GetEmbeddedObject
354 // that means that the duplication is being loaded
355 bool bDuplicate
= rContainer
.HasInstantiatedEmbeddedObject( rObjName
);
356 DBG_ASSERT( !bDuplicate
, "An object in the document is referenced twice!" );
358 if( xDocStor
!= xCntnrStor
|| pTemp
|| bDuplicate
)
360 // TODO/LATER: make this altogether a method in the EmbeddedObjectContainer
362 // create a unique name for the duplicate object
364 rObjName
= rContainer
.CreateUniqueObjectName();
371 uno::Reference
< io::XStream
> xStm
= xDocStor
->openStreamElement( rObjName
,
372 embed::ElementModes::READWRITE
| embed::ElementModes::TRUNCATE
);
373 std::unique_ptr
<SvStream
> pStream(::utl::UcbStreamHelper::CreateStream( xStm
));
374 pTemp
->ReadStream( *pStream
);
377 // TODO/LATER: what to do when other types of objects are based on substream persistence?
378 // This is an ole object
379 uno::Reference
< beans::XPropertySet
> xProps( xStm
, uno::UNO_QUERY_THROW
);
380 xProps
->setPropertyValue(
382 uno::makeAny( OUString( "application/vnd.sun.star.oleobject" ) ) );
384 xStm
->getOutputStream()->closeOutput();
386 catch ( uno::Exception
& )
395 xCntnrStor
->copyElementTo( aSrcObjName
, xDocStor
, rObjName
);
397 catch ( uno::Exception
& )
404 // make object known to the container
405 // TODO/LATER: could be done a little bit more efficient!
406 OUString
aName( rObjName
);
408 // TODO/LATER: The provided pClassId is ignored for now.
409 // The stream contains OLE storage internally and this storage already has a class id specifying the
410 // server that was used to create the object. pClassId could be used to specify the server that should
411 // be used for the next opening, but this information seems to be out of the file format responsibility
413 OUString
const baseURL(mpDocPersist
->getDocumentBaseURL());
414 rContainer
.GetEmbeddedObject(aName
, &baseURL
);
417 OUString
SvXMLEmbeddedObjectHelper::ImplInsertEmbeddedObjectURL(
418 const OUString
& rURLStr
)
422 OUString aContainerStorageName
, aObjectStorageName
;
423 if( !ImplGetStorageNames( rURLStr
, aContainerStorageName
,
425 SvXMLEmbeddedObjectHelperMode::Write
== meCreateMode
) )
428 if( SvXMLEmbeddedObjectHelperMode::Read
== meCreateMode
)
430 OutputStorageWrapper_Impl
*pOut
= nullptr;
431 std::map
< OUString
, rtl::Reference
<OutputStorageWrapper_Impl
> >::iterator aIter
;
435 aIter
= mxStreamMap
->find( rURLStr
);
436 if( aIter
!= mxStreamMap
->end() && aIter
->second
.is() )
437 pOut
= aIter
->second
.get();
440 SvGlobalName aClassId
, *pClassId
= nullptr;
441 sal_Int32 nPos
= aObjectStorageName
.lastIndexOf( '!' );
442 if( -1 != nPos
&& aClassId
.MakeId( aObjectStorageName
.copy( nPos
+1 ) ) )
444 aObjectStorageName
= aObjectStorageName
.copy( 0, nPos
);
445 pClassId
= &aClassId
;
448 ImplReadObject( aContainerStorageName
, aObjectStorageName
, pClassId
, pOut
? pOut
->GetStream() : nullptr );
449 sRetURL
= XML_EMBEDDEDOBJECT_URL_BASE
+ aObjectStorageName
;
453 mxStreamMap
->erase( aIter
);
458 // Objects are written using ::comphelper::IEmbeddedHelper::SaveAs
460 if( !aContainerStorageName
.isEmpty() )
462 sRetURL
+= aContainerStorageName
+ "/";
464 sRetURL
+= aObjectStorageName
;
470 uno::Reference
< io::XInputStream
> SvXMLEmbeddedObjectHelper::ImplGetReplacementImage(
471 const uno::Reference
< embed::XEmbeddedObject
>& xObj
)
473 uno::Reference
< io::XInputStream
> xStream
;
479 bool bSwitchBackToLoaded
= false;
480 sal_Int32 nCurState
= xObj
->getCurrentState();
481 if ( nCurState
== embed::EmbedStates::LOADED
|| nCurState
== embed::EmbedStates::RUNNING
)
483 // means that the object is not active
484 // copy replacement image from old to new container
486 xStream
= mpDocPersist
->getEmbeddedObjectContainer().GetGraphicStream( xObj
, &aMediaType
);
491 // the image must be regenerated
492 // TODO/LATER: another aspect could be used
493 if ( nCurState
== embed::EmbedStates::LOADED
)
494 bSwitchBackToLoaded
= true;
497 xStream
= svt::EmbeddedObjectRef::GetGraphicReplacementStream(
498 embed::Aspects::MSOLE_CONTENT
,
503 if ( bSwitchBackToLoaded
)
504 // switch back to loaded state; that way we have a minimum cache confusion
505 xObj
->changeState( embed::EmbedStates::LOADED
);
507 catch( uno::Exception
& )
514 void SvXMLEmbeddedObjectHelper::Init(
515 const uno::Reference
< embed::XStorage
>& rRootStorage
,
516 ::comphelper::IEmbeddedHelper
& rPersist
,
517 SvXMLEmbeddedObjectHelperMode eCreateMode
)
519 mxRootStorage
= rRootStorage
;
520 mpDocPersist
= &rPersist
;
521 meCreateMode
= eCreateMode
;
524 rtl::Reference
<SvXMLEmbeddedObjectHelper
> SvXMLEmbeddedObjectHelper::Create(
525 const uno::Reference
< embed::XStorage
>& rRootStorage
,
526 ::comphelper::IEmbeddedHelper
& rDocPersist
,
527 SvXMLEmbeddedObjectHelperMode eCreateMode
)
529 rtl::Reference
<SvXMLEmbeddedObjectHelper
> pThis(new SvXMLEmbeddedObjectHelper
);
531 pThis
->Init( rRootStorage
, rDocPersist
, eCreateMode
);
536 rtl::Reference
<SvXMLEmbeddedObjectHelper
> SvXMLEmbeddedObjectHelper::Create(
537 ::comphelper::IEmbeddedHelper
& rDocPersist
,
538 SvXMLEmbeddedObjectHelperMode eCreateMode
)
540 rtl::Reference
<SvXMLEmbeddedObjectHelper
> pThis(new SvXMLEmbeddedObjectHelper
);
542 pThis
->Init( nullptr, rDocPersist
, eCreateMode
);
547 OUString SAL_CALL
SvXMLEmbeddedObjectHelper::resolveEmbeddedObjectURL(const OUString
& rURL
)
549 MutexGuard
aGuard( maMutex
);
554 sRet
= ImplInsertEmbeddedObjectURL(rURL
);
556 catch (const RuntimeException
&)
560 catch (const Exception
&)
562 css::uno::Any anyEx
= cppu::getCaughtException();
563 throw WrappedTargetRuntimeException(
564 "SvXMLEmbeddedObjectHelper::resolveEmbeddedObjectURL non-RuntimeException",
565 static_cast<uno::XWeak
*>(this), anyEx
);
570 // XNameAccess: alien objects!
571 Any SAL_CALL
SvXMLEmbeddedObjectHelper::getByName(
572 const OUString
& rURLStr
)
574 MutexGuard
aGuard( maMutex
);
576 if( SvXMLEmbeddedObjectHelperMode::Read
== meCreateMode
)
578 Reference
< XOutputStream
> xStrm
;
581 auto aIter
= mxStreamMap
->find( rURLStr
);
582 if( aIter
!= mxStreamMap
->end() && aIter
->second
.is() )
583 xStrm
= aIter
->second
.get();
587 rtl::Reference
<OutputStorageWrapper_Impl
> xOut
= new OutputStorageWrapper_Impl
;
589 mxStreamMap
.emplace();
590 (*mxStreamMap
)[rURLStr
] = xOut
;
598 bool bGraphicRepl
= false;
599 bool bOasisFormat
= true;
600 Reference
< XInputStream
> xStrm
;
601 OUString aContainerStorageName
, aObjectStorageName
;
602 if( ImplGetStorageNames( rURLStr
, aContainerStorageName
,
610 comphelper::EmbeddedObjectContainer
& rContainer
=
611 mpDocPersist
->getEmbeddedObjectContainer();
613 Reference
< embed::XEmbeddedObject
> xObj
= rContainer
.GetEmbeddedObject( aObjectStorageName
);
614 DBG_ASSERT( xObj
.is(), "Didn't get object" );
620 xStrm
= ImplGetReplacementImage( xObj
);
624 Reference
< embed::XEmbedPersist
> xPersist( xObj
, UNO_QUERY
);
627 if( !mxTempStorage
.is() )
629 comphelper::OStorageHelper::GetTemporaryStorage();
630 Sequence
< beans::PropertyValue
> aDummy( 0 ), aEmbDescr( 1 );
631 aEmbDescr
[0].Name
= "StoreVisualReplacement";
632 aEmbDescr
[0].Value
<<= !bOasisFormat
;
635 uno::Reference
< io::XInputStream
> xGrInStream
= ImplGetReplacementImage( xObj
);
636 if ( xGrInStream
.is() )
638 aEmbDescr
.realloc( 2 );
639 aEmbDescr
[1].Name
= "VisualReplacement";
640 aEmbDescr
[1].Value
<<= xGrInStream
;
644 xPersist
->storeToEntry( mxTempStorage
, aObjectStorageName
,
646 Reference
< io::XStream
> xStream
=
647 mxTempStorage
->openStreamElement(
649 embed::ElementModes::READ
);
651 xStrm
= xStream
->getInputStream();
656 catch ( uno::Exception
& )
667 Sequence
< OUString
> SAL_CALL
SvXMLEmbeddedObjectHelper::getElementNames()
672 sal_Bool SAL_CALL
SvXMLEmbeddedObjectHelper::hasByName( const OUString
& rURLStr
)
674 MutexGuard
aGuard( maMutex
);
675 if( SvXMLEmbeddedObjectHelperMode::Read
== meCreateMode
)
681 OUString aContainerStorageName
, aObjectStorageName
;
682 if( !ImplGetStorageNames( rURLStr
, aContainerStorageName
,
687 comphelper::EmbeddedObjectContainer
& rContainer
= mpDocPersist
->getEmbeddedObjectContainer();
688 return !aObjectStorageName
.isEmpty() &&
689 rContainer
.HasEmbeddedObject( aObjectStorageName
);
694 Type SAL_CALL
SvXMLEmbeddedObjectHelper::getElementType()
696 MutexGuard
aGuard( maMutex
);
697 if( SvXMLEmbeddedObjectHelperMode::Read
== meCreateMode
)
698 return cppu::UnoType
<XOutputStream
>::get();
700 return cppu::UnoType
<XInputStream
>::get();
703 sal_Bool SAL_CALL
SvXMLEmbeddedObjectHelper::hasElements()
705 MutexGuard
aGuard( maMutex
);
706 if( SvXMLEmbeddedObjectHelperMode::Read
== meCreateMode
)
712 comphelper::EmbeddedObjectContainer
& rContainer
= mpDocPersist
->getEmbeddedObjectContainer();
713 return rContainer
.HasEmbeddedObjects();
717 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */