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 .
20 #include <oox/ole/vbaproject.hxx>
22 #include <com/sun/star/beans/XPropertySet.hpp>
23 #include <com/sun/star/document/XStorageBasedDocument.hpp>
24 #include <com/sun/star/embed/ElementModes.hpp>
25 #include <com/sun/star/embed/XTransactedObject.hpp>
26 #include <com/sun/star/frame/XModel.hpp>
27 #include <com/sun/star/lang/XMultiServiceFactory.hpp>
28 #include <com/sun/star/script/ModuleType.hpp>
29 #include <com/sun/star/script/XLibraryContainer.hpp>
30 #include <com/sun/star/script/vba/XVBACompatibility.hpp>
31 #include <com/sun/star/script/vba/XVBAMacroResolver.hpp>
32 #include <com/sun/star/uno/XComponentContext.hpp>
33 #include <comphelper/configurationhelper.hxx>
34 #include <comphelper/storagehelper.hxx>
35 #include <osl/diagnose.h>
36 #include <rtl/tencinfo.h>
37 #include <rtl/ustrbuf.h>
38 #include <sal/log.hxx>
39 #include <oox/helper/binaryinputstream.hxx>
40 #include <oox/helper/containerhelper.hxx>
41 #include <oox/helper/propertyset.hxx>
42 #include <oox/helper/textinputstream.hxx>
43 #include <oox/ole/olestorage.hxx>
44 #include <oox/ole/vbacontrol.hxx>
45 #include <oox/ole/vbahelper.hxx>
46 #include <oox/ole/vbainputstream.hxx>
47 #include <oox/ole/vbamodule.hxx>
48 #include <oox/token/properties.hxx>
53 using namespace ::com::sun::star
;
54 using namespace ::com::sun::star::container
;
55 using namespace ::com::sun::star::document
;
56 using namespace ::com::sun::star::embed
;
57 using namespace ::com::sun::star::frame
;
58 using namespace ::com::sun::star::io
;
59 using namespace ::com::sun::star::lang
;
60 using namespace ::com::sun::star::script
;
61 using namespace ::com::sun::star::script::vba
;
62 using namespace ::com::sun::star::uno
;
64 using ::comphelper::ConfigurationHelper
;
68 bool lclReadConfigItem( const Reference
< XInterface
>& rxConfigAccess
, const OUString
& rItemName
)
70 // some applications do not support all configuration items, assume 'false' in this case
73 Any aItem
= ConfigurationHelper::readRelativeKey( rxConfigAccess
, "Filter/Import/VBA", rItemName
);
74 return aItem
.has
< bool >() && aItem
.get
< bool >();
76 catch(const Exception
& )
84 VbaFilterConfig::VbaFilterConfig( const Reference
< XComponentContext
>& rxContext
, const OUString
& rConfigCompName
)
86 OSL_ENSURE( rxContext
.is(), "VbaFilterConfig::VbaFilterConfig - missing component context" );
87 if( rxContext
.is() ) try
89 OSL_ENSURE( !rConfigCompName
.isEmpty(), "VbaFilterConfig::VbaFilterConfig - invalid configuration component name" );
90 OUString aConfigPackage
= "org.openoffice.Office." + rConfigCompName
;
91 mxConfigAccess
= ConfigurationHelper::openConfig( rxContext
, aConfigPackage
, comphelper::EConfigurationModes::ReadOnly
);
93 catch(const Exception
& )
96 OSL_ENSURE( mxConfigAccess
.is(), "VbaFilterConfig::VbaFilterConfig - cannot open configuration" );
99 VbaFilterConfig::~VbaFilterConfig()
103 bool VbaFilterConfig::isImportVba() const
105 return lclReadConfigItem( mxConfigAccess
, "Load" );
108 bool VbaFilterConfig::isImportVbaExecutable() const
110 return lclReadConfigItem( mxConfigAccess
, "Executable" );
113 bool VbaFilterConfig::isExportVba() const
115 return lclReadConfigItem( mxConfigAccess
, "Save" );
118 VbaMacroAttacherBase::VbaMacroAttacherBase( const OUString
& rMacroName
) :
119 maMacroName( rMacroName
)
121 OSL_ENSURE( !maMacroName
.isEmpty(), "VbaMacroAttacherBase::VbaMacroAttacherBase - empty macro name" );
124 VbaMacroAttacherBase::~VbaMacroAttacherBase()
128 void VbaMacroAttacherBase::resolveAndAttachMacro( const Reference
< XVBAMacroResolver
>& rxResolver
)
132 attachMacro( rxResolver
->resolveVBAMacroToScriptURL( maMacroName
) );
134 catch(const Exception
& )
139 VbaProject::VbaProject( const Reference
< XComponentContext
>& rxContext
,
140 const Reference
< XModel
>& rxDocModel
, const OUString
& rConfigCompName
) :
141 VbaFilterConfig( rxContext
, rConfigCompName
),
142 mxContext( rxContext
),
143 mxDocModel( rxDocModel
),
144 maPrjName( "Standard" )
146 OSL_ENSURE( mxContext
.is(), "VbaProject::VbaProject - missing component context" );
147 OSL_ENSURE( mxDocModel
.is(), "VbaProject::VbaProject - missing document model" );
150 VbaProject::~VbaProject()
154 bool VbaProject::importVbaProject( StorageBase
& rVbaPrjStrg
)
156 // create GraphicHelper
157 Reference
< css::frame::XFrame
> xFrame
;
158 if ( mxDocModel
.is() )
160 Reference
< css::frame::XController
> xController
= mxDocModel
->getCurrentController();
161 xFrame
= xController
.is() ? xController
->getFrame() : nullptr;
163 StorageRef noStorage
;
164 // if the GraphicHelper tries to use noStorage it will of course crash
165 // but.. this shouldn't happen as there is no reason for GraphicHelper
166 // to do that when importing VBA projects
167 GraphicHelper
grfHlp( mxContext
, xFrame
, noStorage
);
168 importVbaProject( rVbaPrjStrg
, grfHlp
);
169 // return true if something has been imported
170 return (mxBasicLib
.is() && mxBasicLib
->hasElements()) ||
171 (mxDialogLib
.is() && mxDialogLib
->hasElements());
174 void VbaProject::importVbaProject( StorageBase
& rVbaPrjStrg
, const GraphicHelper
& rGraphicHelper
)
176 if( rVbaPrjStrg
.isStorage() )
178 // load the code modules and forms
180 importVba( rVbaPrjStrg
, rGraphicHelper
);
181 // copy entire storage into model
183 copyStorage( rVbaPrjStrg
);
187 void VbaProject::importVbaData(const uno::Reference
<io::XInputStream
>& xInputStream
)
189 uno::Reference
<document::XStorageBasedDocument
> xStorageBasedDoc(mxDocModel
, uno::UNO_QUERY
);
190 uno::Reference
<embed::XStorage
> xDocStorage(xStorageBasedDoc
->getDocumentStorage(), uno::UNO_QUERY
);
192 const sal_Int32 nOpenMode
= ElementModes::SEEKABLE
| ElementModes::WRITE
| ElementModes::TRUNCATE
;
193 uno::Reference
<io::XOutputStream
> xDocStream(xDocStorage
->openStreamElement("_MS_VBA_Macros_XML", nOpenMode
), uno::UNO_QUERY
);
194 comphelper::OStorageHelper::CopyInputToOutput(xInputStream
, xDocStream
);
196 uno::Reference
<embed::XTransactedObject
>(xDocStorage
, uno::UNO_QUERY_THROW
)->commit();
199 void VbaProject::registerMacroAttacher( const VbaMacroAttacherRef
& rxAttacher
)
201 OSL_ENSURE( rxAttacher
.get(), "VbaProject::registerMacroAttacher - unexpected empty reference" );
202 maMacroAttachers
.push_back( rxAttacher
);
205 // protected ------------------------------------------------------------------
207 void VbaProject::addDummyModule( const OUString
& rName
, sal_Int32 nType
)
209 OSL_ENSURE( !rName
.isEmpty(), "VbaProject::addDummyModule - missing module name" );
210 maDummyModules
[ rName
] = nType
;
213 void VbaProject::prepareImport()
217 // private --------------------------------------------------------------------
219 Reference
< XLibraryContainer
> VbaProject::getLibraryContainer( sal_Int32 nPropId
)
221 PropertySet
aDocProp( mxDocModel
);
222 Reference
< XLibraryContainer
> xLibContainer( aDocProp
.getAnyProperty( nPropId
), UNO_QUERY
);
223 return xLibContainer
;
226 Reference
< XNameContainer
> VbaProject::openLibrary( sal_Int32 nPropId
)
228 Reference
< XNameContainer
> xLibrary
;
231 Reference
< XLibraryContainer
> xLibContainer( getLibraryContainer( nPropId
), UNO_SET_THROW
);
232 if( !xLibContainer
->hasByName( maPrjName
) )
233 xLibContainer
->createLibrary( maPrjName
);
234 xLibrary
.set( xLibContainer
->getByName( maPrjName
), UNO_QUERY_THROW
);
236 catch(const Exception
& )
239 OSL_ENSURE( xLibrary
.is(), "VbaProject::openLibrary - cannot create library" );
243 Reference
< XNameContainer
> const & VbaProject::createBasicLibrary()
245 if( !mxBasicLib
.is() )
246 mxBasicLib
= openLibrary( PROP_BasicLibraries
);
250 Reference
< XNameContainer
> const & VbaProject::createDialogLibrary()
252 if( !mxDialogLib
.is() )
253 mxDialogLib
= openLibrary( PROP_DialogLibraries
);
257 void VbaProject::importVba( StorageBase
& rVbaPrjStrg
, const GraphicHelper
& rGraphicHelper
)
259 readVbaModules( rVbaPrjStrg
);
260 importModulesAndForms(rVbaPrjStrg
, rGraphicHelper
);
261 // attach macros to registered objects
265 void VbaProject::readVbaModules( StorageBase
& rVbaPrjStrg
)
267 StorageRef xVbaStrg
= rVbaPrjStrg
.openSubStorage( "VBA", false );
268 OSL_ENSURE( xVbaStrg
.get(), "VbaProject::readVbaModules - cannot open 'VBA' substorage" );
272 /* Read the 'VBA/dir' stream which contains general settings of the VBA
273 project such as the text encoding used throughout several streams, and
274 a list of all code modules.
276 BinaryXInputStream
aInStrm( xVbaStrg
->openInputStream( "dir" ), true );
277 // VbaInputStream implements decompression
278 VbaInputStream
aDirStrm( aInStrm
);
279 OSL_ENSURE( !aDirStrm
.isEof(), "VbaProject::importVba - cannot open 'dir' stream" );
280 if( aDirStrm
.isEof() )
283 // virtual call, derived classes may do some preparations
286 // read all records of the directory
287 rtl_TextEncoding eTextEnc
= RTL_TEXTENCODING_MS_1252
;
288 sal_uInt16 nModuleCount
= 0;
289 bool bExecutable
= isImportVbaExecutable();
291 sal_uInt16 nRecId
= 0;
292 StreamDataSequence aRecData
;
293 while( VbaHelper::readDirRecord( nRecId
, aRecData
, aDirStrm
) && (nRecId
!= VBA_ID_PROJECTEND
) )
295 // create record stream object from imported record data
296 SequenceInputStream
aRecStrm( aRecData
);
297 sal_Int32 nRecSize
= aRecData
.getLength();
300 case VBA_ID_PROJECTCODEPAGE
:
302 OSL_ENSURE( nRecSize
== 2, "VbaProject::importVba - invalid record size" );
303 OSL_ENSURE( maModules
.empty(), "VbaProject::importVba - unexpected PROJECTCODEPAGE record" );
304 rtl_TextEncoding eNewTextEnc
= rtl_getTextEncodingFromWindowsCodePage( aRecStrm
.readuInt16() );
305 OSL_ENSURE( eNewTextEnc
!= RTL_TEXTENCODING_DONTKNOW
, "VbaProject::importVba - unknown text encoding" );
306 if( eNewTextEnc
!= RTL_TEXTENCODING_DONTKNOW
)
307 eTextEnc
= eNewTextEnc
;
310 case VBA_ID_PROJECTNAME
:
312 OUString aPrjName
= aRecStrm
.readCharArrayUC( nRecSize
, eTextEnc
);
313 OSL_ENSURE( !aPrjName
.isEmpty(), "VbaProject::importVba - invalid project name" );
314 if( !aPrjName
.isEmpty() )
315 maPrjName
= aPrjName
;
318 case VBA_ID_PROJECTMODULES
:
319 OSL_ENSURE( nRecSize
== 2, "VbaProject::importVba - invalid record size" );
320 OSL_ENSURE( maModules
.empty(), "VbaProject::importVba - unexpected PROJECTMODULES record" );
321 nModuleCount
= aRecStrm
.readuInt16();
323 case VBA_ID_MODULENAME
:
325 OUString aName
= aRecStrm
.readCharArrayUC( nRecSize
, eTextEnc
);
326 OSL_ENSURE( !aName
.isEmpty(), "VbaProject::importVba - invalid module name" );
327 OSL_ENSURE( !maModules
.has( aName
), "VbaProject::importVba - multiple modules with the same name" );
328 VbaModuleMap::mapped_type
& rxModule
= maModules
[ aName
];
329 rxModule
.reset( new VbaModule( mxContext
, mxDocModel
, aName
, eTextEnc
, bExecutable
) );
330 // read all remaining records until the MODULEEND record
331 rxModule
->importDirRecords( aDirStrm
);
332 OSL_ENSURE( !maModulesByStrm
.has( rxModule
->getStreamName() ), "VbaProject::importVba - multiple modules with the same stream name" );
333 maModulesByStrm
[ rxModule
->getStreamName() ] = rxModule
;
338 SAL_WARN_IF( nModuleCount
!= maModules
.size(), "oox", "VbaProject::importVba - invalid module count" );
340 /* The directory does not contain the real type of the modules, it
341 distinguishes only between 'procedural' and 'document' (the latter
342 includes class and form modules). Now, the exact type of all modules
343 will be read from the 'PROJECT' stream. It consists of text lines in
344 'key=value' format which list the code modules by type.
346 - The line 'document=<modulename>/&HXXXXXXXX' declares document
347 modules. These are attached to the Word document (usually called
348 'ThisDocument'), the Excel workbook (usually called
349 'ThisWorkbook'), or single Excel worksheets or chartsheets (usually
350 called 'SheetX' or 'ChartX', X being a decimal number). Of course,
351 users may rename all these modules. The slash character separates
352 an automation server version number (hexadecimal 'XXXXXXXX') from
354 - The line 'Module=<modulename>' declares common procedural code
356 - The line 'Class=<modulename>' declares a class module.
357 - The line 'BaseClass=<modulename>' declares a code module attached
358 to a user form with the same name.
360 BinaryXInputStream
aPrjStrm( rVbaPrjStrg
.openInputStream( "PROJECT" ), true );
361 OSL_ENSURE( !aPrjStrm
.isEof(), "VbaProject::importVba - cannot open 'PROJECT' stream" );
362 // do not exit if this stream does not exist, but proceed to load the modules below
363 if( !aPrjStrm
.isEof() )
365 TextInputStream
aPrjTextStrm( mxContext
, aPrjStrm
, eTextEnc
);
366 OUString aKey
, aValue
;
367 bool bExitLoop
= false;
368 while( !bExitLoop
&& !aPrjTextStrm
.isEof() )
370 // read a text line from the stream
371 OUString aLine
= aPrjTextStrm
.readLine().trim();
372 sal_Int32 nLineLen
= aLine
.getLength();
373 // exit if a subsection starts (section name is given in brackets)
374 bExitLoop
= (nLineLen
>= 2) && (aLine
[ 0 ] == '[') && (aLine
[ nLineLen
- 1 ] == ']');
375 if( !bExitLoop
&& VbaHelper::extractKeyValue( aKey
, aValue
, aLine
) )
377 sal_Int32 nType
= ModuleType::UNKNOWN
;
378 if( aKey
.equalsIgnoreAsciiCase( "Document" ) )
380 nType
= ModuleType::DOCUMENT
;
381 // strip automation server version from module names
382 sal_Int32 nSlashPos
= aValue
.indexOf( '/' );
384 aValue
= aValue
.copy( 0, nSlashPos
);
386 else if( aKey
.equalsIgnoreAsciiCase( "Module" ) )
387 nType
= ModuleType::NORMAL
;
388 else if( aKey
.equalsIgnoreAsciiCase( "Class" ) )
389 nType
= ModuleType::CLASS
;
390 else if( aKey
.equalsIgnoreAsciiCase( "BaseClass" ) )
391 nType
= ModuleType::FORM
;
393 if( (nType
!= ModuleType::UNKNOWN
) && !aValue
.isEmpty() )
395 OSL_ENSURE( maModules
.has( aValue
), "VbaProject::importVba - module not found" );
396 if( VbaModule
* pModule
= maModules
.get( aValue
).get() )
397 pModule
->setType( nType
);
402 if( !maModules
.empty() ) try
404 /* Set library container to VBA compatibility mode. This will create
405 the VBA Globals object and store it in the Basic manager of the
409 Reference
< XVBACompatibility
> xVBACompat( getLibraryContainer( PROP_BasicLibraries
), UNO_QUERY_THROW
);
410 xVBACompat
->setVBACompatibilityMode( true );
411 xVBACompat
->setProjectName( maPrjName
);
414 catch(const Exception
& )
418 catch(const Exception
& )
423 void VbaProject::importModulesAndForms( StorageBase
& rVbaPrjStrg
, const GraphicHelper
& rGraphicHelper
)
425 StorageRef xVbaStrg
= rVbaPrjStrg
.openSubStorage( "VBA", false );
426 OSL_ENSURE( xVbaStrg
.get(), "VbaProject::importModulesAndForms - cannot open 'VBA' substorage" );
429 rtl_TextEncoding eTextEnc
= RTL_TEXTENCODING_MS_1252
;
430 bool bExecutable
= isImportVbaExecutable();
432 // create empty dummy modules
433 VbaModuleMap aDummyModules
;
434 for (auto const& dummyModule
: maDummyModules
)
436 OSL_ENSURE( !maModules
.has( dummyModule
.first
) && !aDummyModules
.has( dummyModule
.first
), "VbaProject::importVba - multiple modules with the same name" );
437 VbaModuleMap::mapped_type
& rxModule
= aDummyModules
[ dummyModule
.first
];
438 rxModule
.reset( new VbaModule( mxContext
, mxDocModel
, dummyModule
.first
, eTextEnc
, bExecutable
) );
439 rxModule
->setType( dummyModule
.second
);
442 /* Now it is time to load the source code. All modules will be inserted
443 into the Basic library of the document specified by the 'maPrjName'
444 member. Do not create the Basic library, if there are no modules
446 if( !maModules
.empty() || !aDummyModules
.empty() ) try
448 // get the model factory and the basic library
449 Reference
< XMultiServiceFactory
> xModelFactory( mxDocModel
, UNO_QUERY_THROW
);
450 Reference
< XNameContainer
> xBasicLib( createBasicLibrary(), UNO_SET_THROW
);
452 // try to get access to document objects related to code modules
453 Reference
< XNameAccess
> xDocObjectNA
;
456 xDocObjectNA
.set( xModelFactory
->createInstance( "ooo.vba.VBAObjectModuleObjectProvider" ), UNO_QUERY
);
458 catch(const Exception
& )
460 // not all documents support this
465 // #TODO cater for mxOleOverridesSink, like I used to before
466 // call Basic source code import for each module, std::[c]ref enforces pass-by-ref
467 maModules
.forEachMem( &VbaModule::createAndImportModule
,
468 ::std::ref( *xVbaStrg
), ::std::cref( xBasicLib
),
469 ::std::cref( xDocObjectNA
) );
471 // create empty dummy modules
472 aDummyModules
.forEachMem( &VbaModule::createEmptyModule
,
473 ::std::cref( xBasicLib
), ::std::cref( xDocObjectNA
) );
476 catch(const Exception
& )
480 /* Load the forms. The file format specification requires that a module
481 must exist for every form. We are a bit more tolerant and scan the
482 project storage for all form substorages. This may 'repair' broken VBA
483 storages that misses to mention a module for an existing form. */
484 ::std::vector
< OUString
> aElements
;
485 rVbaPrjStrg
.getElementNames( aElements
);
486 for (auto const& elem
: aElements
)
488 // try to open the element as storage
491 StorageRef xSubStrg
= rVbaPrjStrg
.openSubStorage( elem
, false );
492 if( xSubStrg
.get() ) try
494 // resolve module name from storage name (which equals the module stream name)
495 VbaModule
* pModule
= maModulesByStrm
.get( elem
).get();
496 OSL_ENSURE( pModule
&& (pModule
->getType() == ModuleType::FORM
),
497 "VbaProject::importVba - form substorage without form module" );
498 OUString aModuleName
;
500 aModuleName
= pModule
->getName();
502 // create and import the form
503 Reference
< XNameContainer
> xDialogLib( createDialogLibrary(), UNO_SET_THROW
);
504 VbaUserForm
aForm( mxContext
, mxDocModel
, rGraphicHelper
, true/*bDefaultColorBgr*/ );
505 aForm
.importForm( xDialogLib
, *xSubStrg
, aModuleName
, eTextEnc
);
507 catch(const Exception
& )
514 void VbaProject::attachMacros()
516 if( !maMacroAttachers
.empty() && mxContext
.is() ) try
518 Reference
< XMultiComponentFactory
> xFactory( mxContext
->getServiceManager(), UNO_SET_THROW
);
519 Sequence
< Any
> aArgs( 2 );
520 aArgs
[ 0 ] <<= mxDocModel
;
521 aArgs
[ 1 ] <<= maPrjName
;
522 Reference
< XVBAMacroResolver
> xResolver( xFactory
->createInstanceWithArgumentsAndContext(
523 "com.sun.star.script.vba.VBAMacroResolver", aArgs
, mxContext
), UNO_QUERY_THROW
);
524 maMacroAttachers
.forEachMem( &VbaMacroAttacherBase::resolveAndAttachMacro
, ::std::cref( xResolver
) );
526 catch(const Exception
& )
531 void VbaProject::copyStorage( StorageBase
& rVbaPrjStrg
)
533 if( mxContext
.is() ) try
535 Reference
< XStorageBasedDocument
> xStorageBasedDoc( mxDocModel
, UNO_QUERY_THROW
);
536 Reference
< XStorage
> xDocStorage( xStorageBasedDoc
->getDocumentStorage(), UNO_SET_THROW
);
538 const sal_Int32 nOpenMode
= ElementModes::SEEKABLE
| ElementModes::WRITE
| ElementModes::TRUNCATE
;
539 Reference
< XStream
> xDocStream( xDocStorage
->openStreamElement( "_MS_VBA_Macros", nOpenMode
), UNO_SET_THROW
);
540 OleStorage
aDestStorage( mxContext
, xDocStream
, false );
541 rVbaPrjStrg
.copyStorageToStorage( aDestStorage
);
542 aDestStorage
.commit();
544 Reference
< XTransactedObject
>( xDocStorage
, UNO_QUERY_THROW
)->commit();
546 catch(const Exception
& )
554 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */