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/documentinfo.hxx>
35 #include <comphelper/storagehelper.hxx>
36 #include <osl/diagnose.h>
37 #include <rtl/tencinfo.h>
38 #include <sal/log.hxx>
39 #include <oox/helper/binaryinputstream.hxx>
40 #include <oox/helper/propertyset.hxx>
41 #include <oox/helper/textinputstream.hxx>
42 #include <oox/ole/olestorage.hxx>
43 #include <oox/ole/vbacontrol.hxx>
44 #include <oox/ole/vbahelper.hxx>
45 #include <oox/ole/vbainputstream.hxx>
46 #include <oox/ole/vbamodule.hxx>
47 #include <oox/token/properties.hxx>
51 using namespace ::com::sun::star
;
52 using namespace ::com::sun::star::container
;
53 using namespace ::com::sun::star::document
;
54 using namespace ::com::sun::star::embed
;
55 using namespace ::com::sun::star::frame
;
56 using namespace ::com::sun::star::io
;
57 using namespace ::com::sun::star::lang
;
58 using namespace ::com::sun::star::script
;
59 using namespace ::com::sun::star::script::vba
;
60 using namespace ::com::sun::star::uno
;
62 using ::comphelper::ConfigurationHelper
;
66 bool lclReadConfigItem( const Reference
< XInterface
>& rxConfigAccess
, const OUString
& rItemName
)
68 // some applications do not support all configuration items, assume 'false' in this case
71 Any aItem
= ConfigurationHelper::readRelativeKey( rxConfigAccess
, "Filter/Import/VBA", rItemName
);
72 return aItem
.has
< bool >() && aItem
.get
< bool >();
74 catch(const Exception
& )
82 VbaFilterConfig::VbaFilterConfig( const Reference
< XComponentContext
>& rxContext
, const OUString
& rConfigCompName
)
84 OSL_ENSURE( rxContext
.is(), "VbaFilterConfig::VbaFilterConfig - missing component context" );
85 if( rxContext
.is() ) try
87 OSL_ENSURE( !rConfigCompName
.isEmpty(), "VbaFilterConfig::VbaFilterConfig - invalid configuration component name" );
88 OUString aConfigPackage
= "org.openoffice.Office." + rConfigCompName
;
89 mxConfigAccess
= ConfigurationHelper::openConfig( rxContext
, aConfigPackage
, comphelper::EConfigurationModes::ReadOnly
);
91 catch(const Exception
& )
94 OSL_ENSURE( mxConfigAccess
.is(), "VbaFilterConfig::VbaFilterConfig - cannot open configuration" );
97 VbaFilterConfig::~VbaFilterConfig()
101 bool VbaFilterConfig::isImportVba() const
103 return lclReadConfigItem( mxConfigAccess
, "Load" );
106 bool VbaFilterConfig::isImportVbaExecutable() const
108 return lclReadConfigItem( mxConfigAccess
, "Executable" );
111 bool VbaFilterConfig::isExportVba() const
113 return lclReadConfigItem( mxConfigAccess
, "Save" );
116 VbaMacroAttacherBase::VbaMacroAttacherBase( const OUString
& rMacroName
) :
117 maMacroName( rMacroName
)
119 OSL_ENSURE( !maMacroName
.isEmpty(), "VbaMacroAttacherBase::VbaMacroAttacherBase - empty macro name" );
122 VbaMacroAttacherBase::~VbaMacroAttacherBase()
126 void VbaMacroAttacherBase::resolveAndAttachMacro( const Reference
< XVBAMacroResolver
>& rxResolver
)
130 attachMacro( rxResolver
->resolveVBAMacroToScriptURL( maMacroName
) );
132 catch(const Exception
& )
137 VbaProject::VbaProject( const Reference
< XComponentContext
>& rxContext
,
138 const Reference
< XModel
>& rxDocModel
, const OUString
& rConfigCompName
) :
139 VbaFilterConfig( rxContext
, rConfigCompName
),
140 mxContext( rxContext
),
141 mxDocModel( rxDocModel
),
142 maPrjName( "Standard" )
144 OSL_ENSURE( mxContext
.is(), "VbaProject::VbaProject - missing component context" );
145 OSL_ENSURE( mxDocModel
.is(), "VbaProject::VbaProject - missing document model" );
148 VbaProject::~VbaProject()
152 bool VbaProject::importVbaProject( StorageBase
& rVbaPrjStrg
)
154 // create GraphicHelper
155 Reference
< css::frame::XFrame
> xFrame
;
156 if ( mxDocModel
.is() )
158 Reference
< css::frame::XController
> xController
= mxDocModel
->getCurrentController();
159 xFrame
= xController
.is() ? xController
->getFrame() : nullptr;
161 StorageRef noStorage
;
162 // if the GraphicHelper tries to use noStorage it will of course crash
163 // but... this shouldn't happen as there is no reason for GraphicHelper
164 // to do that when importing VBA projects
165 GraphicHelper
grfHlp( mxContext
, xFrame
, noStorage
);
166 importVbaProject( rVbaPrjStrg
, grfHlp
);
167 // return true if something has been imported
168 return (mxBasicLib
.is() && mxBasicLib
->hasElements()) ||
169 (mxDialogLib
.is() && mxDialogLib
->hasElements());
172 void VbaProject::importVbaProject( StorageBase
& rVbaPrjStrg
, const GraphicHelper
& rGraphicHelper
)
174 if( rVbaPrjStrg
.isStorage() )
176 // load the code modules and forms
178 importVba( rVbaPrjStrg
, rGraphicHelper
);
179 // copy entire storage into model
181 copyStorage( rVbaPrjStrg
);
185 void VbaProject::importVbaData(const uno::Reference
<io::XInputStream
>& xInputStream
)
187 uno::Reference
<document::XStorageBasedDocument
> xStorageBasedDoc(mxDocModel
, uno::UNO_QUERY
);
188 uno::Reference
<embed::XStorage
> xDocStorage
= xStorageBasedDoc
->getDocumentStorage();
190 const sal_Int32 nOpenMode
= ElementModes::SEEKABLE
| ElementModes::WRITE
| ElementModes::TRUNCATE
;
191 uno::Reference
<io::XOutputStream
> xDocStream(xDocStorage
->openStreamElement("_MS_VBA_Macros_XML", nOpenMode
), uno::UNO_QUERY
);
192 comphelper::OStorageHelper::CopyInputToOutput(xInputStream
, xDocStream
);
194 uno::Reference
<embed::XTransactedObject
>(xDocStorage
, uno::UNO_QUERY_THROW
)->commit();
197 void VbaProject::registerMacroAttacher( const VbaMacroAttacherRef
& rxAttacher
)
199 OSL_ENSURE( rxAttacher
, "VbaProject::registerMacroAttacher - unexpected empty reference" );
200 maMacroAttachers
.push_back( rxAttacher
);
203 // protected ------------------------------------------------------------------
205 void VbaProject::addDummyModule( const OUString
& rName
, sal_Int32 nType
)
207 OSL_ENSURE( !rName
.isEmpty(), "VbaProject::addDummyModule - missing module name" );
208 maDummyModules
[ rName
] = nType
;
211 void VbaProject::prepareImport()
215 // private --------------------------------------------------------------------
217 Reference
< XLibraryContainer
> VbaProject::getLibraryContainer( sal_Int32 nPropId
)
219 PropertySet
aDocProp( mxDocModel
);
220 Reference
< XLibraryContainer
> xLibContainer( aDocProp
.getAnyProperty( nPropId
), UNO_QUERY
);
221 return xLibContainer
;
224 Reference
< XNameContainer
> VbaProject::openLibrary( sal_Int32 nPropId
)
226 Reference
< XNameContainer
> xLibrary
;
229 Reference
< XLibraryContainer
> xLibContainer( getLibraryContainer( nPropId
), UNO_SET_THROW
);
230 if( !xLibContainer
->hasByName( maPrjName
) )
231 xLibContainer
->createLibrary( maPrjName
);
232 xLibrary
.set( xLibContainer
->getByName( maPrjName
), UNO_QUERY_THROW
);
234 catch(const Exception
& )
237 OSL_ENSURE( xLibrary
.is(), "VbaProject::openLibrary - cannot create library" );
241 Reference
< XNameContainer
> const & VbaProject::createBasicLibrary()
243 if( !mxBasicLib
.is() )
244 mxBasicLib
= openLibrary( PROP_BasicLibraries
);
248 Reference
< XNameContainer
> const & VbaProject::createDialogLibrary()
250 if( !mxDialogLib
.is() )
251 mxDialogLib
= openLibrary( PROP_DialogLibraries
);
255 void VbaProject::importVba( StorageBase
& rVbaPrjStrg
, const GraphicHelper
& rGraphicHelper
)
257 readVbaModules( rVbaPrjStrg
);
258 importModulesAndForms(rVbaPrjStrg
, rGraphicHelper
);
259 // attach macros to registered objects
263 void VbaProject::readVbaModules( StorageBase
& rVbaPrjStrg
)
265 StorageRef xVbaStrg
= rVbaPrjStrg
.openSubStorage( "VBA", false );
266 OSL_ENSURE( xVbaStrg
, "VbaProject::readVbaModules - cannot open 'VBA' substorage" );
270 /* Read the 'VBA/dir' stream which contains general settings of the VBA
271 project such as the text encoding used throughout several streams, and
272 a list of all code modules.
274 BinaryXInputStream
aInStrm( xVbaStrg
->openInputStream( "dir" ), true );
275 // VbaInputStream implements decompression
276 VbaInputStream
aDirStrm( aInStrm
);
277 OSL_ENSURE( !aDirStrm
.isEof(), "VbaProject::importVba - cannot open 'dir' stream" );
278 if( aDirStrm
.isEof() )
281 // virtual call, derived classes may do some preparations
284 // read all records of the directory
285 rtl_TextEncoding eTextEnc
= RTL_TEXTENCODING_MS_1252
;
286 sal_uInt16 nModuleCount
= 0;
287 bool bExecutable
= isImportVbaExecutable();
289 sal_uInt16 nRecId
= 0;
290 StreamDataSequence aRecData
;
291 while( VbaHelper::readDirRecord( nRecId
, aRecData
, aDirStrm
) && (nRecId
!= VBA_ID_PROJECTEND
) )
293 // create record stream object from imported record data
294 SequenceInputStream
aRecStrm( aRecData
);
295 sal_Int32 nRecSize
= aRecData
.getLength();
298 case VBA_ID_PROJECTCODEPAGE
:
300 OSL_ENSURE( nRecSize
== 2, "VbaProject::importVba - invalid record size" );
301 OSL_ENSURE( maModules
.empty(), "VbaProject::importVba - unexpected PROJECTCODEPAGE record" );
302 rtl_TextEncoding eNewTextEnc
= rtl_getTextEncodingFromWindowsCodePage( aRecStrm
.readuInt16() );
303 OSL_ENSURE( eNewTextEnc
!= RTL_TEXTENCODING_DONTKNOW
, "VbaProject::importVba - unknown text encoding" );
304 if( eNewTextEnc
!= RTL_TEXTENCODING_DONTKNOW
)
305 eTextEnc
= eNewTextEnc
;
308 case VBA_ID_PROJECTNAME
:
310 OUString aPrjName
= aRecStrm
.readCharArrayUC( nRecSize
, eTextEnc
);
311 OSL_ENSURE( !aPrjName
.isEmpty(), "VbaProject::importVba - invalid project name" );
312 if( !aPrjName
.isEmpty() )
313 maPrjName
= aPrjName
;
316 case VBA_ID_PROJECTMODULES
:
317 OSL_ENSURE( nRecSize
== 2, "VbaProject::importVba - invalid record size" );
318 OSL_ENSURE( maModules
.empty(), "VbaProject::importVba - unexpected PROJECTMODULES record" );
319 nModuleCount
= aRecStrm
.readuInt16();
321 case VBA_ID_MODULENAME
:
323 OUString aName
= aRecStrm
.readCharArrayUC( nRecSize
, eTextEnc
);
324 OSL_ENSURE( !aName
.isEmpty(), "VbaProject::importVba - invalid module name" );
325 OSL_ENSURE( !maModules
.has( aName
), "VbaProject::importVba - multiple modules with the same name" );
326 VbaModuleMap::mapped_type
& rxModule
= maModules
[ aName
];
327 rxModule
= std::make_shared
<VbaModule
>( mxContext
, mxDocModel
, aName
, eTextEnc
, bExecutable
);
328 // read all remaining records until the MODULEEND record
329 rxModule
->importDirRecords( aDirStrm
);
330 OSL_ENSURE( !maModulesByStrm
.has( rxModule
->getStreamName() ), "VbaProject::importVba - multiple modules with the same stream name" );
331 maModulesByStrm
[ rxModule
->getStreamName() ] = rxModule
;
336 SAL_WARN_IF( nModuleCount
!= maModules
.size(), "oox", "VbaProject::importVba - invalid module count" );
338 /* The directory does not contain the real type of the modules, it
339 distinguishes only between 'procedural' and 'document' (the latter
340 includes class and form modules). Now, the exact type of all modules
341 will be read from the 'PROJECT' stream. It consists of text lines in
342 'key=value' format which list the code modules by type.
344 - The line 'document=<modulename>/&HXXXXXXXX' declares document
345 modules. These are attached to the Word document (usually called
346 'ThisDocument'), the Excel workbook (usually called
347 'ThisWorkbook'), or single Excel worksheets or chartsheets (usually
348 called 'SheetX' or 'ChartX', X being a decimal number). Of course,
349 users may rename all these modules. The slash character separates
350 an automation server version number (hexadecimal 'XXXXXXXX') from
352 - The line 'Module=<modulename>' declares common procedural code
354 - The line 'Class=<modulename>' declares a class module.
355 - The line 'BaseClass=<modulename>' declares a code module attached
356 to a user form with the same name.
358 BinaryXInputStream
aPrjStrm( rVbaPrjStrg
.openInputStream( "PROJECT" ), true );
359 OSL_ENSURE( !aPrjStrm
.isEof(), "VbaProject::importVba - cannot open 'PROJECT' stream" );
360 // do not exit if this stream does not exist, but proceed to load the modules below
361 if( !aPrjStrm
.isEof() )
363 TextInputStream
aPrjTextStrm( mxContext
, aPrjStrm
, eTextEnc
);
364 OUString aKey
, aValue
;
365 bool bExitLoop
= false;
366 while( !bExitLoop
&& !aPrjTextStrm
.isEof() )
368 // read a text line from the stream
369 OUString aLine
= aPrjTextStrm
.readLine().trim();
370 sal_Int32 nLineLen
= aLine
.getLength();
371 // exit if a subsection starts (section name is given in brackets)
372 bExitLoop
= (nLineLen
>= 2) && (aLine
[ 0 ] == '[') && (aLine
[ nLineLen
- 1 ] == ']');
373 if( !bExitLoop
&& VbaHelper::extractKeyValue( aKey
, aValue
, aLine
) )
375 sal_Int32 nType
= ModuleType::UNKNOWN
;
376 if( aKey
.equalsIgnoreAsciiCase( "Document" ) )
378 nType
= ModuleType::DOCUMENT
;
379 // strip automation server version from module names
380 sal_Int32 nSlashPos
= aValue
.indexOf( '/' );
382 aValue
= aValue
.copy( 0, nSlashPos
);
384 else if( aKey
.equalsIgnoreAsciiCase( "Module" ) )
385 nType
= ModuleType::NORMAL
;
386 else if( aKey
.equalsIgnoreAsciiCase( "Class" ) )
387 nType
= ModuleType::CLASS
;
388 else if( aKey
.equalsIgnoreAsciiCase( "BaseClass" ) )
389 nType
= ModuleType::FORM
;
391 if( (nType
!= ModuleType::UNKNOWN
) && !aValue
.isEmpty() )
393 OSL_ENSURE( maModules
.has( aValue
), "VbaProject::importVba - module not found" );
394 if( VbaModule
* pModule
= maModules
.get( aValue
).get() )
395 pModule
->setType( nType
);
401 if( maModules
.empty() )
406 /* Set library container to VBA compatibility mode. This will create
407 the VBA Globals object and store it in the Basic manager of the
411 Reference
< XVBACompatibility
> xVBACompat( getLibraryContainer( PROP_BasicLibraries
), UNO_QUERY_THROW
);
412 xVBACompat
->setVBACompatibilityMode( true );
413 xVBACompat
->setProjectName( maPrjName
);
416 catch(const Exception
& )
420 catch(const Exception
& )
425 void VbaProject::importModulesAndForms( StorageBase
& rVbaPrjStrg
, const GraphicHelper
& rGraphicHelper
)
427 StorageRef xVbaStrg
= rVbaPrjStrg
.openSubStorage( "VBA", false );
428 OSL_ENSURE( xVbaStrg
, "VbaProject::importModulesAndForms - cannot open 'VBA' substorage" );
431 rtl_TextEncoding eTextEnc
= RTL_TEXTENCODING_MS_1252
;
432 bool bExecutable
= isImportVbaExecutable();
434 // create empty dummy modules
435 VbaModuleMap aDummyModules
;
436 for (auto const& dummyModule
: maDummyModules
)
438 OSL_ENSURE( !maModules
.has( dummyModule
.first
) && !aDummyModules
.has( dummyModule
.first
), "VbaProject::importVba - multiple modules with the same name" );
439 VbaModuleMap::mapped_type
& rxModule
= aDummyModules
[ dummyModule
.first
];
440 rxModule
= std::make_shared
<VbaModule
>( mxContext
, mxDocModel
, dummyModule
.first
, eTextEnc
, bExecutable
);
441 rxModule
->setType( dummyModule
.second
);
444 /* Now it is time to load the source code. All modules will be inserted
445 into the Basic library of the document specified by the 'maPrjName'
446 member. Do not create the Basic library, if there are no modules
448 if( !maModules
.empty() || !aDummyModules
.empty() ) try
450 // get the model factory and the basic library
451 Reference
< XMultiServiceFactory
> xModelFactory( mxDocModel
, UNO_QUERY_THROW
);
452 Reference
< XNameContainer
> xBasicLib( createBasicLibrary(), UNO_SET_THROW
);
454 // try to get access to document objects related to code modules
455 Reference
< XNameAccess
> xDocObjectNA
;
458 xDocObjectNA
.set( xModelFactory
->createInstance( "ooo.vba.VBAObjectModuleObjectProvider" ), UNO_QUERY
);
460 catch(const Exception
& )
462 // not all documents support this
467 // #TODO cater for mxOleOverridesSink, like I used to before
468 // call Basic source code import for each module, std::[c]ref enforces pass-by-ref
469 maModules
.forEachMem( &VbaModule::createAndImportModule
,
470 ::std::ref( *xVbaStrg
), ::std::cref( xBasicLib
),
471 ::std::cref( xDocObjectNA
) );
473 // create empty dummy modules
474 aDummyModules
.forEachMem( &VbaModule::createEmptyModule
,
475 ::std::cref( xBasicLib
), ::std::cref( xDocObjectNA
) );
478 catch(const Exception
& )
482 /* Load the forms. The file format specification requires that a module
483 must exist for every form. We are a bit more tolerant and scan the
484 project storage for all form substorages. This may 'repair' broken VBA
485 storages that misses to mention a module for an existing form. */
486 ::std::vector
< OUString
> aElements
;
487 rVbaPrjStrg
.getElementNames( aElements
);
488 for (auto const& elem
: aElements
)
490 // try to open the element as storage
493 StorageRef xSubStrg
= rVbaPrjStrg
.openSubStorage( elem
, false );
496 // resolve module name from storage name (which equals the module stream name)
497 VbaModule
* pModule
= maModulesByStrm
.get( elem
).get();
498 OSL_ENSURE( pModule
&& (pModule
->getType() == ModuleType::FORM
),
499 "VbaProject::importVba - form substorage without form module" );
500 OUString aModuleName
;
502 aModuleName
= pModule
->getName();
504 // create and import the form
505 Reference
< XNameContainer
> xDialogLib( createDialogLibrary(), UNO_SET_THROW
);
506 VbaUserForm
aForm( mxContext
, mxDocModel
, rGraphicHelper
, true/*bDefaultColorBgr*/ );
507 aForm
.importForm( xDialogLib
, *xSubStrg
, aModuleName
, eTextEnc
);
509 catch(const Exception
& )
516 void VbaProject::attachMacros()
518 if( maMacroAttachers
.empty() || !mxContext
.is() )
523 comphelper::DocumentInfo::notifyMacroEventRead(mxDocModel
);
525 Reference
< XMultiComponentFactory
> xFactory( mxContext
->getServiceManager(), UNO_SET_THROW
);
526 Sequence
< Any
> aArgs( 2 );
527 aArgs
[ 0 ] <<= mxDocModel
;
528 aArgs
[ 1 ] <<= maPrjName
;
529 Reference
< XVBAMacroResolver
> xResolver( xFactory
->createInstanceWithArgumentsAndContext(
530 "com.sun.star.script.vba.VBAMacroResolver", aArgs
, mxContext
), UNO_QUERY_THROW
);
531 maMacroAttachers
.forEachMem( &VbaMacroAttacherBase::resolveAndAttachMacro
, ::std::cref( xResolver
) );
534 catch(const Exception
& )
539 void VbaProject::copyStorage( StorageBase
& rVbaPrjStrg
)
541 if( !mxContext
.is() )
546 Reference
< XStorageBasedDocument
> xStorageBasedDoc( mxDocModel
, UNO_QUERY_THROW
);
547 Reference
< XStorage
> xDocStorage( xStorageBasedDoc
->getDocumentStorage(), UNO_SET_THROW
);
549 const sal_Int32 nOpenMode
= ElementModes::SEEKABLE
| ElementModes::WRITE
| ElementModes::TRUNCATE
;
550 Reference
< XStream
> xDocStream( xDocStorage
->openStreamElement( "_MS_VBA_Macros", nOpenMode
), UNO_SET_THROW
);
551 OleStorage
aDestStorage( mxContext
, xDocStream
, false );
552 rVbaPrjStrg
.copyStorageToStorage( aDestStorage
);
553 aDestStorage
.commit();
555 Reference
< XTransactedObject
>( xDocStorage
, UNO_QUERY_THROW
)->commit();
557 catch(const Exception
& )
564 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */